Browse Source

Implementation of for...of loops (#704)

* 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
pull/722/head
joshwd36 4 years ago committed by GitHub
parent
commit
87d9e9cea8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 139
      boa/src/builtins/array/array_iterator.rs
  2. 65
      boa/src/builtins/array/mod.rs
  3. 132
      boa/src/builtins/array/tests.rs
  4. 154
      boa/src/builtins/iterable/mod.rs
  5. 5
      boa/src/builtins/mod.rs
  6. 67
      boa/src/builtins/string/mod.rs
  7. 89
      boa/src/builtins/string/string_iterator.rs
  8. 99
      boa/src/builtins/string/tests.rs
  9. 11
      boa/src/context.rs
  10. 2
      boa/src/object/gcobject.rs
  11. 39
      boa/src/object/mod.rs
  12. 12
      boa/src/syntax/ast/keyword.rs
  13. 20
      boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs
  14. 208
      boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs
  15. 4
      boa/src/syntax/ast/node/iteration/mod.rs
  16. 129
      boa/src/syntax/ast/node/iteration/tests.rs
  17. 7
      boa/src/syntax/ast/node/mod.rs
  18. 40
      boa/src/syntax/parser/expression/primary/object_initializer/tests.rs
  19. 67
      boa/src/syntax/parser/statement/declaration/lexical.rs
  20. 12
      boa/src/syntax/parser/statement/declaration/mod.rs
  21. 12
      boa/src/syntax/parser/statement/declaration/tests.rs
  22. 18
      boa/src/syntax/parser/statement/iteration/for_statement.rs
  23. 8
      boa/src/syntax/parser/statement/mod.rs
  24. 25
      boa/src/value/mod.rs

139
boa/src/builtins/array/array_iterator.rs

@ -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
boa/src/builtins/array/mod.rs

@ -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},
@ -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) {
@ -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,

132
boa/src/builtins/array/tests.rs

@ -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");
}

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

@ -0,0 +1,154 @@
use crate::{
builtins::string::string_iterator::StringIterator,
builtins::{
function::{BuiltInFunction, Function, FunctionFlags},
ArrayIterator,
},
object::GcObject,
object::{Object, PROTOTYPE},
property::Property,
BoaProfiler, Context, Result, Value,
};
#[derive(Debug, Default)]
pub struct IteratorPrototypes {
iterator_prototype: GcObject,
array_iterator: GcObject,
string_iterator: GcObject,
}
impl IteratorPrototypes {
pub fn init(ctx: &mut Context) -> Self {
let iterator_prototype = create_iterator_prototype(ctx);
Self {
iterator_prototype: iterator_prototype
.as_gc_object()
.expect("Iterator prototype is not an object"),
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)
.as_gc_object()
.expect("String Iterator Prototype is not an object"),
}
}
pub fn array_iterator(&self) -> GcObject {
self.array_iterator.clone()
}
pub fn iterator_prototype(&self) -> GcObject {
self.iterator_prototype.clone()
}
pub fn string_iterator(&self) -> GcObject {
self.string_iterator.clone()
}
}
/// CreateIterResultObject( value, done )
///
/// Generates an object supporting the IteratorResult interface.
pub fn create_iter_result_object(ctx: &mut Context, value: Value, done: bool) -> Value {
let object = Value::new_object(Some(ctx.global_object()));
let value_property = Property::default().value(value);
let done_property = Property::default().value(Value::boolean(done));
object.set_property("value", value_property);
object.set_property("done", done_property);
object
}
/// Get an iterator record
pub fn get_iterator(ctx: &mut Context, iterable: Value) -> Result<IteratorRecord> {
let iterator_function = iterable
.get_property(ctx.well_known_symbols().iterator_symbol())
.and_then(|mut p| p.value.take())
.ok_or_else(|| ctx.construct_type_error("Not an iterable"))?;
let iterator_object = ctx.call(&iterator_function, &iterable, &[])?;
let next_function = iterator_object
.get_property("next")
.and_then(|mut p| p.value.take())
.ok_or_else(|| ctx.construct_type_error("Could not find property `next`"))?;
Ok(IteratorRecord::new(iterator_object, next_function))
}
/// Create the %IteratorPrototype% object
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%iteratorprototype%-object
fn create_iterator_prototype(ctx: &mut Context) -> Value {
let global = ctx.global_object();
let _timer = BoaProfiler::global().start_event("Iterator Prototype", "init");
let iterator_prototype = Value::new_object(Some(global));
let mut function = Object::function(
Function::BuiltIn(
BuiltInFunction(|v, _, _| Ok(v.clone())),
FunctionFlags::CALLABLE,
),
global.get_field("Function").get_field(PROTOTYPE),
);
function.insert_field("length", Value::from(0));
function.insert_field("name", Value::string("[Symbol.iterator]"));
let symbol_iterator = ctx.well_known_symbols().iterator_symbol();
iterator_prototype.set_field(symbol_iterator, Value::from(function));
iterator_prototype
}
#[derive(Debug)]
pub struct IteratorRecord {
iterator_object: Value,
next_function: Value,
}
impl IteratorRecord {
fn new(iterator_object: Value, next_function: Value) -> Self {
Self {
iterator_object,
next_function,
}
}
/// Get the next value in the iterator
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iteratornext
pub(crate) fn next(&self, ctx: &mut Context) -> Result<IteratorResult> {
let next = ctx.call(&self.next_function, &self.iterator_object, &[])?;
let done = next
.get_property("done")
.and_then(|mut p| p.value.take())
.and_then(|v| v.as_boolean())
.ok_or_else(|| ctx.construct_type_error("Could not find property `done`"))?;
let next_result = next
.get_property("value")
.and_then(|mut p| p.value.take())
.unwrap_or_default();
Ok(IteratorResult::new(next_result, done))
}
}
#[derive(Debug)]
pub struct IteratorResult {
value: Value,
done: bool,
}
impl IteratorResult {
fn new(value: Value, done: bool) -> Self {
Self { value, done }
}
pub fn is_done(&self) -> bool {
self.done
}
pub fn value(self) -> Value {
self.value
}
}

