Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of Map() #550

Merged
merged 33 commits into from
Jul 17, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d79b1c7
Initial implementation of Map()
joshwd36 Jul 5, 2020
352a78d
Remove impl InternalState for HashMap
joshwd36 Jul 5, 2020
aadf771
Fix clippy lint
joshwd36 Jul 5, 2020
cf925ce
Actually fix clippy lint
joshwd36 Jul 5, 2020
029863f
Fix ok_or lint
joshwd36 Jul 5, 2020
85a3702
format code
joshwd36 Jul 5, 2020
11c4004
Use IndexMap instead of HashMap
joshwd36 Jul 6, 2020
72d8786
Initial implementation of Map()
joshwd36 Jul 5, 2020
f11be4d
Remove impl InternalState for HashMap
joshwd36 Jul 5, 2020
57c87d9
Fix clippy lint
joshwd36 Jul 5, 2020
329e580
Actually fix clippy lint
joshwd36 Jul 5, 2020
3bc79d9
Fix ok_or lint
joshwd36 Jul 5, 2020
07f64af
format code
joshwd36 Jul 5, 2020
5e9358b
Rebase upstream
joshwd36 Jul 11, 2020
84293d9
Prevent recursive display for map and array
joshwd36 Jul 11, 2020
d381a91
Merge
joshwd36 Jul 11, 2020
e663666
Run rustfmt
joshwd36 Jul 11, 2020
96e82d2
Merge create into init
joshwd36 Jul 11, 2020
cce249a
Fix return type on init
joshwd36 Jul 11, 2020
a6e093a
Initial implementation of preventing Map() being called as a function
joshwd36 Jul 11, 2020
77a0833
Use bitflags for callable and constructable
joshwd36 Jul 11, 2020
374f854
FunctionFlags doesn't have to be public
joshwd36 Jul 11, 2020
f21e217
Initial implementation of Map()
joshwd36 Jul 5, 2020
3c37294
Rebase upstream
joshwd36 Jul 11, 2020
a9853b8
Prevent recursive display for map and array
joshwd36 Jul 11, 2020
2a6d23e
Initial implementation of Map()
joshwd36 Jul 5, 2020
0af0d8e
Use IndexMap instead of HashMap
joshwd36 Jul 6, 2020
c05028e
Run rustfmt
joshwd36 Jul 11, 2020
1db2ee4
Remove artifact from rebase
joshwd36 Jul 14, 2020
1730256
Use into() for value creation
joshwd36 Jul 14, 2020
7716cbd
Remove GcCell around OrderedMap
joshwd36 Jul 14, 2020
ae519c3
Remove commented get_map function
joshwd36 Jul 14, 2020
213747b
Merge branch 'master' into map
HalidOdat Jul 17, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions boa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ rustc-hash = "1.1.0"
num-bigint = { version = "0.3.0", features = ["serde"] }
num-integer = "0.1.43"
bitflags = "1.2.1"
indexmap = "1.4.0"

# Optional Dependencies
serde = { version = "1.0.114", features = ["derive"], optional = true }
Expand Down
328 changes: 328 additions & 0 deletions boa/src/builtins/map/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
#![allow(clippy::mutable_key_type)]

use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{
builtins::{
object::{ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE},
property::Property,
value::{ResultValue, Value},
},
exec::Interpreter,
BoaProfiler,
};
use gc::GcCell;
use ordered_map::OrderedMap;

pub mod ordered_map;
#[cfg(test)]
mod tests;

#[derive(Debug, Clone)]
pub(crate) struct Map(OrderedMap<Value, Value>);

