Browse Source

Initial implementation of Map() (#550)

Co-authored-by: HalidOdat <halidodat@gmail.com>
pull/579/head
joshwd36 4 years ago committed by GitHub
parent
commit
1c1132d8e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      Cargo.lock
  2. 1
      boa/Cargo.toml
  3. 1
      boa/src/builtins/array/mod.rs
  4. 1
      boa/src/builtins/bigint/mod.rs
  5. 1
      boa/src/builtins/boolean/mod.rs
  6. 1
      boa/src/builtins/error/mod.rs
  7. 1
      boa/src/builtins/error/range.rs
  8. 1
      boa/src/builtins/error/reference.rs
  9. 1
      boa/src/builtins/error/syntax.rs
  10. 1
      boa/src/builtins/error/type.rs
  11. 60
      boa/src/builtins/function/mod.rs
  12. 311
      boa/src/builtins/map/mod.rs
  13. 144
      boa/src/builtins/map/ordered_map.rs
  14. 231
      boa/src/builtins/map/tests.rs
  15. 3
      boa/src/builtins/mod.rs
  16. 1
      boa/src/builtins/number/mod.rs
  17. 27
      boa/src/builtins/object/mod.rs
  18. 1
      boa/src/builtins/regexp/mod.rs
  19. 1
      boa/src/builtins/string/mod.rs
  20. 1
      boa/src/builtins/symbol/mod.rs
  21. 38
      boa/src/builtins/value/display.rs
  22. 33
      boa/src/exec/mod.rs

14
Cargo.lock generated

@ -7,6 +7,7 @@ dependencies = [
"bitflags", "bitflags",
"criterion", "criterion",
"gc", "gc",
"indexmap",
"jemallocator", "jemallocator",
"measureme", "measureme",
"num-bigint", "num-bigint",
@ -145,9 +146,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.58" version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" checksum = "0fde55d2a2bfaa4c9668bbc63f531fbdeee3ffe188f4662511ce2c22b3eedebe"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -373,6 +374,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "indexmap"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.9.0" version = "0.9.0"

1
boa/Cargo.toml

@ -23,6 +23,7 @@ rustc-hash = "1.1.0"
num-bigint = { version = "0.3.0", features = ["serde"] } num-bigint = { version = "0.3.0", features = ["serde"] }
num-integer = "0.1.43" num-integer = "0.1.43"
bitflags = "1.2.1" bitflags = "1.2.1"
indexmap = "1.4.0"
ryu-js = "0.2.0" ryu-js = "0.2.0"
# Optional Dependencies # Optional Dependencies

1
boa/src/builtins/array/mod.rs

@ -1064,6 +1064,7 @@ impl Array {
global, global,
prototype, prototype,
true, true,
true,
); );
// Static Methods // Static Methods

1
boa/src/builtins/bigint/mod.rs

@ -215,6 +215,7 @@ impl BigInt {
global, global,
prototype, prototype,
false, false,
true,
); );
make_builtin_fn(Self::as_int_n, "asIntN", &bigint_object, 2); make_builtin_fn(Self::as_int_n, "asIntN", &bigint_object, 2);

1
boa/src/builtins/boolean/mod.rs

@ -115,6 +115,7 @@ impl Boolean {
global, global,
prototype, prototype,
true, true,
true,
); );
(Self::NAME, boolean_object) (Self::NAME, boolean_object)

1
boa/src/builtins/error/mod.rs

@ -92,6 +92,7 @@ impl Error {
global, global,
prototype, prototype,
true, true,
true,
); );
(Self::NAME, error_object) (Self::NAME, error_object)

1
boa/src/builtins/error/range.rs

@ -78,6 +78,7 @@ impl RangeError {
global, global,
prototype, prototype,
true, true,
true,
); );
(Self::NAME, range_error_object) (Self::NAME, range_error_object)

1
boa/src/builtins/error/reference.rs

@ -76,6 +76,7 @@ impl ReferenceError {
global, global,
prototype, prototype,
true, true,
true,
); );
(Self::NAME, reference_error_object) (Self::NAME, reference_error_object)

1
boa/src/builtins/error/syntax.rs

@ -80,6 +80,7 @@ impl SyntaxError {
global, global,
prototype, prototype,
true, true,
true,
); );
(Self::NAME, syntax_error_object) (Self::NAME, syntax_error_object)

1
boa/src/builtins/error/type.rs

