Skip to content

Commit

Permalink
Implementation of for...of loops (#704)
Browse files Browse the repository at this point in the history
* Initial implementation of for...of loop

* Initial implementation of for...of loop

* Extend for...of to support var, let, and const lhs

* Use cached well known symbols

* Nest use statements

* Nest use statements

* Add tests.

* Initial implementation of for...of loop

* Extend for...of to support var, let, and const lhs

* Use cached well known symbols

* Nest use statements

* Nest use statements

* Add tests.

* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.

* Initial implementation of for...of loop

* Extend for...of to support var, let, and const lhs

* Use cached well known symbols

* Nest use statements

* Nest use statements

* Add tests.

* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.

* Initial implementation of for...of loop

* Extend for...of to support var, let, and const lhs

* Use cached well known symbols

* Initial implementation of for...of loop

* Nest use statements

* Add string iterator

* Clean up merge

* Use ctx.global_iterator()

* Merge upstream

* Use u32 as array next index

* Use into

* Use boa::Result

* Tidy up use statement

* Tidy up use statement
  • Loading branch information
joshwd36 authored Oct 2, 2020
1 parent 3cb8c94 commit 87d9e9c
Show file tree
Hide file tree
Showing 24 changed files with 1,291 additions and 73 deletions.
139 changes: 139 additions & 0 deletions boa/src/builtins/array/array_iterator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, Value},
object::ObjectData,
property::{Attribute, Property},
BoaProfiler, Context, Result,
};
use gc::{Finalize, Trace};

#[derive(Debug, Clone, Finalize, Trace)]
pub enum ArrayIterationKind {
Key,
Value,
KeyAndValue,
}

/// The Array Iterator object represents an iteration over an array. It implements the iterator protocol.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects
#[derive(Debug, Clone, Finalize, Trace)]
pub struct ArrayIterator {
array: Value,
next_index: u32,
kind: ArrayIterationKind,
}

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

fn new(array: Value, kind: ArrayIterationKind) -> Self {
ArrayIterator {
array,
kind,
next_index: 0,
}
}

/// CreateArrayIterator( array, kind )
///
/// Creates a new iterator over the given array.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator
pub(crate) fn create_array_iterator(
ctx: &Context,
array: Value,
kind: ArrayIterationKind,
) -> Result<Value> {
let array_iterator = Value::new_object(Some(ctx.global_object()));
array_iterator.set_data(ObjectData::ArrayIterator(Self::new(array, kind)));
array_iterator
.as_object_mut()
.expect("array iterator object")
.set_prototype_instance(ctx.iterator_prototypes().array_iterator().into());
Ok(array_iterator)
}

/// %ArrayIteratorPrototype%.next( )
///
/// Gets the next result in the array.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: 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(array_iterator) = object.as_array_iterator_mut() {
let index = array_iterator.next_index;
if array_iterator.array.is_undefined() {
return Ok(create_iter_result_object(ctx, Value::undefined(), true));
}
let len = array_iterator
.array
.get_field("length")
.as_number()
.ok_or_else(|| ctx.construct_type_error("Not an array"))?
as u32;
if array_iterator.next_index >= len {
array_iterator.array = Value::undefined();
return Ok(create_iter_result_object(ctx, Value::undefined(), true));
}
array_iterator.next_index = index + 1;
match array_iterator.kind {
ArrayIterationKind::Key => {
Ok(create_iter_result_object(ctx, index.into(), false))
}
ArrayIterationKind::Value => {
let element_value = array_iterator.array.get_field(index);
Ok(create_iter_result_object(ctx, element_value, false))
}
ArrayIterationKind::KeyAndValue => {
let element_value = array_iterator.array.get_field(index);
let result = Array::make_array(
&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 ArrayIterator")
}
} else {
ctx.throw_type_error("`this` is not an ArrayIterator")
}
}

/// Create the %ArrayIteratorPrototype% object
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: 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 array_iterator = Value::new_object(Some(global));
make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx);
array_iterator
.as_object_mut()
.expect("array 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 =
Property::data_descriptor(Value::string("Array Iterator"), Attribute::CONFIGURABLE);
array_iterator.set_property(to_string_tag, to_string_tag_property);
array_iterator
}
}
65 changes: 65 additions & 0 deletions boa/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
//! [spec]: https://tc39.es/ecma262/#sec-array-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
pub mod array_iterator;
#[cfg(test)]
mod tests;

