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

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

Merged
merged 11 commits into from
Oct 15, 2020
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.clone())
croraf marked this conversation as resolved.
Show resolved Hide resolved
.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
136 changes: 136 additions & 0 deletions boa/src/builtins/map/map_iterator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Map, 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 {
map: Value,
next_index: u32,
kind: MapIterationKind,
}

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

fn new(map: Value, kind: MapIterationKind) -> Self {
MapIterator {
map,
kind,
next_index: 0,
}
}

/// 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( )
///
/// Gets the next result in the map.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: TODO https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.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 index = map_iterator.next_index;
if map_iterator.map.is_undefined() {
return Ok(create_iter_result_object(ctx, Value::undefined(), true));
}
let len = map_iterator
.map
.get_field("length")
.as_number()
.ok_or_else(|| ctx.construct_type_error("Not a map"))? as u32;
if map_iterator.next_index >= len {
map_iterator.map = Value::undefined();
return Ok(create_iter_result_object(ctx, Value::undefined(), true));
}
map_iterator.next_index = index + 1;
match map_iterator.kind {
MapIterationKind::Key => Ok(create_iter_result_object(ctx, index.into(), false)),
MapIterationKind::Value => {
let element_value = map_iterator.map.get_field(index);
Ok(create_iter_result_object(ctx, element_value, false))
}
MapIterationKind::KeyAndValue => {
let element_value = map_iterator.map.get_field(index);
let result = Map::constructor(
&Value::new_object(Some(ctx.global_object())),
&[index.into(), element_value],
ctx,
)?;
Ok(create_iter_result_object(ctx, result, false))
}
}
} 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]: TODO https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-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
}
}
18 changes: 18 additions & 0 deletions boa/src/builtins/map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use crate::{
};
use ordered_map::OrderedMap;

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

pub mod ordered_map;
#[cfg(test)]
mod tests;
Expand All @@ -29,6 +32,7 @@ impl BuiltIn for Map {
.name(Self::NAME)
.length(Self::LENGTH)
.method(Self::set, "set", 2)
.method(Self::entries, "entries", 0)
HalidOdat marked this conversation as resolved.
Show resolved Hide resolved
.method(Self::delete, "delete", 1)
.method(Self::get, "get", 1)
.method(Self::clear, "clear", 0)
Expand Down Expand Up @@ -98,6 +102,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
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
11 changes: 11 additions & 0 deletions boa/src/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
builtins::{
array::array_iterator::ArrayIterator,
function::{BuiltInFunction, Function, FunctionFlags, NativeFunction},
map::map_iterator::MapIterator,
map::ordered_map::OrderedMap,
string::string_iterator::StringIterator,
BigInt, Date, RegExp,
Expand Down Expand Up @@ -76,6 +77,7 @@ pub enum ObjectData {
Array,
ArrayIterator(ArrayIterator),
Map(OrderedMap<Value, Value>),
MapIterator(MapIterator),
RegExp(Box<RegExp>),
BigInt(RcBigInt),
Boolean(bool),
Expand All @@ -102,6 +104,7 @@ impl Display for ObjectData {
Self::Function(_) => "Function",
Self::RegExp(_) => "RegExp",
Self::Map(_) => "Map",
Self::MapIterator(_) => "MapIterator",
Self::String(_) => "String",
Self::StringIterator(_) => "StringIterator",
Self::Symbol(_) => "Symbol",
Expand Down Expand Up @@ -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.
#[inline]
pub fn is_string(&self) -> bool {
Expand Down