5
boa/src/builtins/mod.rs

@ -9,6 +9,7 @@ pub mod error;
pub mod function;
pub mod global_this;
pub mod infinity;
pub mod iterable;
pub mod json;
pub mod map;
pub mod math;
@ -21,7 +22,7 @@ pub mod symbol;
pub mod undefined;
pub(crate) use self::{
array::Array,
array::{array_iterator::ArrayIterator, Array},
bigint::BigInt,
boolean::Boolean,
console::Console,
@ -49,6 +50,7 @@ pub fn init(interpreter: &mut Context) {
// The `Function` global must be initialized before other types.
function::init,
Object::init,
Symbol::init,
Array::init,
BigInt::init,
Boolean::init,
@ -59,7 +61,6 @@ pub fn init(interpreter: &mut Context) {
Number::init,
RegExp::init,
String::init,
Symbol::init,
Console::init,
// Global error types.
Error::init,

67
boa/src/builtins/string/mod.rs

@ -9,10 +9,14 @@
//! [spec]: https://tc39.es/ecma262/#sec-string-object
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
pub mod string_iterator;
#[cfg(test)]
mod tests;
use super::function::{make_builtin_fn, make_constructor_fn};
use crate::builtins::function::{BuiltInFunction, Function, FunctionFlags};
use crate::builtins::string::string_iterator::StringIterator;
use crate::object::PROTOTYPE;
use crate::{
builtins::RegExp,
object::{Object, ObjectData},
@ -21,12 +25,42 @@ use crate::{
BoaProfiler, Context, Result,
};
use regex::Regex;
use std::string::String as StdString;
use std::{
char::decode_utf16,
cmp::{max, min},
f64::NAN,
string::String as StdString,
};
pub(crate) fn code_point_at(string: RcString, position: i32) -> Option<(u32, u8, bool)> {
let size = string.encode_utf16().count() as i32;
if position < 0 || position >= size {
return None;
}
let mut encoded = string.encode_utf16();
let first = encoded.nth(position as usize)?;
if !is_leading_surrogate(first) && !is_trailing_surrogate(first) {
return Some((first as u32, 1, false));
}
if is_trailing_surrogate(first) || position + 1 == size {
return Some((first as u32, 1, true));
}
let second = encoded.next()?;
if !is_trailing_surrogate(second) {
return Some((first as u32, 1, true));
}
let cp = (first as u32 - 0xD800) * 0x400 + (second as u32 - 0xDC00) + 0x10000;
Some((cp, 2, false))
}
fn is_leading_surrogate(value: u16) -> bool {
value >= 0xD800 && value <= 0xDBFF
}
fn is_trailing_surrogate(value: u16) -> bool {
value >= 0xDC00 && value <= 0xDFFF
}
/// JavaScript `String` implementation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct String;
@ -72,7 +106,7 @@ impl String {
None => RcString::default(),
};
let length = string.chars().count();
let length = string.encode_utf16().count();
this.set_field("length", Value::from(length as i32));
@ -922,7 +956,7 @@ impl String {
.expect("failed to get argument for String method")
.to_integer(ctx)? as i32
};
let length = primitive_val.chars().count() as i32;
let length = primitive_val.encode_utf16().count() as i32;
// If less than 2 args specified, end is the length of the this object converted to a String
let end = if args.len() < 2 {
length
@ -940,12 +974,14 @@ impl String {
let to = max(final_start, final_end) as usize;
// Extract the part of the string contained between the start index and the end index
// where start is guaranteed to be smaller or equals to end
let extracted_string: StdString = primitive_val
.chars()
.skip(from)
.take(to.wrapping_sub(from))
.collect();
Ok(Value::from(extracted_string))
let extracted_string: std::result::Result<StdString, _> = decode_utf16(
primitive_val
.encode_utf16()
.skip(from)
.take(to.wrapping_sub(from)),
)
.collect();
Ok(Value::from(extracted_string.expect("Invalid string")))
}
/// `String.prototype.substr( start[, length] )`
@ -1062,6 +1098,10 @@ impl String {
RegExp::match_all(&re, this.to_string(ctx)?.to_string())
}
pub(crate) fn iterator(this: &Value, _args: &[Value], ctx: &mut Context) -> Result<Value> {
StringIterator::create_string_iterator(ctx, this.clone())
}
/// Initialise the `String` object on the global object.
#[inline]
pub(crate) fn init(interpreter: &mut Context) -> (&'static str, Value) {
@ -1118,6 +1158,15 @@ impl String {
make_builtin_fn(Self::match_all, "matchAll", &prototype, 1, interpreter);
make_builtin_fn(Self::replace, "replace", &prototype, 2, interpreter);
let symbol_iterator = interpreter.well_known_symbols().iterator_symbol();
let mut function = Object::function(
Function::BuiltIn(BuiltInFunction(Self::iterator), FunctionFlags::CALLABLE),
global.get_field("Function").get_field(PROTOTYPE),
);
function.insert_field("length", Value::from(0));
function.insert_field("name", Value::string("[Symbol.iterator]"));
prototype.set_field(symbol_iterator, Value::from(function));
let string_object = make_constructor_fn(
Self::NAME,
Self::LENGTH,

89
boa/src/builtins/string/string_iterator.rs

@ -0,0 +1,89 @@
use crate::builtins::string::code_point_at;
use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object},
object::ObjectData,
property::{Attribute, Property},
BoaProfiler, Context, Result, Value,
};
use gc::{Finalize, Trace};
#[derive(Debug, Clone, Finalize, Trace)]
pub struct StringIterator {
string: Value,
next_index: i32,
}
impl StringIterator {
fn new(string: Value) -> Self {
Self {
string,
next_index: 0,
}
}
pub fn create_string_iterator(ctx: &mut Context, string: Value) -> Result<Value> {
let string_iterator = Value::new_object(Some(ctx.global_object()));
string_iterator.set_data(ObjectData::StringIterator(Self::new(string)));
string_iterator
.as_object_mut()
.expect("array iterator object")
.set_prototype_instance(ctx.iterator_prototypes().string_iterator().into());
Ok(string_iterator)
}
pub 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(string_iterator) = object.as_string_iterator_mut() {
if string_iterator.string.is_undefined() {
return Ok(create_iter_result_object(ctx, Value::undefined(), true));
}
let native_string = string_iterator.string.to_string(ctx)?;
let len = native_string.encode_utf16().count() as i32;
let position = string_iterator.next_index;
if position >= len {
string_iterator.string = Value::undefined();
return Ok(create_iter_result_object(ctx, Value::undefined(), true));
}
let (_, code_unit_count, _) =
code_point_at(native_string, position).expect("Invalid code point position");
string_iterator.next_index += code_unit_count as i32;
let result_string = crate::builtins::string::String::substring(
&string_iterator.string,
&[position.into(), string_iterator.next_index.into()],
ctx,
)?;
Ok(create_iter_result_object(ctx, result_string, 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("String Iterator", "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("String Iterator"), Attribute::CONFIGURABLE);
array_iterator.set_property(to_string_tag, to_string_tag_property);
array_iterator
}
}

99
boa/src/builtins/string/tests.rs

@ -679,3 +679,102 @@ fn last_index_non_integer_position_argument() {
);
assert_eq!(forward(&mut engine, "'abcx'.lastIndexOf('x', null)"), "3");
}
#[test]
fn empty_iter() {
let mut engine = Context::new();
let init = r#"
let iter = new String()[Symbol.iterator]();
let next = iter.next();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "true");
}
#[test]
fn ascii_iter() {
let mut engine = Context::new();
let init = r#"
let iter = new String("Hello World")[Symbol.iterator]();
let next = iter.next();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "next.value"), "\"H\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"e\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"l\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"l\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"o\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\" \"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"W\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"o\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"r\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"l\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"d\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "true");
}
#[test]
fn unicode_iter() {
let mut engine = Context::new();
let init = r#"
let iter = new String("C🙂🙂l W🙂rld")[Symbol.iterator]();
let next = iter.next();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "next.value"), "\"C\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"🙂\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"🙂\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"l\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\" \"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"W\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"🙂\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"r\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"l\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "\"d\"");
assert_eq!(forward(&mut engine, "next.done"), "false");
forward(&mut engine, "next = iter.next()");
assert_eq!(forward(&mut engine, "next.value"), "undefined");
assert_eq!(forward(&mut engine, "next.done"), "true");
}

11
boa/src/context.rs

@ -4,6 +4,7 @@ use crate::{
builtins::{
self,
function::{Function, FunctionFlags, NativeFunction},
iterable::IteratorPrototypes,
symbol::{Symbol, WellKnownSymbols},
Console,
},
@ -50,6 +51,8 @@ pub struct Context {
/// Cached well known symbols
well_known_symbols: WellKnownSymbols,
iterator_prototypes: IteratorPrototypes,
}
impl Default for Context {
@ -63,13 +66,14 @@ impl Default for Context {
symbol_count,
console: Console::default(),
well_known_symbols,
iterator_prototypes: IteratorPrototypes::default(),
};
// Add new builtIns to Context Realm
// At a later date this can be removed from here and called explicitly,
// but for now we almost always want these default builtins
context.create_intrinsics();
context.iterator_prototypes = IteratorPrototypes::init(&mut context);
context
}
}
@ -517,4 +521,9 @@ impl Context {
pub fn well_known_symbols(&self) -> &WellKnownSymbols {
&self.well_known_symbols
}
#[inline]
pub fn iterator_prototypes(&self) -> &IteratorPrototypes {
&self.iterator_prototypes
}
}

2
boa/src/object/gcobject.rs

@ -29,7 +29,7 @@ pub type Ref<'object> = GcCellRef<'object, Object>;
pub type RefMut<'object> = GcCellRefMut<'object, Object>;
/// Garbage collected `Object`.
#[derive(Trace, Finalize, Clone)]
#[derive(Trace, Finalize, Clone, Default)]
pub struct GcObject(Gc<GcCell<Object>>);
// This is needed for the call method since we cannot mutate the function itself since we

39
boa/src/object/mod.rs

@ -1,7 +1,10 @@
//! This module implements the Rust representation of a JavaScript object.
use crate::{
builtins::{function::Function, map::ordered_map::OrderedMap, BigInt, Date, RegExp},
builtins::{
array::array_iterator::ArrayIterator, function::Function, map::ordered_map::OrderedMap,
string::string_iterator::StringIterator, BigInt, Date, RegExp,
},
property::{Property, PropertyKey},
value::{RcBigInt, RcString, RcSymbol, Value},
BoaProfiler,
@ -62,12 +65,14 @@ pub struct Object {
#[derive(Debug, Trace, Finalize)]
pub enum ObjectData {
Array,
ArrayIterator(ArrayIterator),
Map(OrderedMap<Value, Value>),
RegExp(Box<RegExp>),
BigInt(RcBigInt),
Boolean(bool),
Function(Function),
String(RcString),
StringIterator(StringIterator),
Number(f64),
Symbol(RcSymbol),
Error,
@ -84,10 +89,12 @@ impl Display for ObjectData {
"{}",
match self {
Self::Array => "Array",
Self::ArrayIterator(_) => "ArrayIterator",
Self::Function(_) => "Function",
Self::RegExp(_) => "RegExp",
Self::Map(_) => "Map",
Self::String(_) => "String",
Self::StringIterator(_) => "StringIterator",
Self::Symbol(_) => "Symbol",
Self::Error => "Error",
Self::Ordinary => "Ordinary",
@ -252,6 +259,36 @@ impl Object {
}
}
/// Checks if it is an `ArrayIterator` object.
#[inline]
pub fn is_array_iterator(&self) -> bool {
matches!(self.data, ObjectData::ArrayIterator(_))
}
#[inline]
pub fn as_array_iterator(&self) -> Option<&ArrayIterator> {
match self.data {
ObjectData::ArrayIterator(ref iter) => Some(iter),
_ => None,
}
}
#[inline]
pub fn as_array_iterator_mut(&mut self) -> Option<&mut ArrayIterator> {
match &mut self.data {
ObjectData::ArrayIterator(iter) => Some(iter),
_ => None,
}
}
#[inline]
pub fn as_string_iterator_mut(&mut self) -> Option<&mut StringIterator> {
match &mut self.data {
ObjectData::StringIterator(iter) => Some(iter),
_ => None,
}
}
/// Checks if it is a `Map` object.pub
#[inline]
pub fn is_map(&self) -> bool {

12
boa/src/syntax/ast/keyword.rs

@ -291,6 +291,16 @@ pub enum Keyword {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
New,
/// The `of` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
Of,
/// The `return` keyword
///
/// More information:
@ -467,6 +477,7 @@ impl Keyword {
Self::Import => "import",
Self::Let => "let",
Self::New => "new",
Self::Of => "of",
Self::Return => "return",
Self::Super => "super",
Self::Switch => "switch",
@ -538,6 +549,7 @@ impl FromStr for Keyword {
"import" => Ok(Self::Import),
"let" => Ok(Self::Let),
"new" => Ok(Self::New),
"of" => Ok(Self::Of),
"return" => Ok(Self::Return),
"super" => Ok(Self::Super),
"switch" => Ok(Self::Switch),

20
boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs

@ -38,7 +38,11 @@ pub struct ConstDeclList {
impl Executable for ConstDeclList {
fn run(&self, interpreter: &mut Context) -> Result<Value> {
for decl in self.as_ref() {
let val = decl.init().run(interpreter)?;
let val = if let Some(init) = decl.init() {
init.run(interpreter)?
} else {
return interpreter.throw_syntax_error("missing = in const declaration");
};
interpreter
.realm_mut()
@ -99,25 +103,29 @@ impl From<ConstDeclList> for Node {
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct ConstDecl {
name: Identifier,
init: Node,
init: Option<Node>,
}
impl fmt::Display for ConstDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} = {}", self.name, self.init)
fmt::Display::fmt(&self.name, f)?;
if let Some(ref init) = self.init {
write!(f, " = {}", init)?;
}
Ok(())
}
}
impl ConstDecl {
/// Creates a new variable declaration.
pub(in crate::syntax) fn new<N, I>(name: N, init: I) -> Self
pub(in crate::syntax) fn new<N, I>(name: N, init: Option<I>) -> Self
where
N: Into<Identifier>,
I: Into<Node>,
{
Self {
name: name.into(),
init: init.into(),
init: init.map(|n| n.into()),
}
}
@ -127,7 +135,7 @@ impl ConstDecl {
}
/// Gets the initialization node for the variable, if any.
pub fn init(&self) -> &Node {
pub fn init(&self) -> &Option<Node> {
&self.init
}
}

208
boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs

@ -0,0 +1,208 @@
use crate::{
builtins::iterable::get_iterator,
environment::lexical_environment::{new_declarative_environment, VariableScope},
exec::{Executable, InterpreterState},
syntax::ast::node::Node,
BoaProfiler, Context, Result, Value,
};
use gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct ForOfLoop {
variable: Box<Node>,
iterable: Box<Node>,
body: Box<Node>,
}
impl ForOfLoop {
pub fn new<V, I, B>(variable: V, iterable: I, body: B) -> Self
where
V: Into<Node>,
I: Into<Node>,
B: Into<Node>,
{
Self {
variable: Box::new(variable.into()),
iterable: Box::new(iterable.into()),
body: Box::new(body.into()),
}
}
pub fn variable(&self) -> &Node {
&self.variable
}
pub fn iterable(&self) -> &Node {
&self.iterable
}
pub fn body(&self) -> &Node {
&self.body
}
pub fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
write!(f, "for ({} of {}) {{", self.variable, self.iterable)?;
self.body().display(f, indentation + 1)?;
f.write_str("}")
}
}
impl fmt::Display for ForOfLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)
}
}
impl From<ForOfLoop> for Node {
fn from(for_of: ForOfLoop) -> Node {
Self::ForOfLoop(for_of)
}
}
impl Executable for ForOfLoop {
fn run(&self, interpreter: &mut Context) -> Result<Value> {
let _timer = BoaProfiler::global().start_event("ForOf", "exec");
let iterable = self.iterable().run(interpreter)?;
let iterator = get_iterator(interpreter, iterable)?;
let mut result = Value::undefined();
loop {
{
let env = &mut interpreter.realm_mut().environment;
env.push(new_declarative_environment(Some(
env.get_current_environment_ref().clone(),
)));
}
let iterator_result = iterator.next(interpreter)?;
if iterator_result.is_done() {
break;
}
let next_result = iterator_result.value();
match self.variable() {
Node::Identifier(ref name) => {
let environment = &mut interpreter.realm_mut().environment;
if environment.has_binding(name.as_ref()) {
// Binding already exists
environment.set_mutable_binding(name.as_ref(), next_result.clone(), true);
} else {
environment.create_mutable_binding(
name.as_ref().to_owned(),
true,
VariableScope::Function,
);
environment.initialize_binding(name.as_ref(), next_result.clone());
}
}
Node::VarDeclList(ref list) => match list.as_ref() {
[var] => {
let environment = &mut interpreter.realm_mut().environment;
if var.init().is_some() {
return interpreter.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer");
}
if environment.has_binding(var.name()) {
environment.set_mutable_binding(var.name(), next_result, true);
} else {
environment.create_mutable_binding(
var.name().to_owned(),
false,
VariableScope::Function,
);
environment.initialize_binding(var.name(), next_result);
}
}
_ => {
return interpreter.throw_syntax_error(
"only one variable can be declared in the head of a for-of loop",
)
}
},
Node::LetDeclList(ref list) => match list.as_ref() {
[var] => {
let environment = &mut interpreter.realm_mut().environment;
if var.init().is_some() {
return interpreter.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer");
}
environment.create_mutable_binding(
var.name().to_owned(),
false,
VariableScope::Block,
);
environment.initialize_binding(var.name(), next_result);
}
_ => {
return interpreter.throw_syntax_error(
"only one variable can be declared in the head of a for-of loop",
)
}
},
Node::ConstDeclList(ref list) => match list.as_ref() {
[var] => {
let environment = &mut interpreter.realm_mut().environment;
if var.init().is_some() {
return interpreter.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer");
}
environment.create_immutable_binding(
var.name().to_owned(),
false,
VariableScope::Block,
);
environment.initialize_binding(var.name(), next_result);
}
_ => {
return interpreter.throw_syntax_error(
"only one variable can be declared in the head of a for-of loop",
)
}
},
Node::Assign(_) => {
return interpreter.throw_syntax_error(
"a declaration in the head of a for-of loop can't have an initializer",
);
}
_ => {
return interpreter
.throw_syntax_error("unknown left hand side in head of for-of loop")
}
}
result = self.body().run(interpreter)?;
match interpreter.executor().get_current_state() {
InterpreterState::Break(_label) => {
// TODO break to label.
// Loops 'consume' breaks.
interpreter
.executor()
.set_current_state(InterpreterState::Executing);
break;
}
InterpreterState::Continue(_label) => {
// TODO continue to label.
interpreter
.executor()
.set_current_state(InterpreterState::Executing);
// after breaking out of the block, continue execution of the loop
}
InterpreterState::Return => return Ok(result),
InterpreterState::Executing => {
// Continue execution.
}
}
let _ = interpreter.realm_mut().environment.pop();
}
Ok(result)
}
}

4
boa/src/syntax/ast/node/iteration/mod.rs

@ -1,7 +1,8 @@
//! Iteration nodes
pub use self::{
continue_node::Continue, do_while_loop::DoWhileLoop, for_loop::ForLoop, while_loop::WhileLoop,
continue_node::Continue, do_while_loop::DoWhileLoop, for_loop::ForLoop, for_of_loop::ForOfLoop,
while_loop::WhileLoop,
};
#[cfg(test)]
@ -32,4 +33,5 @@ macro_rules! handle_state_with_labels {
pub mod continue_node;
pub mod do_while_loop;
pub mod for_loop;
pub mod for_of_loop;
pub mod while_loop;

129
boa/src/syntax/ast/node/iteration/tests.rs

@ -1,4 +1,4 @@
use crate::exec;
use crate::{exec, forward, Context};
#[test]
fn while_loop_late_break() {
@ -192,6 +192,133 @@ fn do_while_loop_continue() {
assert_eq!(&exec(scenario), "[ 1, 2 ]");
}
#[test]
fn for_of_loop_declaration() {
let mut engine = Context::new();
let scenario = r#"
var result = 0;
for (i of [1, 2, 3]) {
result = i;
}
"#;
engine.eval(scenario).unwrap();
assert_eq!(&forward(&mut engine, "result"), "3");
assert_eq!(&forward(&mut engine, "i"), "3");
}
#[test]
fn for_of_loop_var() {
let mut engine = Context::new();
let scenario = r#"
var result = 0;
for (var i of [1, 2, 3]) {
result = i;
}
"#;
engine.eval(scenario).unwrap();
assert_eq!(&forward(&mut engine, "result"), "3");
assert_eq!(&forward(&mut engine, "i"), "3");
}
#[test]
fn for_of_loop_let() {
let mut engine = Context::new();
let scenario = r#"
var result = 0;
for (let i of [1, 2, 3]) {
result = i;
}
"#;
engine.eval(scenario).unwrap();
assert_eq!(&forward(&mut engine, "result"), "3");
assert_eq!(
&forward(
&mut engine,
r#"
try {
i
} catch(e) {
e.toString()
}
"#
),
"\"ReferenceError: i is not defined\""
);
}
#[test]
fn for_of_loop_const() {
let mut engine = Context::new();
let scenario = r#"
var result = 0;
for (let i of [1, 2, 3]) {
result = i;
}
"#;
engine.eval(scenario).unwrap();
assert_eq!(&forward(&mut engine, "result"), "3");
assert_eq!(
&forward(
&mut engine,
r#"
try {
i
} catch(e) {
e.toString()
}
"#
),
"\"ReferenceError: i is not defined\""
);
}
#[test]
fn for_of_loop_break() {
let mut engine = Context::new();
let scenario = r#"
var result = 0;
for (var i of [1, 2, 3]) {
if (i > 1)
break;
result = i
}
"#;
engine.eval(scenario).unwrap();
assert_eq!(&forward(&mut engine, "result"), "1");
assert_eq!(&forward(&mut engine, "i"), "2");
}
#[test]
fn for_of_loop_continue() {
let mut engine = Context::new();
let scenario = r#"
var result = 0;
for (var i of [1, 2, 3]) {
if (i == 3)
continue;
result = i
}
"#;
engine.eval(scenario).unwrap();
assert_eq!(&forward(&mut engine, "result"), "2");
assert_eq!(&forward(&mut engine, "i"), "3");
}
#[test]
fn for_of_loop_return() {
let mut engine = Context::new();
let scenario = r#"
function foo() {
for (i of [1, 2, 3]) {
if (i > 1)
return i;
}
}
"#;
engine.eval(scenario).unwrap();
assert_eq!(&forward(&mut engine, "foo()"), "2");
}
#[test]
fn for_loop_break_label() {
let scenario = r#"

7
boa/src/syntax/ast/node/mod.rs

@ -30,7 +30,7 @@ pub use self::{
expression::{Call, New},
field::{GetConstField, GetField},
identifier::Identifier,
iteration::{Continue, DoWhileLoop, ForLoop, WhileLoop},
iteration::{Continue, DoWhileLoop, ForLoop, ForOfLoop, WhileLoop},
object::Object,
operator::{Assign, BinOp, UnaryOp},
return_smt::Return,
@ -114,6 +114,9 @@ pub enum Node {
/// A `for` statement. [More information](./iteration/struct.ForLoop.html).
ForLoop(ForLoop),
/// A `for...of` statement. [More information](./iteration/struct.ForOf.html).
ForOfLoop(ForOfLoop),
/// An 'if' statement. [More information](./conditional/struct.If.html).
If(If),
@ -209,6 +212,7 @@ impl Node {
Self::Const(ref c) => write!(f, "{}", c),
Self::ConditionalOp(ref cond_op) => Display::fmt(cond_op, f),
Self::ForLoop(ref for_loop) => for_loop.display(f, indentation),
Self::ForOfLoop(ref for_of) => for_of.display(f, indentation),
Self::This => write!(f, "this"),
Self::Try(ref try_catch) => try_catch.display(f, indentation),
Self::Break(ref break_smt) => Display::fmt(break_smt, f),
@ -263,6 +267,7 @@ impl Executable for Node {
Node::WhileLoop(ref while_loop) => while_loop.run(interpreter),
Node::DoWhileLoop(ref do_while) => do_while.run(interpreter),
Node::ForLoop(ref for_loop) => for_loop.run(interpreter),
Node::ForOfLoop(ref for_of_loop) => for_of_loop.run(interpreter),
Node::If(ref if_smt) => if_smt.run(interpreter),
Node::ConditionalOp(ref op) => op.run(interpreter),
Node::Switch(ref switch) => switch.run(interpreter),

40
boa/src/syntax/parser/expression/primary/object_initializer/tests.rs

@ -23,9 +23,11 @@ fn check_object_literal() {
b: false,
};
",
vec![
ConstDeclList::from(vec![ConstDecl::new("x", Object::from(object_properties))]).into(),
],
vec![ConstDeclList::from(vec![ConstDecl::new(
"x",
Some(Object::from(object_properties)),
)])
.into()],
);
}
@ -47,9 +49,11 @@ fn check_object_short_function() {
b() {},
};
",
vec![
ConstDeclList::from(vec![ConstDecl::new("x", Object::from(object_properties))]).into(),
],
vec![ConstDeclList::from(vec![ConstDecl::new(
"x",
Some(Object::from(object_properties)),
)])
.into()],
);
}
@ -75,9 +79,11 @@ fn check_object_short_function_arguments() {
b(test) {}
};
",
vec![
ConstDeclList::from(vec![ConstDecl::new("x", Object::from(object_properties))]).into(),
],
vec![ConstDeclList::from(vec![ConstDecl::new(
"x",
Some(Object::from(object_properties)),
)])
.into()],
);
}
@ -98,9 +104,11 @@ fn check_object_getter() {
get b() {}
};
",
vec![
ConstDeclList::from(vec![ConstDecl::new("x", Object::from(object_properties))]).into(),
],
vec![ConstDeclList::from(vec![ConstDecl::new(
"x",
Some(Object::from(object_properties)),
)])
.into()],
);
}
@ -125,8 +133,10 @@ fn check_object_setter() {
set b(test) {}
};
",
vec![
ConstDeclList::from(vec![ConstDecl::new("x", Object::from(object_properties))]).into(),
],
vec![ConstDeclList::from(vec![ConstDecl::new(
"x",
Some(Object::from(object_properties)),
)])
.into()],
);
}

67
boa/src/syntax/parser/statement/declaration/lexical.rs

@ -37,11 +37,17 @@ pub(super) struct LexicalDeclaration {
allow_in: AllowIn,
allow_yield: AllowYield,
allow_await: AllowAwait,
const_init_required: bool,
}
impl LexicalDeclaration {
/// Creates a new `LexicalDeclaration` parser.
pub(super) fn new<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
pub(super) fn new<I, Y, A>(
allow_in: I,
allow_yield: Y,
allow_await: A,
const_init_required: bool,
) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
@ -51,6 +57,7 @@ impl LexicalDeclaration {
allow_in: allow_in.into(),
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
const_init_required,
}
}
}
@ -66,14 +73,22 @@ where
let tok = cursor.next()?.ok_or(ParseError::AbruptEnd)?;
match tok.kind() {
TokenKind::Keyword(Keyword::Const) => {
BindingList::new(self.allow_in, self.allow_yield, self.allow_await, true)
.parse(cursor)
}
TokenKind::Keyword(Keyword::Let) => {
BindingList::new(self.allow_in, self.allow_yield, self.allow_await, false)
.parse(cursor)
}
TokenKind::Keyword(Keyword::Const) => BindingList::new(
self.allow_in,
self.allow_yield,
self.allow_await,
true,
self.const_init_required,
)
.parse(cursor),
TokenKind::Keyword(Keyword::Let) => BindingList::new(
self.allow_in,
self.allow_yield,
self.allow_await,
false,
self.const_init_required,
)
.parse(cursor),
_ => unreachable!("unknown token found: {:?}", tok),
}
}
@ -94,11 +109,18 @@ struct BindingList {
allow_yield: AllowYield,
allow_await: AllowAwait,
is_const: bool,
const_init_required: bool,
}
impl BindingList {
/// Creates a new `BindingList` parser.
fn new<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A, is_const: bool) -> Self
fn new<I, Y, A>(
allow_in: I,
allow_yield: Y,
allow_await: A,
is_const: bool,
const_init_required: bool,
) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
@ -109,6 +131,7 @@ impl BindingList {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
is_const,
const_init_required,
}
}
}
@ -133,14 +156,18 @@ where
.parse(cursor)?;
if self.is_const {
if let Some(init) = init {
const_decls.push(ConstDecl::new(ident, init));
if self.const_init_required {
if init.is_some() {
const_decls.push(ConstDecl::new(ident, init));
} else {
return Err(ParseError::expected(
vec![TokenKind::Punctuator(Punctuator::Assign)],
cursor.next()?.ok_or(ParseError::AbruptEnd)?,
"const declaration",
));
}
} else {
return Err(ParseError::expected(
vec![TokenKind::Punctuator(Punctuator::Assign)],
cursor.next()?.ok_or(ParseError::AbruptEnd)?,
"const declaration",
));
const_decls.push(ConstDecl::new(ident, init))
}
} else {
let_decls.push(LetDecl::new(ident, init));
@ -148,6 +175,12 @@ where
match cursor.peek_semicolon()? {
SemicolonResult::Found(_) => break,
SemicolonResult::NotFound(tk)
if tk.kind() == &TokenKind::Keyword(Keyword::Of)
|| tk.kind() == &TokenKind::Keyword(Keyword::In) =>
{
break
}
SemicolonResult::NotFound(tk)
if tk.kind() == &TokenKind::Punctuator(Punctuator::Comma) =>
{

12
boa/src/syntax/parser/statement/declaration/mod.rs

@ -35,10 +35,11 @@ use std::io::Read;
pub(super) struct Declaration {
allow_yield: AllowYield,
allow_await: AllowAwait,
const_init_required: bool,
}
impl Declaration {
pub(super) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
pub(super) fn new<Y, A>(allow_yield: Y, allow_await: A, const_init_required: bool) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
@ -46,6 +47,7 @@ impl Declaration {
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
const_init_required,
}
}
}
@ -65,7 +67,13 @@ where
HoistableDeclaration::new(self.allow_yield, self.allow_await, false).parse(cursor)
}
TokenKind::Keyword(Keyword::Const) | TokenKind::Keyword(Keyword::Let) => {
LexicalDeclaration::new(true, self.allow_yield, self.allow_await).parse(cursor)
LexicalDeclaration::new(
true,
self.allow_yield,
self.allow_await,
self.const_init_required,
)
.parse(cursor)
}
_ => unreachable!("unknown token found: {:?}", tok),
}

12
boa/src/syntax/parser/statement/declaration/tests.rs

@ -124,7 +124,7 @@ fn multiple_let_declaration() {
fn const_declaration() {
check_parser(
"const a = 5;",
vec![ConstDeclList::from(ConstDecl::new("a", Const::from(5))).into()],
vec![ConstDeclList::from(ConstDecl::new("a", Some(Const::from(5)))).into()],
);
}
@ -133,12 +133,12 @@ fn const_declaration() {
fn const_declaration_keywords() {
check_parser(
"const yield = 5;",
vec![ConstDeclList::from(ConstDecl::new("yield", Const::from(5))).into()],
vec![ConstDeclList::from(ConstDecl::new("yield", Some(Const::from(5)))).into()],
);
check_parser(
"const await = 5;",
vec![ConstDeclList::from(ConstDecl::new("await", Const::from(5))).into()],
vec![ConstDeclList::from(ConstDecl::new("await", Some(Const::from(5)))).into()],
);
}
@ -147,7 +147,7 @@ fn const_declaration_keywords() {
fn const_declaration_no_spaces() {
check_parser(
"const a=5;",
vec![ConstDeclList::from(ConstDecl::new("a", Const::from(5))).into()],
vec![ConstDeclList::from(ConstDecl::new("a", Some(Const::from(5)))).into()],
);
}
@ -163,8 +163,8 @@ fn multiple_const_declaration() {
check_parser(
"const a = 5, c = 6;",
vec![ConstDeclList::from(vec![
ConstDecl::new("a", Const::from(5)),
ConstDecl::new("c", Const::from(6)),
ConstDecl::new("a", Some(Const::from(5))),
ConstDecl::new("c", Some(Const::from(6))),
])
.into()],
);

18
boa/src/syntax/parser/statement/iteration/for_statement.rs

@ -11,7 +11,7 @@ use crate::syntax::lexer::TokenKind;
use crate::{
syntax::{
ast::{
node::{ForLoop, Node},
node::{ForLoop, ForOfLoop, Node},
Const, Keyword, Punctuator,
},
parser::{
@ -65,7 +65,7 @@ impl<R> TokenParser<R> for ForStatement
where
R: Read,
{
type Output = ForLoop;
type Output = Node;
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
let _timer = BoaProfiler::global().start_event("ForStatement", "Parsing");
@ -82,7 +82,7 @@ where
)
}
TokenKind::Keyword(Keyword::Let) | TokenKind::Keyword(Keyword::Const) => {
Some(Declaration::new(self.allow_yield, self.allow_await).parse(cursor)?)
Some(Declaration::new(self.allow_yield, self.allow_await, false).parse(cursor)?)
}
TokenKind::Punctuator(Punctuator::Semicolon) => None,
_ => Some(Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?),
@ -93,8 +93,14 @@ where
Some(tok) if tok.kind() == &TokenKind::Keyword(Keyword::In) => {
unimplemented!("for...in statement")
}
Some(tok) if tok.kind() == &TokenKind::identifier("of") => {
unimplemented!("for...of statement")
Some(tok) if tok.kind() == &TokenKind::Keyword(Keyword::Of) && init.is_some() => {
let _ = cursor.next();
let iterable =
Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect(Punctuator::CloseParen, "for of statement")?;
let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return)
.parse(cursor)?;
return Ok(ForOfLoop::new(init.unwrap(), iterable, body).into());
}
_ => {}
}
@ -124,6 +130,6 @@ where
Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?;
// TODO: do not encapsulate the `for` in a block just to have an inner scope.
Ok(ForLoop::new(init, cond, step, body))
Ok(ForLoop::new(init, cond, step, body).into())
}
}

8
boa/src/syntax/parser/statement/mod.rs

@ -38,9 +38,11 @@ use self::{
use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser};
use crate::syntax::lexer::{InputElement, TokenKind};
use crate::{
syntax::ast::{node, Keyword, Node, Punctuator},
syntax::{
ast::{node, Keyword, Node, Punctuator},
lexer::{InputElement, TokenKind},
},
BoaProfiler,
};
use labelled_stm::LabelledStatement;
@ -373,7 +375,7 @@ where
TokenKind::Keyword(Keyword::Function)
| TokenKind::Keyword(Keyword::Const)
| TokenKind::Keyword(Keyword::Let) => {
Declaration::new(self.allow_yield, self.allow_await).parse(cursor)
Declaration::new(self.allow_yield, self.allow_await, true).parse(cursor)
}
_ => {
Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)

25
boa/src/value/mod.rs

@ -303,6 +303,14 @@ impl Value {
}
}
#[inline]
pub fn as_gc_object(&self) -> Option<GcObject> {
match self {
Self::Object(o) => Some(o.clone()),
_ => None,
}
}
#[inline]
pub fn as_object_mut(&self) -> Option<GcCellRefMut<'_, Object>> {
match *self {
@ -317,6 +325,13 @@ impl Value {
matches!(self, Self::Symbol(_))
}
pub fn as_symbol(&self) -> Option<RcSymbol> {
match self {
Self::Symbol(symbol) => Some(symbol.clone()),
_ => None,
}
}
/// Returns true if the value is a function
#[inline]
pub fn is_function(&self) -> bool {
@ -398,6 +413,14 @@ impl Value {
matches!(self, Self::Boolean(_))
}
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
match self {
Self::Boolean(boolean) => Some(*boolean),
_ => None,
}
}
/// Returns true if the value is a bigint.
#[inline]
pub fn is_bigint(&self) -> bool {
@ -916,7 +939,7 @@ impl Value {
/// [table]: https://tc39.es/ecma262/#table-14
/// [spec]: https://tc39.es/ecma262/#sec-requireobjectcoercible
#[inline]
pub fn require_object_coercible<'a>(&'a self, ctx: &mut Context) -> Result<&'a Value> {
pub fn require_object_coercible(&self, ctx: &mut Context) -> Result<&Value> {
if self.is_null_or_undefined() {
Err(ctx.construct_type_error("cannot convert null or undefined to Object"))
} else {

Loading…
Cancel
Save