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

Implementation of for...of loops #704

Merged
merged 39 commits into from
Oct 2, 2020
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
27f92ed
Initial implementation of for...of loop
joshwd36 Sep 17, 2020
39adfef
Initial implementation of for...of loop
joshwd36 Sep 17, 2020
c3e388f
Extend for...of to support var, let, and const lhs
joshwd36 Sep 23, 2020
fc2d560
Use cached well known symbols
joshwd36 Sep 23, 2020
fcb4869
Merge remote-tracking branch 'origin/Iterables' into Iterables
joshwd36 Sep 23, 2020
21a1c02
Nest use statements
joshwd36 Sep 23, 2020
3536085
Nest use statements
joshwd36 Sep 23, 2020
b428960
Add tests.
joshwd36 Sep 23, 2020
6659087
Initial implementation of for...of loop
joshwd36 Sep 17, 2020
3d1d398
Extend for...of to support var, let, and const lhs
joshwd36 Sep 23, 2020
6209208
Use cached well known symbols
joshwd36 Sep 23, 2020
f32f090
Nest use statements
joshwd36 Sep 23, 2020
08d4a93
Nest use statements
joshwd36 Sep 23, 2020
a6afab7
Add tests.
joshwd36 Sep 23, 2020
e1ba773
Add tests, cache iterator prototypes, pull common iterator functions …
joshwd36 Sep 25, 2020
9ce630e
Merge remote-tracking branch 'origin/Iterables' into Iterables
joshwd36 Sep 25, 2020
66f4c79
Initial implementation of for...of loop
joshwd36 Sep 17, 2020
5228e56
Extend for...of to support var, let, and const lhs
joshwd36 Sep 23, 2020
b49e03f
Use cached well known symbols
joshwd36 Sep 23, 2020
1db94ab
Nest use statements
joshwd36 Sep 23, 2020
3737ca7
Nest use statements
joshwd36 Sep 23, 2020
f1d3639
Add tests.
joshwd36 Sep 23, 2020
a6e0dd2
Add tests, cache iterator prototypes, pull common iterator functions …
joshwd36 Sep 25, 2020
6287bb2
Initial implementation of for...of loop
joshwd36 Sep 17, 2020
2ca4003
Extend for...of to support var, let, and const lhs
joshwd36 Sep 23, 2020
42a0741
Use cached well known symbols
joshwd36 Sep 23, 2020
57a67b2
Initial implementation of for...of loop
joshwd36 Sep 17, 2020
a451f4b
Nest use statements
joshwd36 Sep 23, 2020
8f80692
Add string iterator
joshwd36 Sep 29, 2020
384c824
Clean up merge
joshwd36 Sep 29, 2020
08a6f51
Merge remote-tracking branch 'origin/Iterables' into Iterables
joshwd36 Sep 29, 2020
29e1dee
Use ctx.global_iterator()
joshwd36 Oct 2, 2020
7e565e8
Merge remote-tracking branch 'upstream/master' into Iterables
joshwd36 Oct 2, 2020
8e91a22
Merge upstream
joshwd36 Oct 2, 2020
0abe3c7
Use u32 as array next index
joshwd36 Oct 2, 2020
9b43518
Use into
joshwd36 Oct 2, 2020
3a5d75f
Use boa::Result
joshwd36 Oct 2, 2020
69c76c4
Tidy up use statement
joshwd36 Oct 2, 2020
875a383
Tidy up use statement
joshwd36 Oct 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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