@ -84,6 +84,7 @@ impl TypeError {
global, global,
prototype, prototype,
true, true,
true,
); );
(Self::NAME, type_error_object) (Self::NAME, type_error_object)

60
boa/src/builtins/function/mod.rs

@ -24,6 +24,7 @@ use crate::{
syntax::ast::node::{FormalParameter, StatementList}, syntax::ast::node::{FormalParameter, StatementList},
BoaProfiler, BoaProfiler,
}; };
use bitflags::bitflags;
use gc::{unsafe_empty_trace, Finalize, Trace}; use gc::{unsafe_empty_trace, Finalize, Trace};
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
@ -90,6 +91,43 @@ unsafe impl Trace for FunctionBody {
unsafe_empty_trace!(); unsafe_empty_trace!();
} }
bitflags! {
#[derive(Finalize, Default)]
struct FunctionFlags: u8 {
const CALLABLE = 0b0000_0001;
const CONSTRUCTABLE = 0b0000_0010;
}
}
impl FunctionFlags {
fn from_parameters(callable: bool, constructable: bool) -> Self {
let mut flags = Self::default();
if callable {
flags |= Self::CALLABLE;
}
if constructable {
flags |= Self::CONSTRUCTABLE;
}
flags
}
#[inline]
fn is_callable(&self) -> bool {
self.contains(Self::CALLABLE)
}
#[inline]
fn is_constructable(&self) -> bool {
self.contains(Self::CONSTRUCTABLE)
}
}
unsafe impl Trace for FunctionFlags {
unsafe_empty_trace!();
}
/// Boa representation of a Function Object. /// Boa representation of a Function Object.
/// ///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects> /// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
@ -103,10 +141,8 @@ pub struct Function {
pub this_mode: ThisMode, pub this_mode: ThisMode,
// Environment, built-in functions don't need Environments // Environment, built-in functions don't need Environments
pub environment: Option<Environment>, pub environment: Option<Environment>,
/// Is it constructable /// Is it constructable or
constructable: bool, flags: FunctionFlags,
/// Is it callable.
callable: bool,
} }
impl Function { impl Function {
@ -126,8 +162,7 @@ impl Function {
environment: scope, environment: scope,
params: parameter_list.into(), params: parameter_list.into(),
this_mode, this_mode,
constructable, flags: FunctionFlags::from_parameters(callable, constructable),
callable,
} }
} }
@ -183,7 +218,7 @@ impl Function {
interpreter: &mut Interpreter, interpreter: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let _timer = BoaProfiler::global().start_event("function::call", "function"); let _timer = BoaProfiler::global().start_event("function::call", "function");
if self.callable { if self.flags.is_callable() {
match self.body { match self.body {
FunctionBody::BuiltIn(func) => func(this, args_list, interpreter), FunctionBody::BuiltIn(func) => func(this, args_list, interpreter),
FunctionBody::Ordinary(ref body) => { FunctionBody::Ordinary(ref body) => {
@ -249,7 +284,7 @@ impl Function {
args_list: &[Value], args_list: &[Value],
interpreter: &mut Interpreter, interpreter: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
if self.constructable { if self.flags.is_constructable() {
match self.body { match self.body {
FunctionBody::BuiltIn(func) => { FunctionBody::BuiltIn(func) => {
func(this, args_list, interpreter)?; func(this, args_list, interpreter)?;
@ -351,12 +386,12 @@ impl Function {
/// Returns true if the function object is callable. /// Returns true if the function object is callable.
pub fn is_callable(&self) -> bool { pub fn is_callable(&self) -> bool {
self.callable self.flags.is_callable()
} }
/// Returns true if the function object is constructable. /// Returns true if the function object is constructable.
pub fn is_constructable(&self) -> bool { pub fn is_constructable(&self) -> bool {
self.constructable self.flags.is_constructable()
} }
} }
@ -420,13 +455,14 @@ pub fn make_constructor_fn(
global: &Value, global: &Value,
prototype: Value, prototype: Value,
constructable: bool, constructable: bool,
callable: bool,
) -> Value { ) -> Value {
let _timer = let _timer =
BoaProfiler::global().start_event(&format!("make_constructor_fn: {}", name), "init"); BoaProfiler::global().start_event(&format!("make_constructor_fn: {}", name), "init");
// Create the native function // Create the native function
let mut function = Function::builtin(Vec::new(), body); let mut function = Function::builtin(Vec::new(), body);
function.constructable = constructable; function.flags = FunctionFlags::from_parameters(callable, constructable);
let mut constructor = Object::function(function); let mut constructor = Object::function(function);
@ -507,7 +543,7 @@ pub fn init(global: &Value) -> (&str, Value) {
let prototype = Value::new_object(Some(global)); let prototype = Value::new_object(Some(global));
let function_object = let function_object =
make_constructor_fn("Function", 1, make_function, global, prototype, true); make_constructor_fn("Function", 1, make_function, global, prototype, true, true);
("Function", function_object) ("Function", function_object)
} }

311
boa/src/builtins/map/mod.rs

@ -0,0 +1,311 @@
#![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 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 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 mut object = object.borrow_mut();
if let Some(map) = object.as_map_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"));
};
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 mut object = object.borrow_mut();
if let Some(map) = object.as_map_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(deleted.into())
}
/// `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.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(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(map.contains_key(key).into());
}
}
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_ref().cloned() {
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_ref().cloned() {
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(data));
Ok(this.clone())
}
/// Initialise the `Map` object on the global object.
pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// 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);
let map_object = make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_map,
global,
prototype,
true,
false,
);
(Self::NAME, map_object)
}
}

144
boa/src/builtins/map/ordered_map.rs

@ -0,0 +1,144 @@
use gc::{custom_trace, Finalize, Trace};
use indexmap::{map::IntoIter, map::Iter, map::IterMut, IndexMap};
use std::collections::hash_map::RandomState;
use std::fmt::Debug;
use std::hash::{BuildHasher, Hash};
/// A newtype wrapping indexmap::IndexMap
#[derive(Clone)]
pub struct OrderedMap<K, V, S = RandomState>(IndexMap<K, V, S>)
where
K: Hash + Eq;
impl<K: Eq + Hash + Trace, V: Trace, S: BuildHasher> Finalize for OrderedMap<K, V, S> {}
unsafe impl<K: Eq + Hash + Trace, V: Trace, S: BuildHasher> Trace for OrderedMap<K, V, S> {
custom_trace!(this, {
for (k, v) in this.0.iter() {
mark(k);
mark(v);
}
});
}
impl<K: Hash + Eq + Debug, V: Debug> Debug for OrderedMap<K, V> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
self.0.fmt(formatter)
}
}
impl<K: Hash + Eq, V> Default for OrderedMap<K, V> {
fn default() -> Self {
Self::new()
}
}
impl<K, V> OrderedMap<K, V>
where
K: Hash + Eq,
{
pub fn new() -> Self {
OrderedMap(IndexMap::new())
}
pub fn with_capacity(capacity: usize) -> Self {
OrderedMap(IndexMap::with_capacity(capacity))
}
/// Return the number of key-value pairs in the map.
///
/// Computes in **O(1)** time.
pub fn len(&self) -> usize {
self.0.len()
}
/// Returns true if the map contains no elements.
///
/// Computes in **O(1)** time.
pub fn is_empty(&self) -> bool {
self.0.len() == 0
}
/// Insert a key-value pair in the map.
///
/// If an equivalent key already exists in the map: the key remains and
/// retains in its place in the order, its corresponding value is updated
/// with `value` and the older value is returned inside `Some(_)`.
///
/// If no equivalent key existed in the map: the new key-value pair is
/// inserted, last in order, and `None` is returned.
///
/// Computes in **O(1)** time (amortized average).
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
self.0.insert(key, value)
}
/// Remove the key-value pair equivalent to `key` and return
/// its value.
///
/// Like `Vec::remove`, the pair is removed by shifting all of the
/// elements that follow it, preserving their relative order.
/// **This perturbs the index of all of those elements!**
///
/// Return `None` if `key` is not in map.
///
/// Computes in **O(n)** time (average).
pub fn remove(&mut self, key: &K) -> Option<V> {
self.0.shift_remove(key)
}
/// Return a reference to the value stored for `key`, if it is present,
/// else `None`.
///
/// Computes in **O(1)** time (average).
pub fn get(&self, key: &K) -> Option<&V> {
self.0.get(key)
}
/// Return an iterator over the key-value pairs of the map, in their order
pub fn iter(&self) -> Iter<'_, K, V> {
self.0.iter()
}
/// Return `true` if an equivalent to `key` exists in the map.
///
/// Computes in **O(1)** time (average).
pub fn contains_key(&self, key: &K) -> bool {
self.0.contains_key(key)
}
}
impl<'a, K, V, S> IntoIterator for &'a OrderedMap<K, V, S>
where
K: Hash + Eq,
S: BuildHasher,
{
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'a, K, V, S> IntoIterator for &'a mut OrderedMap<K, V, S>
where
K: Hash + Eq,
S: BuildHasher,
{
type Item = (&'a K, &'a mut V);
type IntoIter = IterMut<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}
impl<K, V, S> IntoIterator for OrderedMap<K, V, S>
where
K: Hash + Eq,
S: BuildHasher,
{
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> IntoIter<K, V> {
self.0.into_iter()
}
}

231
boa/src/builtins/map/tests.rs

@ -0,0 +1,231 @@
use crate::{exec::Interpreter, forward, realm::Realm};
#[test]
fn construct_empty() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = new Map();
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "empty.size");
assert_eq!(result, "0");
}
#[test]
fn construct_from_array() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let map = new Map([["1", "one"], ["2", "two"]]);
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "map.size");
assert_eq!(result, "2");
}
#[test]
fn clone() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let original = new Map([["1", "one"], ["2", "two"]]);
let clone = new Map(original);
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "clone.size");
assert_eq!(result, "2");
let result = forward(
&mut engine,
r#"
original.set("3", "three");
original.size"#,
);
assert_eq!(result, "3");
let result = forward(&mut engine, "clone.size");
assert_eq!(result, "2");
}
#[test]
fn merge() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let first = new Map([["1", "one"], ["2", "two"]]);
let second = new Map([["2", "second two"], ["3", "three"]]);
let third = new Map([["4", "four"], ["5", "five"]]);
let merged1 = new Map([...first, ...second]);
let merged2 = new Map([...second, ...third]);
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "merged1.size");
assert_eq!(result, "3");
let result = forward(&mut engine, "merged1.get('2')");
assert_eq!(result, "second two");
let result = forward(&mut engine, "merged2.size");
assert_eq!(result, "4");
}
#[test]
fn get() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let map = new Map([["1", "one"], ["2", "two"]]);
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "map.get('1')");
assert_eq!(result, "one");
let result = forward(&mut engine, "map.get('2')");
assert_eq!(result, "two");
let result = forward(&mut engine, "map.get('3')");
assert_eq!(result, "undefined");
let result = forward(&mut engine, "map.get()");
assert_eq!(result, "undefined");
}
#[test]
fn set() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let map = new Map();
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "map.set()");
assert_eq!(result, "Map { undefined → undefined }");
let result = forward(&mut engine, "map.set('1', 'one')");
assert_eq!(result, "Map { undefined → undefined, 1 → one }");
let result = forward(&mut engine, "map.set('2')");
assert_eq!(
result,
"Map { undefined → undefined, 1 → one, 2 → undefined }"
);
}
#[test]
fn clear() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let map = new Map([["1", "one"], ["2", "two"]]);
map.clear();
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "map.size");
assert_eq!(result, "0");
}
#[test]
fn delete() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let map = new Map([["1", "one"], ["2", "two"]]);
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "map.delete('1')");
assert_eq!(result, "true");
let result = forward(&mut engine, "map.size");
assert_eq!(result, "1");
let result = forward(&mut engine, "map.delete('1')");
assert_eq!(result, "false");
}
#[test]
fn has() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let map = new Map([["1", "one"]]);
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "map.has()");
assert_eq!(result, "false");
let result = forward(&mut engine, "map.has('1')");
assert_eq!(result, "true");
let result = forward(&mut engine, "map.has('2')");
assert_eq!(result, "false");
}
#[test]
fn for_each() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let map = new Map([[1, 5], [2, 10], [3, 15]]);
let valueSum = 0;
let keySum = 0;
let sizeSum = 0;
function callingCallback(value, key, map) {
valueSum += value;
keySum += key;
sizeSum += map.size;
}
map.forEach(callingCallback);
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "valueSum"), "30");
assert_eq!(forward(&mut engine, "keySum"), "6");
assert_eq!(forward(&mut engine, "sizeSum"), "9");
}
#[test]
fn modify_key() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let obj = new Object();
let map = new Map([[obj, "one"]]);
obj.field = "Value";
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "map.get(obj)");
assert_eq!(result, "one");
}
#[test]
fn order() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let map = new Map([[1, "one"]]);
map.set(2, "two");
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "map");
assert_eq!(result, "Map { 1 → one, 2 → two }");
let result = forward(&mut engine, "map.set(1, \"five\");map");
assert_eq!(result, "Map { 1 → five, 2 → two }");
let result = forward(&mut engine, "map.set();map");
assert_eq!(result, "Map { 1 → five, 2 → two, undefined → undefined }");
let result = forward(&mut engine, "map.delete(2);map");
assert_eq!(result, "Map { 1 → five, undefined → undefined }");
let result = forward(&mut engine, "map.set(2, \"two\");map");
assert_eq!(result, "Map { 1 → five, undefined → undefined, 2 → two }");
}
#[test]
fn recursive_display() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
let map = new Map();
let array = new Array([map]);
map.set("y", map);
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "map");
assert_eq!(result, "Map { y → Map(1) }");
let result = forward(&mut engine, "map.set(\"z\", array)");
assert_eq!(result, "Map { y → Map(2), z → Array(1) }");
}
#[test]
#[should_panic]
fn not_a_function() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = "let map = Map()";
forward(&mut engine, init);
}