use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{
builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator},
object::{ObjectData, PROTOTYPE},
property::{Attribute, Property},
value::{same_value_zero, Value},
Expand Down Expand Up @@ -1091,6 +1093,60 @@ impl Array {
Ok(accumulator)
}

/// `Array.prototype.values( )`
///
/// The values method returns an iterable that iterates over the values in the array.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
pub(crate) fn values(
this: &Value,
_args: &[Value],
interpreter: &mut Context,
) -> Result<Value> {
ArrayIterator::create_array_iterator(interpreter, this.clone(), ArrayIterationKind::Value)
}

/// `Array.prototype.keys( )`
///
/// The keys method returns an iterable that iterates over the indexes in the array.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
pub(crate) fn keys(this: &Value, _args: &[Value], interpreter: &mut Context) -> Result<Value> {
ArrayIterator::create_array_iterator(interpreter, this.clone(), ArrayIterationKind::Key)
}

/// `Array.prototype.entries( )`
///
/// The entries method returns an iterable that iterates over the key-value pairs in the array.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
pub(crate) fn entries(
this: &Value,
_args: &[Value],
interpreter: &mut Context,
) -> Result<Value> {
ArrayIterator::create_array_iterator(
interpreter,
this.clone(),
ArrayIterationKind::KeyAndValue,
)
}

/// Initialise the `Array` object on the global object.
#[inline]
pub(crate) fn init(interpreter: &mut Context) -> (&'static str, Value) {
Expand Down Expand Up @@ -1137,6 +1193,15 @@ impl Array {
2,
interpreter,
);
make_builtin_fn(Self::values, "values", &prototype, 0, interpreter);
make_builtin_fn(Self::keys, "keys", &prototype, 0, interpreter);
make_builtin_fn(Self::entries, "entries", &prototype, 0, interpreter);

let symbol_iterator = interpreter.well_known_symbols().iterator_symbol();
prototype.set_property(
symbol_iterator,
Property::default().value(prototype.get_field("values")),
);

let array = make_constructor_fn(
Self::NAME,
Expand Down
132 changes: 132 additions & 0 deletions boa/src/builtins/array/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1078,3 +1078,135 @@ fn call_array_constructor_with_one_argument() {
// let result = forward(&mut engine, "one.length");
// assert_eq!(result, "1");
}

#[test]
fn array_values_simple() {
let mut engine = Context::new();
let init = r#"
var iterator = [1, 2, 3].values();
var next = iterator.next();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "next.value"), "1");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "2");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "3");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "true");
}

#[test]
fn array_keys_simple() {
let mut engine = Context::new();
let init = r#"
var iterator = [1, 2, 3].keys();
var next = iterator.next();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "next.value"), "0");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "1");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "2");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "true");
}

#[test]
fn array_entries_simple() {
let mut engine = Context::new();
let init = r#"
var iterator = [1, 2, 3].entries();
var next = iterator.next();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "next.value"), "[ 0, 1 ]");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "[ 1, 2 ]");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "[ 2, 3 ]");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "true");
}

#[test]
fn array_values_empty() {
let mut engine = Context::new();
let init = r#"
var iterator = [].values();
var next = iterator.next();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "true");
}

#[test]
fn array_values_sparse() {
let mut engine = Context::new();
let init = r#"
var array = Array();
array[3] = 5;
var iterator = array.values();
var next = iterator.next();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "5");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "true");
}

#[test]
fn array_symbol_iterator() {
let mut engine = Context::new();
let init = r#"
var iterator = [1, 2, 3][Symbol.iterator]();
var next = iterator.next();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "next.value"), "1");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "2");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "3");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iterator.next()");
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "true");
}

#[test]
fn array_values_symbol_iterator() {
let mut engine = Context::new();
let init = r#"
var iterator = [1, 2, 3].values();
iterator === iterator[Symbol.iterator]();
"#;
assert_eq!(forward(&mut engine, init), "true");
}
Loading

0 comments on commit 87d9e9c

Please sign in to comment.