Browse Source

[builtin Map] Map.prototype.entries method and map iterator (#847)

* Initial commit

* Improving on Map iterator

* Improvements on the iterator

* Almost finish the next method of MapIterator

* Add different kinds to next

* fmt

* Add function description. Add test.

* Added symbol_iterator method. Refactor to use exactly the same function as "entries". Added test for it, unignored pending test.

* Remove TODOs
pull/878/head
croraf 4 years ago committed by GitHub
parent
commit
ce535dd6d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      boa/src/builtins/array/mod.rs
  2. 11
      boa/src/builtins/iterable/mod.rs
  3. 155
      boa/src/builtins/map/map_iterator.rs
  4. 38
      boa/src/builtins/map/mod.rs
  5. 7
      boa/src/builtins/map/ordered_map.rs
  6. 73
      boa/src/builtins/map/tests.rs
  7. 1
      boa/src/builtins/mod.rs
  8. 11
      boa/src/object/mod.rs

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

@ -38,12 +38,14 @@ impl BuiltIn for Array {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
let symbol_iterator = context.well_known_symbols().iterator_symbol(); let symbol_iterator = context.well_known_symbols().iterator_symbol();
let values_function = FunctionBuilder::new(context, Self::values) let values_function = FunctionBuilder::new(context, Self::values)
.name("values") .name("values")
.length(0) .length(0)
.callable(true) .callable(true)
.constructable(false) .constructable(false)
.build(); .build();
let array = ConstructorBuilder::with_standard_object( let array = ConstructorBuilder::with_standard_object(
context, context,
Self::constructor, Self::constructor,

11
boa/src/builtins/iterable/mod.rs

@ -1,6 +1,7 @@
use crate::{ use crate::{
builtins::string::string_iterator::StringIterator, builtins::string::string_iterator::StringIterator,
builtins::ArrayIterator, builtins::ArrayIterator,
builtins::MapIterator,
object::{GcObject, ObjectInitializer}, object::{GcObject, ObjectInitializer},
property::{Attribute, DataDescriptor}, property::{Attribute, DataDescriptor},
BoaProfiler, Context, Result, Value, BoaProfiler, Context, Result, Value,
@ -11,6 +12,7 @@ pub struct IteratorPrototypes {
iterator_prototype: GcObject, iterator_prototype: GcObject,
array_iterator: GcObject, array_iterator: GcObject,
string_iterator: GcObject, string_iterator: GcObject,
map_iterator: GcObject,
} }
impl IteratorPrototypes { impl IteratorPrototypes {
@ -23,9 +25,12 @@ impl IteratorPrototypes {
array_iterator: ArrayIterator::create_prototype(ctx, iterator_prototype.clone()) array_iterator: ArrayIterator::create_prototype(ctx, iterator_prototype.clone())
.as_gc_object() .as_gc_object()
.expect("Array Iterator Prototype is not an object"), .expect("Array Iterator Prototype is not an object"),
string_iterator: StringIterator::create_prototype(ctx, iterator_prototype) string_iterator: StringIterator::create_prototype(ctx, iterator_prototype.clone())
.as_gc_object() .as_gc_object()
.expect("String Iterator Prototype is not an object"), .expect("String Iterator Prototype is not an object"),
map_iterator: MapIterator::create_prototype(ctx, iterator_prototype)
.as_gc_object()
.expect("Map Iterator Prototype is not an object"),
} }
} }
@ -40,6 +45,10 @@ impl IteratorPrototypes {
pub fn string_iterator(&self) -> GcObject { pub fn string_iterator(&self) -> GcObject {
self.string_iterator.clone() self.string_iterator.clone()
} }
pub fn map_iterator(&self) -> GcObject {
self.map_iterator.clone()
}
} }
/// CreateIterResultObject( value, done ) /// CreateIterResultObject( value, done )

155
boa/src/builtins/map/map_iterator.rs

@ -0,0 +1,155 @@
use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, Value},
object::ObjectData,
property::{Attribute, DataDescriptor},
BoaProfiler, Context, Result,
};
use gc::{Finalize, Trace};
#[derive(Debug, Clone, Finalize, Trace)]
pub enum MapIterationKind {
Key,
Value,
KeyAndValue,
}
/// The Map Iterator object represents an iteration over a map. It implements the iterator protocol.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: TODO https://tc39.es/ecma262/#sec-array-iterator-objects
#[derive(Debug, Clone, Finalize, Trace)]
pub struct MapIterator {
iterated_map: Value,
map_next_index: usize,
map_iteration_kind: MapIterationKind,
}
impl MapIterator {
pub(crate) const NAME: &'static str = "MapIterator";
fn new(map: Value, kind: MapIterationKind) -> Self {
MapIterator {
iterated_map: map,
map_next_index: 0,
map_iteration_kind: kind,
}
}
/// Abstract operation CreateMapIterator( map, kind )
///
/// Creates a new iterator over the given map.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://www.ecma-international.org/ecma-262/11.0/index.html#sec-createmapiterator
pub(crate) fn create_map_iterator(
ctx: &Context,
map: Value,
kind: MapIterationKind,
) -> Result<Value> {
let map_iterator = Value::new_object(Some(ctx.global_object()));
map_iterator.set_data(ObjectData::MapIterator(Self::new(map, kind)));
map_iterator
.as_object_mut()
.expect("map iterator object")
.set_prototype_instance(ctx.iterator_prototypes().map_iterator().into());
Ok(map_iterator)
}
/// %MapIteratorPrototype%.next( )
///
/// Advances the iterator and gets the next result in the map.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next
pub(crate) fn next(this: &Value, _args: &[Value], ctx: &mut Context) -> Result<Value> {
if let Value::Object(ref object) = this {
let mut object = object.borrow_mut();
if let Some(map_iterator) = object.as_map_iterator_mut() {
let m = &map_iterator.iterated_map;
let mut index = map_iterator.map_next_index;
let item_kind = &map_iterator.map_iteration_kind;
if map_iterator.iterated_map.is_undefined() {
return Ok(create_iter_result_object(ctx, Value::undefined(), true));
}
if let Value::Object(ref object) = m {
if let Some(entries) = object.borrow().as_map_ref() {
let num_entries = entries.len();
while index < num_entries {
let e = entries.get_index(index);
index += 1;
map_iterator.map_next_index = index;
if let Some((key, value)) = e {
match item_kind {
MapIterationKind::Key => {
return Ok(create_iter_result_object(
ctx,
key.clone(),
false,
));
}
MapIterationKind::Value => {
return Ok(create_iter_result_object(
ctx,
value.clone(),
false,
));
}
MapIterationKind::KeyAndValue => {
let result = Array::construct_array(
&Array::new_array(ctx)?,
&[key.clone(), value.clone()],
)?;
return Ok(create_iter_result_object(ctx, result, false));
}
}
}
}
} else {
return Err(ctx.construct_type_error("'this' is not a Map"));
}
} else {
return Err(ctx.construct_type_error("'this' is not a Map"));
}
map_iterator.iterated_map = Value::undefined();
Ok(create_iter_result_object(ctx, Value::undefined(), true))
} else {
ctx.throw_type_error("`this` is not an MapIterator")
}
} else {
ctx.throw_type_error("`this` is not an MapIterator")
}
}
/// Create the %MapIteratorPrototype% object
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%-object
pub(crate) fn create_prototype(ctx: &mut Context, iterator_prototype: Value) -> Value {
let global = ctx.global_object();
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype
let map_iterator = Value::new_object(Some(global));
make_builtin_fn(Self::next, "next", &map_iterator, 0, ctx);
map_iterator
.as_object_mut()
.expect("map iterator prototype object")
.set_prototype_instance(iterator_prototype);
let to_string_tag = ctx.well_known_symbols().to_string_tag_symbol();
let to_string_tag_property = DataDescriptor::new("Map Iterator", Attribute::CONFIGURABLE);
map_iterator.set_property(to_string_tag, to_string_tag_property);
map_iterator
}
}

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

@ -2,12 +2,15 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData, PROTOTYPE}, object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE},
property::{Attribute, DataDescriptor}, property::{Attribute, DataDescriptor},
BoaProfiler, Context, Result, Value, BoaProfiler, Context, Result, Value,
}; };
use ordered_map::OrderedMap; use ordered_map::OrderedMap;
pub mod map_iterator;
use map_iterator::{MapIterationKind, MapIterator};
pub mod ordered_map; pub mod ordered_map;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -25,9 +28,28 @@ impl BuiltIn for Map {
fn init(context: &mut Context) -> (&'static str, Value, Attribute) { fn init(context: &mut Context) -> (&'static str, Value, Attribute) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
let iterator_symbol = context.well_known_symbols().iterator_symbol();
let entries_function = FunctionBuilder::new(context, Self::entries)
.name("entries")
.length(0)
.callable(true)
.constructable(false)
.build();
let map_object = ConstructorBuilder::new(context, Self::constructor) let map_object = ConstructorBuilder::new(context, Self::constructor)
.name(Self::NAME) .name(Self::NAME)
.length(Self::LENGTH) .length(Self::LENGTH)
.property(
"entries",
entries_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
iterator_symbol,
entries_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.method(Self::set, "set", 2) .method(Self::set, "set", 2)
.method(Self::delete, "delete", 1) .method(Self::delete, "delete", 1)
.method(Self::get, "get", 1) .method(Self::get, "get", 1)
@ -98,6 +120,20 @@ impl Map {
Ok(this.clone()) Ok(this.clone())
} }
/// `Map.prototype.entries()`
///
/// Returns a new Iterator object that contains the [key, value] pairs for each element in the Map object in insertion order.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://www.ecma-international.org/ecma-262/11.0/index.html#sec-map.prototype.entries
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries
pub(crate) fn entries(this: &Value, _: &[Value], ctx: &mut Context) -> Result<Value> {
MapIterator::create_map_iterator(ctx, this.clone(), MapIterationKind::KeyAndValue)
}
/// Helper function to set the size property. /// Helper function to set the size property.
fn set_size(this: &Value, size: usize) { fn set_size(this: &Value, size: usize) {
let size = DataDescriptor::new( let size = DataDescriptor::new(

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

@ -94,6 +94,13 @@ where
self.0.get(key) self.0.get(key)
} }
/// Get a key-value pair by index
/// Valid indices are 0 <= index < self.len()
/// Computes in O(1) time.
pub fn get_index(&self, index: usize) -> Option<(&K, &V)> {
self.0.get_index(index)
}
/// Return an iterator over the key-value pairs of the map, in their order /// Return an iterator over the key-value pairs of the map, in their order
pub fn iter(&self) -> Iter<'_, K, V> { pub fn iter(&self) -> Iter<'_, K, V> {
self.0.iter() self.0.iter()

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

@ -43,9 +43,78 @@ fn clone() {
assert_eq!(result, "2"); assert_eq!(result, "2");
} }
// TODO depends on the https://github.com/boa-dev/boa/issues/810
#[test] #[test]
#[ignore] fn symbol_iterator() {
let mut engine = Context::new();
let init = r#"
const map1 = new Map();
map1.set('0', 'foo');
map1.set(1, 'bar');
const iterator = map1[Symbol.iterator]();
let item1 = iterator.next();
let item2 = iterator.next();
let item3 = iterator.next();
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "item1.value.length");
assert_eq!(result, "2");
let result = forward(&mut engine, "item1.value[0]");
assert_eq!(result, "\"0\"");
let result = forward(&mut engine, "item1.value[1]");
assert_eq!(result, "\"foo\"");
let result = forward(&mut engine, "item1.done");
assert_eq!(result, "false");
let result = forward(&mut engine, "item2.value.length");
assert_eq!(result, "2");
let result = forward(&mut engine, "item2.value[0]");
assert_eq!(result, "1");
let result = forward(&mut engine, "item2.value[1]");
assert_eq!(result, "\"bar\"");
let result = forward(&mut engine, "item2.done");
assert_eq!(result, "false");
let result = forward(&mut engine, "item3.value");
assert_eq!(result, "undefined");
let result = forward(&mut engine, "item3.done");
assert_eq!(result, "true");
}
// Should behave the same as symbol_iterator
#[test]
fn entries() {
let mut engine = Context::new();
let init = r#"
const map1 = new Map();
map1.set('0', 'foo');
map1.set(1, 'bar');
const entriesIterator = map1.entries();
let item1 = entriesIterator.next();
let item2 = entriesIterator.next();
let item3 = entriesIterator.next();
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "item1.value.length");
assert_eq!(result, "2");
let result = forward(&mut engine, "item1.value[0]");
assert_eq!(result, "\"0\"");
let result = forward(&mut engine, "item1.value[1]");
assert_eq!(result, "\"foo\"");
let result = forward(&mut engine, "item1.done");
assert_eq!(result, "false");
let result = forward(&mut engine, "item2.value.length");
assert_eq!(result, "2");
let result = forward(&mut engine, "item2.value[0]");
assert_eq!(result, "1");
let result = forward(&mut engine, "item2.value[1]");
assert_eq!(result, "\"bar\"");
let result = forward(&mut engine, "item2.done");
assert_eq!(result, "false");
let result = forward(&mut engine, "item3.value");
assert_eq!(result, "undefined");
let result = forward(&mut engine, "item3.done");
assert_eq!(result, "true");
}
#[test]
fn merge() { fn merge() {
let mut engine = Context::new(); let mut engine = Context::new();
let init = r#" let init = r#"

1
boa/src/builtins/mod.rs

@ -32,6 +32,7 @@ pub(crate) use self::{
global_this::GlobalThis, global_this::GlobalThis,
infinity::Infinity, infinity::Infinity,
json::Json, json::Json,
map::map_iterator::MapIterator,
map::Map, map::Map,
math::Math, math::Math,
nan::NaN, nan::NaN,

11
boa/src/object/mod.rs

@ -4,6 +4,7 @@ use crate::{
builtins::{ builtins::{
array::array_iterator::ArrayIterator, array::array_iterator::ArrayIterator,
function::{BuiltInFunction, Function, FunctionFlags, NativeFunction}, function::{BuiltInFunction, Function, FunctionFlags, NativeFunction},
map::map_iterator::MapIterator,
map::ordered_map::OrderedMap, map::ordered_map::OrderedMap,
string::string_iterator::StringIterator, string::string_iterator::StringIterator,
BigInt, Date, RegExp, BigInt, Date, RegExp,
@ -76,6 +77,7 @@ pub enum ObjectData {
Array, Array,
ArrayIterator(ArrayIterator), ArrayIterator(ArrayIterator),
Map(OrderedMap<Value, Value>), Map(OrderedMap<Value, Value>),
MapIterator(MapIterator),
RegExp(Box<RegExp>), RegExp(Box<RegExp>),
BigInt(RcBigInt), BigInt(RcBigInt),
Boolean(bool), Boolean(bool),
@ -102,6 +104,7 @@ impl Display for ObjectData {
Self::Function(_) => "Function", Self::Function(_) => "Function",
Self::RegExp(_) => "RegExp", Self::RegExp(_) => "RegExp",
Self::Map(_) => "Map", Self::Map(_) => "Map",
Self::MapIterator(_) => "MapIterator",
Self::String(_) => "String", Self::String(_) => "String",
Self::StringIterator(_) => "StringIterator", Self::StringIterator(_) => "StringIterator",
Self::Symbol(_) => "Symbol", Self::Symbol(_) => "Symbol",
@ -327,6 +330,14 @@ impl Object {
} }
} }
#[inline]
pub fn as_map_iterator_mut(&mut self) -> Option<&mut MapIterator> {
match &mut self.data {
ObjectData::MapIterator(iter) => Some(iter),
_ => 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 {

Loading…
Cancel
Save