3
boa/src/builtins/mod.rs

@ -9,6 +9,7 @@ pub mod function;
pub mod global_this; pub mod global_this;
pub mod infinity; pub mod infinity;
pub mod json; pub mod json;
pub mod map;
pub mod math; pub mod math;
pub mod nan; pub mod nan;
pub mod number; pub mod number;
@ -29,6 +30,7 @@ pub(crate) use self::{
global_this::GlobalThis, global_this::GlobalThis,
infinity::Infinity, infinity::Infinity,
json::Json, json::Json,
map::Map,
math::Math, math::Math,
nan::NaN, nan::NaN,
number::Number, number::Number,
@ -50,6 +52,7 @@ pub fn init(global: &Value) {
BigInt::init, BigInt::init,
Boolean::init, Boolean::init,
Json::init, Json::init,
Map::init,
Math::init, Math::init,
Number::init, Number::init,
RegExp::init, RegExp::init,

1
boa/src/builtins/number/mod.rs

@ -769,6 +769,7 @@ impl Number {
global, global,
prototype, prototype,
true, true,
true,
); );
make_builtin_fn(Self::number_is_finite, "isFinite", &number_object, 1); make_builtin_fn(Self::number_is_finite, "isFinite", &number_object, 1);

27
boa/src/builtins/object/mod.rs

@ -16,6 +16,7 @@
use crate::{ use crate::{
builtins::{ builtins::{
function::Function, function::Function,
map::ordered_map::OrderedMap,
property::Property, property::Property,
value::{RcBigInt, RcString, RcSymbol, ResultValue, Value}, value::{RcBigInt, RcString, RcSymbol, ResultValue, Value},
BigInt, BigInt,
@ -67,6 +68,7 @@ pub struct Object {
#[derive(Debug, Trace, Finalize, Clone)] #[derive(Debug, Trace, Finalize, Clone)]
pub enum ObjectData { pub enum ObjectData {
Array, Array,
Map(OrderedMap<Value, Value>),
BigInt(RcBigInt), BigInt(RcBigInt),
Boolean(bool), Boolean(bool),
Function(Function), Function(Function),
@ -85,6 +87,7 @@ impl Display for ObjectData {
match self { match self {
Self::Function(_) => "Function", Self::Function(_) => "Function",
Self::Array => "Array", Self::Array => "Array",
Self::Map(_) => "Map",
Self::String(_) => "String", Self::String(_) => "String",
Self::Symbol(_) => "Symbol", Self::Symbol(_) => "Symbol",
Self::Error => "Error", Self::Error => "Error",
@ -251,6 +254,28 @@ impl Object {
} }
} }
/// Checks if it is a `Map` object.pub
#[inline]
pub fn is_map(&self) -> bool {
matches!(self.data, ObjectData::Map(_))
}
#[inline]
pub fn as_map_ref(&self) -> Option<&OrderedMap<Value, Value>> {
match self.data {
ObjectData::Map(ref map) => Some(map),
_ => None,
}
}
#[inline]
pub fn as_map_mut(&mut self) -> Option<&mut OrderedMap<Value, Value>> {
match &mut self.data {
ObjectData::Map(map) => Some(map),
_ => None,
}
}
/// Checks if it a `String` object. /// Checks if it a `String` object.
#[inline] #[inline]
pub fn is_string(&self) -> bool { pub fn is_string(&self) -> bool {
@ -548,7 +573,7 @@ pub fn init(global: &Value) -> (&str, Value) {
); );
make_builtin_fn(to_string, "toString", &prototype, 0); make_builtin_fn(to_string, "toString", &prototype, 0);
let object = make_constructor_fn("Object", 1, make_object, global, prototype, true); let object = make_constructor_fn("Object", 1, make_object, global, prototype, true, true);
// static methods of the builtin Object // static methods of the builtin Object
make_builtin_fn(create, "create", &object, 2); make_builtin_fn(create, "create", &object, 2);

1
boa/src/builtins/regexp/mod.rs

@ -486,6 +486,7 @@ impl RegExp {
global, global,
prototype, prototype,
true, true,
true,
) )
} }

1
boa/src/builtins/string/mod.rs

@ -1042,6 +1042,7 @@ impl String {
global, global,
prototype, prototype,
true, true,
true,
); );
(Self::NAME, string_object) (Self::NAME, string_object)

1
boa/src/builtins/symbol/mod.rs

@ -115,6 +115,7 @@ impl Symbol {
global, global,
prototype, prototype,
false, false,
true,
); );
(Self::NAME, symbol_object) (Self::NAME, symbol_object)

38
boa/src/builtins/value/display.rs

@ -58,7 +58,7 @@ macro_rules! print_obj_value {
}; };
} }
pub(crate) fn log_string_from(x: &Value, print_internals: bool) -> String { pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children: bool) -> String {
match x { match x {
// We don't want to print private (compiler) or prototype properties // We don't want to print private (compiler) or prototype properties
Value::Object(ref v) => { Value::Object(ref v) => {
@ -78,6 +78,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool) -> String {
.expect("Could not borrow value"), .expect("Could not borrow value"),
); );
if print_children {
if len == 0 { if len == 0 {
return String::from("[]"); return String::from("[]");
} }
@ -95,12 +96,45 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool) -> String {
.clone() .clone()
.expect("Could not borrow value"), .expect("Could not borrow value"),
print_internals, print_internals,
false,
) )
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
format!("[ {} ]", arr) format!("[ {} ]", arr)
} else {
format!("Array({})", len)
}
}
ObjectData::Map(ref map) => {
let size = i32::from(
&v.borrow()
.properties()
.get("size")
.unwrap()
.value
.clone()
.expect("Could not borrow value"),
);
if size == 0 {
return String::from("Map(0)");
}
if print_children {
let mappings = map
.iter()
.map(|(key, value)| {
let key = log_string_from(key, print_internals, false);
let value = log_string_from(value, print_internals, false);
format!("{} → {}", key, value)
})
.collect::<Vec<String>>()
.join(", ");
format!("Map {{ {} }}", mappings)
} else {
format!("Map({})", size)
}
} }
_ => display_obj(&x, print_internals), _ => display_obj(&x, print_internals),
} }
@ -178,7 +212,7 @@ impl Display for Value {
}, },
Self::String(ref v) => write!(f, "{}", v), Self::String(ref v) => write!(f, "{}", v),
Self::Rational(v) => format_rational(*v, f), Self::Rational(v) => format_rational(*v, f),
Self::Object(_) => write!(f, "{}", log_string_from(self, true)), Self::Object(_) => write!(f, "{}", log_string_from(self, true, true)),
Self::Integer(v) => write!(f, "{}", v), Self::Integer(v) => write!(f, "{}", v),
Self::BigInt(ref num) => write!(f, "{}n", num), Self::BigInt(ref num) => write!(f, "{}n", num),
} }

33
boa/src/exec/mod.rs

@ -38,6 +38,7 @@ use crate::{
}, },
BoaProfiler, BoaProfiler,
}; };
use std::borrow::Borrow;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ops::Deref; use std::ops::Deref;
@ -377,6 +378,38 @@ impl Interpreter {
.collect(); .collect();
return Ok(values); return Ok(values);
} }
// Check if object is a Map
else if let ObjectData::Map(ref map) = x.deref().borrow().data {
let values = map
.borrow()
.iter()
.map(|(key, value)| {
// Construct a new array containing the key-value pair
let array = Value::new_object(Some(
&self
.realm()
.environment
.get_global_object()
.expect("Could not get global object"),
));
array.set_data(ObjectData::Array);
array.borrow().set_internal_slot(
INSTANCE_PROTOTYPE,
self.realm()
.environment
.get_binding_value("Array")
.expect("Array was not initialized")
.borrow()
.get_field(PROTOTYPE),
);
array.borrow().set_field("0", key);
array.borrow().set_field("1", value);
array.borrow().set_field("length", Value::from(2));
array
})
.collect();
return Ok(values);
}
return Err(()); return Err(());
} }

Loading…
Cancel
Save