impl Map {
pub(crate) const NAME: &'static str = "Map";

pub(crate) const LENGTH: usize = 1;

/// Helper function to get the map object.
/*fn get_map(this: &Value, ctx: &mut Interpreter) -> Result<OrderedMap<Value, Value>, Value> {
if let Value::Object(ref object) = this {
let object = object.borrow();
if let Some(map) = object.as_map() {
return Ok(map);
}
}
Err(ctx.construct_type_error("'this' is not a Map"))
}*/

/// Helper function to set the size property.
fn set_size(this: &Value, size: usize) {
let size = Property::new()
.value(Value::from(size))
.writable(false)
.configurable(false)
.enumerable(false);

this.set_property("size".to_string(), size);
}

/// `Map.prototype.set( key, value )`
///
/// This method associates the value with the key. Returns the map object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.set
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set
pub(crate) fn set(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let (key, value) = match args.len() {
0 => (Value::Undefined, Value::Undefined),
1 => (args[0].clone(), Value::Undefined),
_ => (args[0].clone(), args[1].clone()),
};

let size = if let Value::Object(ref object) = this {
let object = object.borrow();
if let Some(map) = object.as_map_ref() {
let mut map = map.borrow_mut();
map.insert(key, value);
map.len()
} else {
return Err(ctx.construct_type_error("'this' is not a Map"));
}
} else {
return Err(ctx.construct_type_error("'this' is not a Map"));
};
HalidOdat marked this conversation as resolved.
Show resolved Hide resolved

Self::set_size(this, size);
Ok(this.clone())
}

/// `Map.prototype.delete( key )`
///
/// This method removes the element associated with the key, if it exists. Returns true if there was an element, false otherwise.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.delete
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete
pub(crate) fn delete(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let undefined = Value::Undefined;
let key = match args.len() {
0 => &undefined,
_ => &args[0],
};

let (deleted, size) = if let Value::Object(ref object) = this {
let object = object.borrow();
if let Some(map) = object.as_map_ref() {
let mut map = map.borrow_mut();
let deleted = map.remove(key).is_some();
(deleted, map.len())
} else {
return Err(ctx.construct_type_error("'this' is not a Map"));
}
} else {
return Err(ctx.construct_type_error("'this' is not a Map"));
};
Self::set_size(this, size);
Ok(Value::boolean(deleted))
}

/// `Map.prototype.get( key )`
///
/// This method returns the value associated with the key, or undefined if there is none.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.get
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get
pub(crate) fn get(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let undefined = Value::Undefined;
let key = match args.len() {
0 => &undefined,
_ => &args[0],
};

if let Value::Object(ref object) = this {
let object = object.borrow();
if let Some(map) = object.as_map_ref() {
return Ok(if let Some(result) = map.borrow().get(key) {
result.clone()
} else {
Value::Undefined
});
}
}

Err(ctx.construct_type_error("'this' is not a Map"))
}

/// `Map.prototype.clear( )`
///
/// This method removes all entries from the map.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.clear
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear
pub(crate) fn clear(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.set_data(ObjectData::Map(GcCell::new(OrderedMap::new())));

Self::set_size(this, 0);

Ok(Value::Undefined)
}

/// `Map.prototype.has( key )`
///
/// This method checks if the map contains an entry with the given key.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has
pub(crate) fn has(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let undefined = Value::Undefined;
let key = match args.len() {
0 => &undefined,
_ => &args[0],
};

if let Value::Object(ref object) = this {
let object = object.borrow();
if let Some(map) = object.as_map_ref() {
return Ok(Value::Boolean(map.borrow().contains_key(key)));
}
}

Err(ctx.construct_type_error("'this' is not a Map"))
}

/// `Map.prototype.forEach( callbackFn [ , thisArg ] )`
///
/// This method executes the provided callback function for each key-value pair in the map.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.foreach
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach
pub(crate) fn for_each(
this: &Value,
args: &[Value],
interpreter: &mut Interpreter,
) -> ResultValue {
if args.is_empty() {
return Err(Value::from("Missing argument for Map.prototype.forEach"));
}

let callback_arg = &args[0];
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined);

if let Value::Object(ref object) = this {
let object = object.borrow();
if let Some(map) = object.as_map_clone() {
for (key, value) in map {
let arguments = [value, key, this.clone()];

interpreter.call(callback_arg, &this_arg, &arguments)?;
}
}
}

Ok(Value::Undefined)
}

/// Helper function to get a key-value pair from an array.
fn get_key_value(value: &Value) -> Option<(Value, Value)> {
if let Value::Object(object) = value {
if object.borrow().is_array() {
let (key, value) = match i32::from(&value.get_field("length")) {
0 => (Value::Undefined, Value::Undefined),
1 => (value.get_field("0"), Value::Undefined),
_ => (value.get_field("0"), value.get_field("1")),
};
return Some((key, value));
}
}
None
}

/// Create a new map
pub(crate) fn make_map(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// Make a new Object which will internally represent the Array (mapping
// between indices and values): this creates an Object with no prototype

// Set Prototype
let prototype = ctx.realm.global_obj.get_field("Map").get_field(PROTOTYPE);

this.set_internal_slot(INSTANCE_PROTOTYPE, prototype);
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)

// add our arguments in
let data = match args.len() {
0 => OrderedMap::new(),
_ => match &args[0] {
Value::Object(object) => {
let object = object.borrow();
if let Some(map) = object.as_map_clone() {
map
} else if object.is_array() {
let mut map = OrderedMap::new();
let len = i32::from(&args[0].get_field("length"));
for i in 0..len {
let val = &args[0].get_field(i.to_string());
let (key, value) = Self::get_key_value(val).ok_or_else(|| {
ctx.construct_type_error(
"iterable for Map should have array-like objects",
)
})?;
map.insert(key, value);
}
map
} else {
return Err(ctx.construct_type_error(
"iterable for Map should have array-like objects",
));
}
}
_ => {
return Err(
ctx.construct_type_error("iterable for Map should have array-like objects")
)
}
},
};

// finally create length property
Self::set_size(this, data.len());

this.set_data(ObjectData::Map(GcCell::new(data)));

Ok(this.clone())
}

/// Create a new `Map` object.
pub(crate) fn create(global: &Value) -> Value {
// Create prototype
let prototype = Value::new_object(Some(global));

make_builtin_fn(Self::set, "set", &prototype, 2);
make_builtin_fn(Self::delete, "delete", &prototype, 1);
make_builtin_fn(Self::get, "get", &prototype, 1);
make_builtin_fn(Self::clear, "clear", &prototype, 0);
make_builtin_fn(Self::has, "has", &prototype, 1);
make_builtin_fn(Self::for_each, "forEach", &prototype, 1);

make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_map,
global,
prototype,
true,
)
}

/// Initialise the `Map` object on the global object.
#[inline]
pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");

(Self::NAME, Self::create(global))
}
joshwd36 marked this conversation as resolved.
Show resolved Hide resolved
}
Loading