Skip to content

Commit

Permalink
[builtin Map] Map.prototype.entries method and map iterator (#847)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
croraf authored Oct 15, 2020
1 parent ab5e888 commit ce535dd
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 4 deletions.
2 changes: 2 additions & 0 deletions boa/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ impl BuiltIn for Array {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");

let symbol_iterator = context.well_known_symbols().iterator_symbol();

let values_function = FunctionBuilder::new(context, Self::values)
.name("values")
.length(0)
.callable(true)
.constructable(false)
.build();

let array = ConstructorBuilder::with_standard_object(
context,
Self::constructor,
Expand Down
11 changes: 10 additions & 1 deletion boa/src/builtins/iterable/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
builtins::string::string_iterator::StringIterator,
builtins::ArrayIterator,
builtins::MapIterator,
object::{GcObject, ObjectInitializer},
property::{Attribute, DataDescriptor},
BoaProfiler, Context, Result, Value,
Expand All @@ -11,6 +12,7 @@ pub struct IteratorPrototypes {
iterator_prototype: GcObject,
array_iterator: GcObject,
string_iterator: GcObject,
map_iterator: GcObject,
}

impl IteratorPrototypes {
Expand All @@ -23,9 +25,12 @@ impl IteratorPrototypes {
array_iterator: ArrayIterator::create_prototype(ctx, iterator_prototype.clone())
.as_gc_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()
.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"),
}
}

Expand All @@ -40,6 +45,10 @@ impl IteratorPrototypes {
pub fn string_iterator(&self) -> GcObject {
self.string_iterator.clone()
}

pub fn map_iterator(&self) -> GcObject {
self.map_iterator.clone()
}
}

/// CreateIterResultObject( value, done )
Expand Down
155 changes: 155 additions & 0 deletions boa/src/builtins/map/map_iterator.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 37 additions & 1 deletion boa/src/builtins/map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

use crate::{
builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData, PROTOTYPE},
object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE},
property::{Attribute, DataDescriptor},
BoaProfiler, Context, Result, Value,
};
use ordered_map::OrderedMap;

pub mod map_iterator;
use map_iterator::{MapIterationKind, MapIterator};

pub mod ordered_map;
#[cfg(test)]
mod tests;
Expand All @@ -25,9 +28,28 @@ impl BuiltIn for Map {
fn init(context: &mut Context) -> (&'static str, Value, Attribute) {
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)
.name(Self::NAME)
.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::delete, "delete", 1)
.method(Self::get, "get", 1)
Expand Down Expand Up @@ -98,6 +120,20 @@ impl Map {
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.
fn set_size(this: &Value, size: usize) {
let size = DataDescriptor::new(
Expand Down
7 changes: 7 additions & 0 deletions boa/src/builtins/map/ordered_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ where
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
pub fn iter(&self) -> Iter<'_, K, V> {
self.0.iter()
Expand Down
73 changes: 71 additions & 2 deletions boa/src/builtins/map/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,78 @@ fn clone() {
assert_eq!(result, "2");
}

// TODO depends on the https://github.com/boa-dev/boa/issues/810
#[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() {
let mut engine = Context::new();
let init = r#"
Expand Down
1 change: 1 addition & 0 deletions boa/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub(crate) use self::{
global_this::GlobalThis,
infinity::Infinity,
json::Json,
map::map_iterator::MapIterator,
map::Map,
math::Math,
nan::NaN,
Expand Down
Loading

0 comments on commit ce535dd

Please sign in to comment.