From 27f92ed5edc9ff738ad874f7daf66adcb8587eb0 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Thu, 17 Sep 2020 23:59:57 +0100 Subject: [PATCH 01/35] Initial implementation of for...of loop --- boa/src/builtins/array/array_iterator.rs | 141 ++++++++++++++++++ boa/src/builtins/array/mod.rs | 19 +++ boa/src/builtins/mod.rs | 2 +- boa/src/context.rs | 13 ++ boa/src/exec/iteration/mod.rs | 79 +++++++++- boa/src/exec/mod.rs | 1 + boa/src/object/mod.rs | 29 ++++ boa/src/syntax/ast/keyword.rs | 12 ++ boa/src/syntax/ast/node/iteration.rs | 53 +++++++ boa/src/syntax/ast/node/mod.rs | 6 +- .../statement/iteration/for_statement.rs | 16 +- boa/src/value/mod.rs | 15 ++ 12 files changed, 378 insertions(+), 8 deletions(-) create mode 100644 boa/src/builtins/array/array_iterator.rs diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs new file mode 100644 index 00000000000..c4a7540d15d --- /dev/null +++ b/boa/src/builtins/array/array_iterator.rs @@ -0,0 +1,141 @@ +use crate::builtins::function::make_builtin_fn; +use crate::builtins::{Array, Value}; +use crate::object::{ObjectData, PROTOTYPE}; +use crate::property::Property; +use crate::{Context, Result}; +use gc::{Finalize, Trace}; +use std::borrow::Borrow; + +#[derive(Debug, Clone, Finalize, Trace)] +pub enum ArrayIterationKind { + Key, + Value, + KeyAndValue, +} + +#[derive(Debug, Clone, Finalize, Trace)] +pub struct ArrayIterator { + array: Value, + next_index: i32, + kind: ArrayIterationKind, +} + +impl ArrayIterator { + fn new(array: Value, kind: ArrayIterationKind) -> Self { + ArrayIterator { + array, + kind, + next_index: 0, + } + } + + pub(crate) fn new_array_iterator( + interpreter: &Context, + array: Value, + kind: ArrayIterationKind, + ) -> Result { + let array_iterator = Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get global object"), + )); + array_iterator.set_data(ObjectData::ArrayIterator(Self::new(array, kind))); + array_iterator + .as_object_mut() + .expect("array iterator object") + .set_prototype_instance( + interpreter + .realm() + .environment + .get_binding_value("Object") + .expect("Object was not initialized") + .borrow() + .get_field(PROTOTYPE), + ); + make_builtin_fn(Self::next, "next", &array_iterator, 0, interpreter); + + Ok(array_iterator) + } + + pub(crate) fn next(this: &Value, _args: &[Value], interpreter: &mut Context) -> Result { + 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 == Value::undefined() { + return Ok(Self::create_iter_result_object( + interpreter, + Value::undefined(), + true, + )); + } + let len = array_iterator + .array + .get_field("length") + .as_number() + .ok_or_else(|| interpreter.construct_type_error("Not an array"))? + as i32; + if array_iterator.next_index >= len { + array_iterator.array = Value::undefined(); + return Ok(Self::create_iter_result_object( + interpreter, + Value::undefined(), + true, + )); + } + array_iterator.next_index = index + 1; + match array_iterator.kind { + ArrayIterationKind::Key => Ok(Self::create_iter_result_object( + interpreter, + Value::integer(index), + false, + )), + ArrayIterationKind::Value => { + let element_value = array_iterator.array.get_field(index); + Ok(Self::create_iter_result_object( + interpreter, + element_value, + false, + )) + } + ArrayIterationKind::KeyAndValue => { + let element_value = array_iterator.array.get_field(index); + let result = Array::make_array( + &Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get global object"), + )), + &[Value::integer(index), element_value], + interpreter, + )?; + Ok(result) + } + } + } else { + interpreter.throw_type_error("`this` is not an ArrayIterator") + } + } else { + interpreter.throw_type_error("`this` is not an ArrayIterator") + } + } + + fn create_iter_result_object(interpreter: &mut Context, value: Value, done: bool) -> Value { + let object = Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get 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 + } +} diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index c9fe1bacab2..3e3394b6709 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -9,10 +9,12 @@ //! [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}; use crate::{ object::{ObjectData, PROTOTYPE}, property::{Attribute, Property}, @@ -1091,6 +1093,14 @@ impl Array { Ok(accumulator) } + pub(crate) fn values( + this: &Value, + _args: &[Value], + interpreter: &mut Context, + ) -> Result { + ArrayIterator::new_array_iterator(interpreter, this.clone(), ArrayIterationKind::Value) + } + /// Initialise the `Array` object on the global object. #[inline] pub(crate) fn init(interpreter: &mut Context) -> (&'static str, Value) { @@ -1137,6 +1147,15 @@ impl Array { 2, interpreter, ); + make_builtin_fn(Self::values, "values", &prototype, 0, interpreter); + + let symbol_iterator = interpreter + .get_well_known_symbol("iterator") + .expect("Symbol.iterator not initialised"); + prototype.set_property( + symbol_iterator, + Property::default().value(prototype.get_field("values")), + ); let array = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 9e1241d84a1..521173c6fb8 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -49,6 +49,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 +60,6 @@ pub fn init(interpreter: &mut Context) { Number::init, RegExp::init, String::init, - Symbol::init, Console::init, // Global error types. Error::init, diff --git a/boa/src/context.rs b/boa/src/context.rs index 1eacbfacee4..1d44a4f8af2 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -495,4 +495,17 @@ impl Context { result } + + pub fn get_well_known_symbol(&self, name: K) -> Option + where + K: Into, + { + let global_object = self.global_object(); + let symbol = global_object.get_field("Symbol"); + let symbol_iterator: Value = (&symbol.get_property(name)?).into(); + let symbol_iterator = symbol_iterator.as_object()?; + let symbol_iterator = symbol_iterator.get_string_property("value")?; + let symbol_iterator = symbol_iterator.value.as_ref()?.as_symbol()?; + Some(symbol_iterator) + } } diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 29d04b2c027..718c8cac3c7 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -3,7 +3,7 @@ use super::{Context, Executable, InterpreterState}; use crate::{ environment::lexical_environment::new_declarative_environment, - syntax::ast::node::{DoWhileLoop, ForLoop, WhileLoop}, + syntax::ast::node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, BoaProfiler, Result, Value, }; @@ -162,3 +162,80 @@ impl Executable for DoWhileLoop { Ok(result) } } + +impl Executable for ForOfLoop { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("ForOf", "exec"); + let iterable = self.iterable().run(interpreter)?; + let iterator_function = iterable + .get_property( + interpreter + .get_well_known_symbol("iterator") + .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?, + ) + .and_then(|mut p| p.value.take()) + .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; + let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } + //let variable = self.variable().run(interpreter)?; + let next_function = iterator_object + .get_property("next") + .and_then(|mut p| p.value.take()) + .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; + let mut result = Value::undefined(); + + self.variable().run(interpreter)?; + loop { + let next = interpreter.call(&next_function, &iterator_object, &[])?; + let done = next + .get_property("done") + .and_then(|mut p| p.value.take()) + .and_then(|v| v.as_boolean()) + .ok_or_else(|| { + interpreter.construct_type_error("Could not find property `done`") + })?; + if done { + break; + } + let next_result = next + .get_property("value") + .and_then(|mut p| p.value.take()) + .ok_or_else(|| { + interpreter.construct_type_error("Could not find property `value`") + })?; + interpreter.set_value(self.variable(), next_result)?; + 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 interpreter.throw_syntax_error("return not in function") + } + InterpreterState::Executing => { + // Continue execution. + } + } + } + let _ = interpreter.realm_mut().environment.pop(); + Ok(result) + } +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 7f7b9905638..ef1f58356ff 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -94,6 +94,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), diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 667a65ed37f..b65f44f7660 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -15,6 +15,7 @@ mod gcobject; mod internal_methods; mod iter; +use crate::builtins::array::array_iterator::ArrayIterator; pub use gcobject::{GcObject, Ref, RefMut}; pub use iter::*; @@ -62,6 +63,7 @@ pub struct Object { #[derive(Debug, Trace, Finalize)] pub enum ObjectData { Array, + ArrayIterator(ArrayIterator), Map(OrderedMap), RegExp(Box), BigInt(RcBigInt), @@ -84,6 +86,7 @@ impl Display for ObjectData { "{}", match self { Self::Array => "Array", + Self::ArrayIterator(_) => "ArrayIterator", Self::Function(_) => "Function", Self::RegExp(_) => "RegExp", Self::Map(_) => "Map", @@ -252,6 +255,28 @@ 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, + } + } + /// Checks if it is a `Map` object.pub #[inline] pub fn is_map(&self) -> bool { @@ -445,4 +470,8 @@ impl Object { _ => None, } } + + pub fn get_string_property(&self, key: &str) -> Option<&Property> { + self.string_properties.get(key) + } } diff --git a/boa/src/syntax/ast/keyword.rs b/boa/src/syntax/ast/keyword.rs index f4d003bcbc7..566d25b3224 100644 --- a/boa/src/syntax/ast/keyword.rs +++ b/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), diff --git a/boa/src/syntax/ast/node/iteration.rs b/boa/src/syntax/ast/node/iteration.rs index d84a12499f3..3e3c941f5a8 100644 --- a/boa/src/syntax/ast/node/iteration.rs +++ b/boa/src/syntax/ast/node/iteration.rs @@ -309,3 +309,56 @@ impl From for Node { Self::Continue(cont) } } + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ForOfLoop { + variable: Box, + iterable: Box, + body: Box, +} + +impl ForOfLoop { + pub fn new(variable: V, iterable: I, body: B) -> Self + where + V: Into, + I: Into, + B: Into, + { + 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(super) 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 for Node { + fn from(for_of: ForOfLoop) -> Node { + Self::ForOfLoop(for_of) + } +} diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 7042451acbc..97e4958a2b1 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/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, @@ -113,6 +113,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), @@ -208,6 +211,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), diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index 8e53392fb73..c74c85a5319 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/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 TokenParser for ForStatement where R: Read, { - type Output = ForLoop; + type Output = Node; fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("ForStatement", "Parsing"); @@ -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()) } } diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index b243173e8eb..6059dc884d2 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -328,6 +328,13 @@ impl Value { matches!(self, Self::Symbol(_)) } + pub fn as_symbol(&self) -> Option { + match self { + Self::Symbol(symbol) => Some(symbol.clone()), + _ => None, + } + } + /// Returns true if the value is a function #[inline] pub fn is_function(&self) -> bool { @@ -409,6 +416,14 @@ impl Value { matches!(self, Self::Boolean(_)) } + #[inline] + pub fn as_boolean(&self) -> Option { + match self { + Self::Boolean(boolean) => Some(*boolean), + _ => None, + } + } + /// Returns true if the value is a bigint. #[inline] pub fn is_bigint(&self) -> bool { From 39adfefa8f5dacb763b0c96c46e5d1992072cbbd Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Thu, 17 Sep 2020 23:59:57 +0100 Subject: [PATCH 02/35] Initial implementation of for...of loop --- boa/src/builtins/array/array_iterator.rs | 141 ++++++++++++++++++ boa/src/builtins/array/mod.rs | 19 +++ boa/src/builtins/mod.rs | 2 +- boa/src/exec/iteration/mod.rs | 79 +++++++++- boa/src/exec/mod.rs | 1 + boa/src/object/mod.rs | 29 ++++ boa/src/syntax/ast/keyword.rs | 12 ++ boa/src/syntax/ast/node/iteration.rs | 53 +++++++ boa/src/syntax/ast/node/mod.rs | 6 +- .../statement/iteration/for_statement.rs | 16 +- boa/src/value/mod.rs | 15 ++ 11 files changed, 365 insertions(+), 8 deletions(-) create mode 100644 boa/src/builtins/array/array_iterator.rs diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs new file mode 100644 index 00000000000..c4a7540d15d --- /dev/null +++ b/boa/src/builtins/array/array_iterator.rs @@ -0,0 +1,141 @@ +use crate::builtins::function::make_builtin_fn; +use crate::builtins::{Array, Value}; +use crate::object::{ObjectData, PROTOTYPE}; +use crate::property::Property; +use crate::{Context, Result}; +use gc::{Finalize, Trace}; +use std::borrow::Borrow; + +#[derive(Debug, Clone, Finalize, Trace)] +pub enum ArrayIterationKind { + Key, + Value, + KeyAndValue, +} + +#[derive(Debug, Clone, Finalize, Trace)] +pub struct ArrayIterator { + array: Value, + next_index: i32, + kind: ArrayIterationKind, +} + +impl ArrayIterator { + fn new(array: Value, kind: ArrayIterationKind) -> Self { + ArrayIterator { + array, + kind, + next_index: 0, + } + } + + pub(crate) fn new_array_iterator( + interpreter: &Context, + array: Value, + kind: ArrayIterationKind, + ) -> Result { + let array_iterator = Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get global object"), + )); + array_iterator.set_data(ObjectData::ArrayIterator(Self::new(array, kind))); + array_iterator + .as_object_mut() + .expect("array iterator object") + .set_prototype_instance( + interpreter + .realm() + .environment + .get_binding_value("Object") + .expect("Object was not initialized") + .borrow() + .get_field(PROTOTYPE), + ); + make_builtin_fn(Self::next, "next", &array_iterator, 0, interpreter); + + Ok(array_iterator) + } + + pub(crate) fn next(this: &Value, _args: &[Value], interpreter: &mut Context) -> Result { + 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 == Value::undefined() { + return Ok(Self::create_iter_result_object( + interpreter, + Value::undefined(), + true, + )); + } + let len = array_iterator + .array + .get_field("length") + .as_number() + .ok_or_else(|| interpreter.construct_type_error("Not an array"))? + as i32; + if array_iterator.next_index >= len { + array_iterator.array = Value::undefined(); + return Ok(Self::create_iter_result_object( + interpreter, + Value::undefined(), + true, + )); + } + array_iterator.next_index = index + 1; + match array_iterator.kind { + ArrayIterationKind::Key => Ok(Self::create_iter_result_object( + interpreter, + Value::integer(index), + false, + )), + ArrayIterationKind::Value => { + let element_value = array_iterator.array.get_field(index); + Ok(Self::create_iter_result_object( + interpreter, + element_value, + false, + )) + } + ArrayIterationKind::KeyAndValue => { + let element_value = array_iterator.array.get_field(index); + let result = Array::make_array( + &Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get global object"), + )), + &[Value::integer(index), element_value], + interpreter, + )?; + Ok(result) + } + } + } else { + interpreter.throw_type_error("`this` is not an ArrayIterator") + } + } else { + interpreter.throw_type_error("`this` is not an ArrayIterator") + } + } + + fn create_iter_result_object(interpreter: &mut Context, value: Value, done: bool) -> Value { + let object = Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get 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 + } +} diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index c9fe1bacab2..3e3394b6709 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -9,10 +9,12 @@ //! [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}; use crate::{ object::{ObjectData, PROTOTYPE}, property::{Attribute, Property}, @@ -1091,6 +1093,14 @@ impl Array { Ok(accumulator) } + pub(crate) fn values( + this: &Value, + _args: &[Value], + interpreter: &mut Context, + ) -> Result { + ArrayIterator::new_array_iterator(interpreter, this.clone(), ArrayIterationKind::Value) + } + /// Initialise the `Array` object on the global object. #[inline] pub(crate) fn init(interpreter: &mut Context) -> (&'static str, Value) { @@ -1137,6 +1147,15 @@ impl Array { 2, interpreter, ); + make_builtin_fn(Self::values, "values", &prototype, 0, interpreter); + + let symbol_iterator = interpreter + .get_well_known_symbol("iterator") + .expect("Symbol.iterator not initialised"); + prototype.set_property( + symbol_iterator, + Property::default().value(prototype.get_field("values")), + ); let array = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 9e1241d84a1..521173c6fb8 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -49,6 +49,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 +60,6 @@ pub fn init(interpreter: &mut Context) { Number::init, RegExp::init, String::init, - Symbol::init, Console::init, // Global error types. Error::init, diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 29d04b2c027..718c8cac3c7 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -3,7 +3,7 @@ use super::{Context, Executable, InterpreterState}; use crate::{ environment::lexical_environment::new_declarative_environment, - syntax::ast::node::{DoWhileLoop, ForLoop, WhileLoop}, + syntax::ast::node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, BoaProfiler, Result, Value, }; @@ -162,3 +162,80 @@ impl Executable for DoWhileLoop { Ok(result) } } + +impl Executable for ForOfLoop { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("ForOf", "exec"); + let iterable = self.iterable().run(interpreter)?; + let iterator_function = iterable + .get_property( + interpreter + .get_well_known_symbol("iterator") + .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?, + ) + .and_then(|mut p| p.value.take()) + .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; + let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } + //let variable = self.variable().run(interpreter)?; + let next_function = iterator_object + .get_property("next") + .and_then(|mut p| p.value.take()) + .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; + let mut result = Value::undefined(); + + self.variable().run(interpreter)?; + loop { + let next = interpreter.call(&next_function, &iterator_object, &[])?; + let done = next + .get_property("done") + .and_then(|mut p| p.value.take()) + .and_then(|v| v.as_boolean()) + .ok_or_else(|| { + interpreter.construct_type_error("Could not find property `done`") + })?; + if done { + break; + } + let next_result = next + .get_property("value") + .and_then(|mut p| p.value.take()) + .ok_or_else(|| { + interpreter.construct_type_error("Could not find property `value`") + })?; + interpreter.set_value(self.variable(), next_result)?; + 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 interpreter.throw_syntax_error("return not in function") + } + InterpreterState::Executing => { + // Continue execution. + } + } + } + let _ = interpreter.realm_mut().environment.pop(); + Ok(result) + } +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 7f7b9905638..ef1f58356ff 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -94,6 +94,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), diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 667a65ed37f..b65f44f7660 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -15,6 +15,7 @@ mod gcobject; mod internal_methods; mod iter; +use crate::builtins::array::array_iterator::ArrayIterator; pub use gcobject::{GcObject, Ref, RefMut}; pub use iter::*; @@ -62,6 +63,7 @@ pub struct Object { #[derive(Debug, Trace, Finalize)] pub enum ObjectData { Array, + ArrayIterator(ArrayIterator), Map(OrderedMap), RegExp(Box), BigInt(RcBigInt), @@ -84,6 +86,7 @@ impl Display for ObjectData { "{}", match self { Self::Array => "Array", + Self::ArrayIterator(_) => "ArrayIterator", Self::Function(_) => "Function", Self::RegExp(_) => "RegExp", Self::Map(_) => "Map", @@ -252,6 +255,28 @@ 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, + } + } + /// Checks if it is a `Map` object.pub #[inline] pub fn is_map(&self) -> bool { @@ -445,4 +470,8 @@ impl Object { _ => None, } } + + pub fn get_string_property(&self, key: &str) -> Option<&Property> { + self.string_properties.get(key) + } } diff --git a/boa/src/syntax/ast/keyword.rs b/boa/src/syntax/ast/keyword.rs index f4d003bcbc7..566d25b3224 100644 --- a/boa/src/syntax/ast/keyword.rs +++ b/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), diff --git a/boa/src/syntax/ast/node/iteration.rs b/boa/src/syntax/ast/node/iteration.rs index d84a12499f3..3e3c941f5a8 100644 --- a/boa/src/syntax/ast/node/iteration.rs +++ b/boa/src/syntax/ast/node/iteration.rs @@ -309,3 +309,56 @@ impl From for Node { Self::Continue(cont) } } + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ForOfLoop { + variable: Box, + iterable: Box, + body: Box, +} + +impl ForOfLoop { + pub fn new(variable: V, iterable: I, body: B) -> Self + where + V: Into, + I: Into, + B: Into, + { + 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(super) 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 for Node { + fn from(for_of: ForOfLoop) -> Node { + Self::ForOfLoop(for_of) + } +} diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 7042451acbc..97e4958a2b1 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/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, @@ -113,6 +113,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), @@ -208,6 +211,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), diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index 8e53392fb73..c74c85a5319 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/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 TokenParser for ForStatement where R: Read, { - type Output = ForLoop; + type Output = Node; fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("ForStatement", "Parsing"); @@ -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()) } } diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index f10fbaedc04..0a8d0b4c148 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -328,6 +328,13 @@ impl Value { matches!(self, Self::Symbol(_)) } + pub fn as_symbol(&self) -> Option { + match self { + Self::Symbol(symbol) => Some(symbol.clone()), + _ => None, + } + } + /// Returns true if the value is a function #[inline] pub fn is_function(&self) -> bool { @@ -409,6 +416,14 @@ impl Value { matches!(self, Self::Boolean(_)) } + #[inline] + pub fn as_boolean(&self) -> Option { + match self { + Self::Boolean(boolean) => Some(*boolean), + _ => None, + } + } + /// Returns true if the value is a bigint. #[inline] pub fn is_bigint(&self) -> bool { From c3e388fc04437e2db60c956073d3fe2d116b8c6b Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:19:44 +0100 Subject: [PATCH 03/35] Extend for...of to support var, let, and const lhs --- boa/src/builtins/array/array_iterator.rs | 70 ++++++----- boa/src/exec/declaration/mod.rs | 6 +- boa/src/exec/iteration/mod.rs | 115 ++++++++++++++++-- boa/src/syntax/ast/node/declaration.rs | 14 ++- .../primary/object_initializer/tests.rs | 40 +++--- .../parser/statement/declaration/lexical.rs | 67 +++++++--- .../parser/statement/declaration/mod.rs | 12 +- .../parser/statement/declaration/tests.rs | 12 +- .../statement/iteration/for_statement.rs | 2 +- boa/src/syntax/parser/statement/mod.rs | 2 +- 10 files changed, 252 insertions(+), 88 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index c4a7540d15d..1fa149a1235 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,10 +1,13 @@ -use crate::builtins::function::make_builtin_fn; -use crate::builtins::{Array, Value}; -use crate::object::{ObjectData, PROTOTYPE}; -use crate::property::Property; -use crate::{Context, Result}; +use crate::{ + builtins::{function::make_builtin_fn, Array, Value}, + object::{ObjectData, PROTOTYPE}, + property::Property, + Context, Result, +}; use gc::{Finalize, Trace}; use std::borrow::Borrow; +use crate::object::Object; +use crate::builtins::function::{Function, FunctionFlags, BuiltInFunction}; #[derive(Debug, Clone, Finalize, Trace)] pub enum ArrayIterationKind { @@ -30,13 +33,12 @@ impl ArrayIterator { } pub(crate) fn new_array_iterator( - interpreter: &Context, + ctx: &Context, array: Value, kind: ArrayIterationKind, ) -> Result { let array_iterator = Value::new_object(Some( - &interpreter - .realm() + &ctx.realm() .environment .get_global_object() .expect("Could not get global object"), @@ -46,27 +48,41 @@ impl ArrayIterator { .as_object_mut() .expect("array iterator object") .set_prototype_instance( - interpreter - .realm() + ctx.realm() .environment .get_binding_value("Object") .expect("Object was not initialized") .borrow() .get_field(PROTOTYPE), ); - make_builtin_fn(Self::next, "next", &array_iterator, 0, interpreter); + make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx); + let mut function = Object::function( + Function::BuiltIn(BuiltInFunction(|v, _, _| Ok(v.clone())), FunctionFlags::CALLABLE), + ctx + .global_object() + .get_field("Function") + .get_field("prototype"), + ); + function.insert_field("length", Value::from(0)); + let symbol_iterator = ctx + .get_well_known_symbol("iterator") + .expect("Symbol.iterator not initialised"); + array_iterator.set_field( + symbol_iterator, + Value::from(function), + ); Ok(array_iterator) } - pub(crate) fn next(this: &Value, _args: &[Value], interpreter: &mut Context) -> Result { + pub(crate) fn next(this: &Value, _args: &[Value], ctx: &mut Context) -> Result { 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 == Value::undefined() { + if array_iterator.array.is_undefined() { return Ok(Self::create_iter_result_object( - interpreter, + ctx, Value::undefined(), true, )); @@ -75,12 +91,12 @@ impl ArrayIterator { .array .get_field("length") .as_number() - .ok_or_else(|| interpreter.construct_type_error("Not an array"))? + .ok_or_else(|| ctx.construct_type_error("Not an array"))? as i32; if array_iterator.next_index >= len { array_iterator.array = Value::undefined(); return Ok(Self::create_iter_result_object( - interpreter, + ctx, Value::undefined(), true, )); @@ -88,46 +104,40 @@ impl ArrayIterator { array_iterator.next_index = index + 1; match array_iterator.kind { ArrayIterationKind::Key => Ok(Self::create_iter_result_object( - interpreter, + ctx, Value::integer(index), false, )), ArrayIterationKind::Value => { let element_value = array_iterator.array.get_field(index); - Ok(Self::create_iter_result_object( - interpreter, - element_value, - false, - )) + Ok(Self::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( - &interpreter - .realm() + &ctx.realm() .environment .get_global_object() .expect("Could not get global object"), )), &[Value::integer(index), element_value], - interpreter, + ctx, )?; Ok(result) } } } else { - interpreter.throw_type_error("`this` is not an ArrayIterator") + ctx.throw_type_error("`this` is not an ArrayIterator") } } else { - interpreter.throw_type_error("`this` is not an ArrayIterator") + ctx.throw_type_error("`this` is not an ArrayIterator") } } - fn create_iter_result_object(interpreter: &mut Context, value: Value, done: bool) -> Value { + fn create_iter_result_object(ctx: &mut Context, value: Value, done: bool) -> Value { let object = Value::new_object(Some( - &interpreter - .realm() + &ctx.realm() .environment .get_global_object() .expect("Could not get global object"), diff --git a/boa/src/exec/declaration/mod.rs b/boa/src/exec/declaration/mod.rs index a882ea78aa5..b34f491ca9f 100644 --- a/boa/src/exec/declaration/mod.rs +++ b/boa/src/exec/declaration/mod.rs @@ -81,7 +81,11 @@ impl Executable for VarDeclList { impl Executable for ConstDeclList { fn run(&self, interpreter: &mut Context) -> Result { 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() diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 718c8cac3c7..50b28819cf6 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -1,6 +1,8 @@ //! Iteration node execution. use super::{Context, Executable, InterpreterState}; +use crate::environment::lexical_environment::VariableScope; +use crate::syntax::ast::Node; use crate::{ environment::lexical_environment::new_declarative_environment, syntax::ast::node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, @@ -171,17 +173,11 @@ impl Executable for ForOfLoop { .get_property( interpreter .get_well_known_symbol("iterator") - .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?, + .ok_or_else(|| interpreter.construct_type_error("Symbol.iterator not initialised"))?, ) .and_then(|mut p| p.value.take()) .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - { - let env = &mut interpreter.realm_mut().environment; - env.push(new_declarative_environment(Some( - env.get_current_environment_ref().clone(), - ))); - } //let variable = self.variable().run(interpreter)?; let next_function = iterator_object .get_property("next") @@ -189,8 +185,13 @@ impl Executable for ForOfLoop { .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; let mut result = Value::undefined(); - self.variable().run(interpreter)?; loop { + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } let next = interpreter.call(&next_function, &iterator_object, &[])?; let done = next .get_property("done") @@ -208,7 +209,101 @@ impl Executable for ForOfLoop { .ok_or_else(|| { interpreter.construct_type_error("Could not find property `value`") })?; - interpreter.set_value(self.variable(), next_result)?; + + 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) => { @@ -234,8 +329,8 @@ impl Executable for ForOfLoop { // Continue execution. } } + let _ = interpreter.realm_mut().environment.pop(); } - let _ = interpreter.realm_mut().environment.pop(); Ok(result) } } diff --git a/boa/src/syntax/ast/node/declaration.rs b/boa/src/syntax/ast/node/declaration.rs index ea73685bd03..04e30f5ac91 100644 --- a/boa/src/syntax/ast/node/declaration.rs +++ b/boa/src/syntax/ast/node/declaration.rs @@ -410,25 +410,29 @@ impl From for Node { #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct ConstDecl { name: Identifier, - init: Node, + init: Option, } 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(name: N, init: I) -> Self + pub(in crate::syntax) fn new(name: N, init: Option) -> Self where N: Into, I: Into, { Self { name: name.into(), - init: init.into(), + init: init.map(|n| n.into()), } } @@ -438,7 +442,7 @@ impl ConstDecl { } /// Gets the initialization node for the variable, if any. - pub fn init(&self) -> &Node { + pub fn init(&self) -> &Option { &self.init } } diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs index 2a58d44cac1..60292bc920c 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/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()], ); } diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs index e816f5eb485..20ad78c98be 100644 --- a/boa/src/syntax/parser/statement/declaration/lexical.rs +++ b/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(allow_in: I, allow_yield: Y, allow_await: A) -> Self + pub(super) fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + const_init_required: bool, + ) -> Self where I: Into, Y: Into, @@ -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(allow_in: I, allow_yield: Y, allow_await: A, is_const: bool) -> Self + fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + is_const: bool, + const_init_required: bool, + ) -> Self where I: Into, Y: Into, @@ -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) => { diff --git a/boa/src/syntax/parser/statement/declaration/mod.rs b/boa/src/syntax/parser/statement/declaration/mod.rs index cba21cdc027..d1c1c9dd985 100644 --- a/boa/src/syntax/parser/statement/declaration/mod.rs +++ b/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(allow_yield: Y, allow_await: A) -> Self + pub(super) fn new(allow_yield: Y, allow_await: A, const_init_required: bool) -> Self where Y: Into, A: Into, @@ -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), } diff --git a/boa/src/syntax/parser/statement/declaration/tests.rs b/boa/src/syntax/parser/statement/declaration/tests.rs index fbc2f646858..5ed9bd2d146 100644 --- a/boa/src/syntax/parser/statement/declaration/tests.rs +++ b/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()], ); diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index c74c85a5319..0c388c8853e 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/for_statement.rs @@ -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)?), diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 6b93bdc7e88..230fb941400 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -350,7 +350,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) From fc2d5608562d189585fcc3e40da6cca727130f53 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:24:30 +0100 Subject: [PATCH 04/35] Use cached well known symbols --- boa/src/builtins/array/array_iterator.rs | 21 +++++++++------------ boa/src/builtins/array/mod.rs | 4 +--- boa/src/exec/iteration/mod.rs | 7 +------ 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 1fa149a1235..bd29b7f988a 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,3 +1,5 @@ +use crate::builtins::function::{BuiltInFunction, Function, FunctionFlags}; +use crate::object::Object; use crate::{ builtins::{function::make_builtin_fn, Array, Value}, object::{ObjectData, PROTOTYPE}, @@ -6,8 +8,6 @@ use crate::{ }; use gc::{Finalize, Trace}; use std::borrow::Borrow; -use crate::object::Object; -use crate::builtins::function::{Function, FunctionFlags, BuiltInFunction}; #[derive(Debug, Clone, Finalize, Trace)] pub enum ArrayIterationKind { @@ -57,21 +57,18 @@ impl ArrayIterator { ); make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx); let mut function = Object::function( - Function::BuiltIn(BuiltInFunction(|v, _, _| Ok(v.clone())), FunctionFlags::CALLABLE), - ctx - .global_object() + Function::BuiltIn( + BuiltInFunction(|v, _, _| Ok(v.clone())), + FunctionFlags::CALLABLE, + ), + ctx.global_object() .get_field("Function") .get_field("prototype"), ); function.insert_field("length", Value::from(0)); - let symbol_iterator = ctx - .get_well_known_symbol("iterator") - .expect("Symbol.iterator not initialised"); - array_iterator.set_field( - symbol_iterator, - Value::from(function), - ); + let symbol_iterator = ctx.well_known_symbols().iterator_symbol(); + array_iterator.set_field(symbol_iterator, Value::from(function)); Ok(array_iterator) } diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 3e3394b6709..20e5690488d 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -1149,9 +1149,7 @@ impl Array { ); make_builtin_fn(Self::values, "values", &prototype, 0, interpreter); - let symbol_iterator = interpreter - .get_well_known_symbol("iterator") - .expect("Symbol.iterator not initialised"); + let symbol_iterator = interpreter.well_known_symbols().iterator_symbol(); prototype.set_property( symbol_iterator, Property::default().value(prototype.get_field("values")), diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 50b28819cf6..71e6eb64fd7 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -170,15 +170,10 @@ impl Executable for ForOfLoop { let _timer = BoaProfiler::global().start_event("ForOf", "exec"); let iterable = self.iterable().run(interpreter)?; let iterator_function = iterable - .get_property( - interpreter - .get_well_known_symbol("iterator") - .ok_or_else(|| interpreter.construct_type_error("Symbol.iterator not initialised"))?, - ) + .get_property(interpreter.well_known_symbols().iterator_symbol()) .and_then(|mut p| p.value.take()) .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - //let variable = self.variable().run(interpreter)?; let next_function = iterator_object .get_property("next") .and_then(|mut p| p.value.take()) From 21a1c026da79cc1761128188fe22ef0c953f8dbe Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:33:19 +0100 Subject: [PATCH 05/35] Nest use statements --- boa/src/builtins/array/array_iterator.rs | 9 +++++---- boa/src/builtins/array/mod.rs | 2 +- boa/src/exec/iteration/mod.rs | 9 +++++---- boa/src/syntax/parser/statement/mod.rs | 8 +++++--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index bd29b7f988a..bd4a7a05ee3 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,8 +1,9 @@ -use crate::builtins::function::{BuiltInFunction, Function, FunctionFlags}; -use crate::object::Object; use crate::{ - builtins::{function::make_builtin_fn, Array, Value}, - object::{ObjectData, PROTOTYPE}, + builtins::{ + function::{make_builtin_fn, BuiltInFunction, Function, FunctionFlags}, + Array, Value, + }, + object::{Object, ObjectData, PROTOTYPE}, property::Property, Context, Result, }; diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 20e5690488d..d476401f509 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -14,8 +14,8 @@ pub mod array_iterator; mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; -use crate::builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator}; use crate::{ + builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator}, object::{ObjectData, PROTOTYPE}, property::{Attribute, Property}, value::{same_value_zero, Value}, diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 71e6eb64fd7..7e917bca10f 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -1,11 +1,12 @@ //! Iteration node execution. use super::{Context, Executable, InterpreterState}; -use crate::environment::lexical_environment::VariableScope; -use crate::syntax::ast::Node; use crate::{ - environment::lexical_environment::new_declarative_environment, - syntax::ast::node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, + environment::lexical_environment::{new_declarative_environment, VariableScope}, + syntax::ast::{ + node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, + Node, + }, BoaProfiler, Result, Value, }; diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 230fb941400..67a60a8e59c 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -37,10 +37,12 @@ use self::{ use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}; -use crate::syntax::lexer::TokenKind; use crate::{ - syntax::ast::{node, Keyword, Node, Punctuator}, - BoaProfiler, + syntax::{ + lexer::TokenKind, + ast::{node, Keyword, Node, Punctuator} + }, + BoaProfiler }; use std::io::Read; From 353608597fcd82acb22702b3f7fa266cf733a3a0 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:40:39 +0100 Subject: [PATCH 06/35] Nest use statements --- boa/src/object/mod.rs | 6 ++++-- boa/src/syntax/parser/statement/mod.rs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index b65f44f7660..849859b5e8c 100644 --- a/boa/src/object/mod.rs +++ b/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, + BigInt, Date, RegExp, + }, property::{Property, PropertyKey}, value::{RcBigInt, RcString, RcSymbol, Value}, BoaProfiler, @@ -15,7 +18,6 @@ mod gcobject; mod internal_methods; mod iter; -use crate::builtins::array::array_iterator::ArrayIterator; pub use gcobject::{GcObject, Ref, RefMut}; pub use iter::*; diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 67a60a8e59c..fdfe91a76e9 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -39,10 +39,10 @@ use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser use crate::{ syntax::{ + ast::{node, Keyword, Node, Punctuator}, lexer::TokenKind, - ast::{node, Keyword, Node, Punctuator} }, - BoaProfiler + BoaProfiler, }; use std::io::Read; From b4289605dd9b8acd874881871232599c62abd882 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 23:44:44 +0100 Subject: [PATCH 07/35] Add tests. --- boa/src/exec/iteration/mod.rs | 4 +- boa/src/exec/iteration/tests.rs | 129 +++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 4 deletions(-) diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 7e917bca10f..59b2f8d4e38 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -318,9 +318,7 @@ impl Executable for ForOfLoop { .set_current_state(InterpreterState::Executing); // after breaking out of the block, continue execution of the loop } - InterpreterState::Return => { - return interpreter.throw_syntax_error("return not in function") - } + InterpreterState::Return => return Ok(result), InterpreterState::Executing => { // Continue execution. } diff --git a/boa/src/exec/iteration/tests.rs b/boa/src/exec/iteration/tests.rs index 864665b3af4..a605bef6385 100644 --- a/boa/src/exec/iteration/tests.rs +++ b/boa/src/exec/iteration/tests.rs @@ -1,4 +1,4 @@ -use crate::exec; +use crate::{exec, forward, Context}; #[test] fn while_loop_late_break() { @@ -191,3 +191,130 @@ 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"); +} From 66590870bcd529e67ddf1fa3083f94f5800993f5 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Thu, 17 Sep 2020 23:59:57 +0100 Subject: [PATCH 08/35] Initial implementation of for...of loop --- boa/src/builtins/array/array_iterator.rs | 141 ++++++++++++++++++ boa/src/builtins/array/mod.rs | 19 +++ boa/src/builtins/mod.rs | 2 +- boa/src/exec/iteration/mod.rs | 79 +++++++++- boa/src/exec/mod.rs | 1 + boa/src/object/mod.rs | 29 ++++ boa/src/syntax/ast/keyword.rs | 12 ++ boa/src/syntax/ast/node/iteration.rs | 53 +++++++ boa/src/syntax/ast/node/mod.rs | 6 +- .../statement/iteration/for_statement.rs | 16 +- boa/src/value/mod.rs | 15 ++ 11 files changed, 365 insertions(+), 8 deletions(-) create mode 100644 boa/src/builtins/array/array_iterator.rs diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs new file mode 100644 index 00000000000..c4a7540d15d --- /dev/null +++ b/boa/src/builtins/array/array_iterator.rs @@ -0,0 +1,141 @@ +use crate::builtins::function::make_builtin_fn; +use crate::builtins::{Array, Value}; +use crate::object::{ObjectData, PROTOTYPE}; +use crate::property::Property; +use crate::{Context, Result}; +use gc::{Finalize, Trace}; +use std::borrow::Borrow; + +#[derive(Debug, Clone, Finalize, Trace)] +pub enum ArrayIterationKind { + Key, + Value, + KeyAndValue, +} + +#[derive(Debug, Clone, Finalize, Trace)] +pub struct ArrayIterator { + array: Value, + next_index: i32, + kind: ArrayIterationKind, +} + +impl ArrayIterator { + fn new(array: Value, kind: ArrayIterationKind) -> Self { + ArrayIterator { + array, + kind, + next_index: 0, + } + } + + pub(crate) fn new_array_iterator( + interpreter: &Context, + array: Value, + kind: ArrayIterationKind, + ) -> Result { + let array_iterator = Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get global object"), + )); + array_iterator.set_data(ObjectData::ArrayIterator(Self::new(array, kind))); + array_iterator + .as_object_mut() + .expect("array iterator object") + .set_prototype_instance( + interpreter + .realm() + .environment + .get_binding_value("Object") + .expect("Object was not initialized") + .borrow() + .get_field(PROTOTYPE), + ); + make_builtin_fn(Self::next, "next", &array_iterator, 0, interpreter); + + Ok(array_iterator) + } + + pub(crate) fn next(this: &Value, _args: &[Value], interpreter: &mut Context) -> Result { + 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 == Value::undefined() { + return Ok(Self::create_iter_result_object( + interpreter, + Value::undefined(), + true, + )); + } + let len = array_iterator + .array + .get_field("length") + .as_number() + .ok_or_else(|| interpreter.construct_type_error("Not an array"))? + as i32; + if array_iterator.next_index >= len { + array_iterator.array = Value::undefined(); + return Ok(Self::create_iter_result_object( + interpreter, + Value::undefined(), + true, + )); + } + array_iterator.next_index = index + 1; + match array_iterator.kind { + ArrayIterationKind::Key => Ok(Self::create_iter_result_object( + interpreter, + Value::integer(index), + false, + )), + ArrayIterationKind::Value => { + let element_value = array_iterator.array.get_field(index); + Ok(Self::create_iter_result_object( + interpreter, + element_value, + false, + )) + } + ArrayIterationKind::KeyAndValue => { + let element_value = array_iterator.array.get_field(index); + let result = Array::make_array( + &Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get global object"), + )), + &[Value::integer(index), element_value], + interpreter, + )?; + Ok(result) + } + } + } else { + interpreter.throw_type_error("`this` is not an ArrayIterator") + } + } else { + interpreter.throw_type_error("`this` is not an ArrayIterator") + } + } + + fn create_iter_result_object(interpreter: &mut Context, value: Value, done: bool) -> Value { + let object = Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get 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 + } +} diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index c9fe1bacab2..3e3394b6709 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -9,10 +9,12 @@ //! [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}; use crate::{ object::{ObjectData, PROTOTYPE}, property::{Attribute, Property}, @@ -1091,6 +1093,14 @@ impl Array { Ok(accumulator) } + pub(crate) fn values( + this: &Value, + _args: &[Value], + interpreter: &mut Context, + ) -> Result { + ArrayIterator::new_array_iterator(interpreter, this.clone(), ArrayIterationKind::Value) + } + /// Initialise the `Array` object on the global object. #[inline] pub(crate) fn init(interpreter: &mut Context) -> (&'static str, Value) { @@ -1137,6 +1147,15 @@ impl Array { 2, interpreter, ); + make_builtin_fn(Self::values, "values", &prototype, 0, interpreter); + + let symbol_iterator = interpreter + .get_well_known_symbol("iterator") + .expect("Symbol.iterator not initialised"); + prototype.set_property( + symbol_iterator, + Property::default().value(prototype.get_field("values")), + ); let array = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 9e1241d84a1..521173c6fb8 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -49,6 +49,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 +60,6 @@ pub fn init(interpreter: &mut Context) { Number::init, RegExp::init, String::init, - Symbol::init, Console::init, // Global error types. Error::init, diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 29d04b2c027..718c8cac3c7 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -3,7 +3,7 @@ use super::{Context, Executable, InterpreterState}; use crate::{ environment::lexical_environment::new_declarative_environment, - syntax::ast::node::{DoWhileLoop, ForLoop, WhileLoop}, + syntax::ast::node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, BoaProfiler, Result, Value, }; @@ -162,3 +162,80 @@ impl Executable for DoWhileLoop { Ok(result) } } + +impl Executable for ForOfLoop { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("ForOf", "exec"); + let iterable = self.iterable().run(interpreter)?; + let iterator_function = iterable + .get_property( + interpreter + .get_well_known_symbol("iterator") + .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?, + ) + .and_then(|mut p| p.value.take()) + .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; + let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } + //let variable = self.variable().run(interpreter)?; + let next_function = iterator_object + .get_property("next") + .and_then(|mut p| p.value.take()) + .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; + let mut result = Value::undefined(); + + self.variable().run(interpreter)?; + loop { + let next = interpreter.call(&next_function, &iterator_object, &[])?; + let done = next + .get_property("done") + .and_then(|mut p| p.value.take()) + .and_then(|v| v.as_boolean()) + .ok_or_else(|| { + interpreter.construct_type_error("Could not find property `done`") + })?; + if done { + break; + } + let next_result = next + .get_property("value") + .and_then(|mut p| p.value.take()) + .ok_or_else(|| { + interpreter.construct_type_error("Could not find property `value`") + })?; + interpreter.set_value(self.variable(), next_result)?; + 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 interpreter.throw_syntax_error("return not in function") + } + InterpreterState::Executing => { + // Continue execution. + } + } + } + let _ = interpreter.realm_mut().environment.pop(); + Ok(result) + } +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 7f7b9905638..ef1f58356ff 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -94,6 +94,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), diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 667a65ed37f..b65f44f7660 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -15,6 +15,7 @@ mod gcobject; mod internal_methods; mod iter; +use crate::builtins::array::array_iterator::ArrayIterator; pub use gcobject::{GcObject, Ref, RefMut}; pub use iter::*; @@ -62,6 +63,7 @@ pub struct Object { #[derive(Debug, Trace, Finalize)] pub enum ObjectData { Array, + ArrayIterator(ArrayIterator), Map(OrderedMap), RegExp(Box), BigInt(RcBigInt), @@ -84,6 +86,7 @@ impl Display for ObjectData { "{}", match self { Self::Array => "Array", + Self::ArrayIterator(_) => "ArrayIterator", Self::Function(_) => "Function", Self::RegExp(_) => "RegExp", Self::Map(_) => "Map", @@ -252,6 +255,28 @@ 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, + } + } + /// Checks if it is a `Map` object.pub #[inline] pub fn is_map(&self) -> bool { @@ -445,4 +470,8 @@ impl Object { _ => None, } } + + pub fn get_string_property(&self, key: &str) -> Option<&Property> { + self.string_properties.get(key) + } } diff --git a/boa/src/syntax/ast/keyword.rs b/boa/src/syntax/ast/keyword.rs index f4d003bcbc7..566d25b3224 100644 --- a/boa/src/syntax/ast/keyword.rs +++ b/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), diff --git a/boa/src/syntax/ast/node/iteration.rs b/boa/src/syntax/ast/node/iteration.rs index d84a12499f3..3e3c941f5a8 100644 --- a/boa/src/syntax/ast/node/iteration.rs +++ b/boa/src/syntax/ast/node/iteration.rs @@ -309,3 +309,56 @@ impl From for Node { Self::Continue(cont) } } + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ForOfLoop { + variable: Box, + iterable: Box, + body: Box, +} + +impl ForOfLoop { + pub fn new(variable: V, iterable: I, body: B) -> Self + where + V: Into, + I: Into, + B: Into, + { + 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(super) 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 for Node { + fn from(for_of: ForOfLoop) -> Node { + Self::ForOfLoop(for_of) + } +} diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 7042451acbc..97e4958a2b1 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/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, @@ -113,6 +113,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), @@ -208,6 +211,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), diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index 8e53392fb73..c74c85a5319 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/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 TokenParser for ForStatement where R: Read, { - type Output = ForLoop; + type Output = Node; fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("ForStatement", "Parsing"); @@ -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()) } } diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index f10fbaedc04..0a8d0b4c148 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -328,6 +328,13 @@ impl Value { matches!(self, Self::Symbol(_)) } + pub fn as_symbol(&self) -> Option { + match self { + Self::Symbol(symbol) => Some(symbol.clone()), + _ => None, + } + } + /// Returns true if the value is a function #[inline] pub fn is_function(&self) -> bool { @@ -409,6 +416,14 @@ impl Value { matches!(self, Self::Boolean(_)) } + #[inline] + pub fn as_boolean(&self) -> Option { + match self { + Self::Boolean(boolean) => Some(*boolean), + _ => None, + } + } + /// Returns true if the value is a bigint. #[inline] pub fn is_bigint(&self) -> bool { From 3d1d398f19c0c5938e686d69df6f6058cc4e2757 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:19:44 +0100 Subject: [PATCH 09/35] Extend for...of to support var, let, and const lhs --- boa/src/builtins/array/array_iterator.rs | 70 ++++++----- boa/src/exec/declaration/mod.rs | 6 +- boa/src/exec/iteration/mod.rs | 115 ++++++++++++++++-- boa/src/syntax/ast/node/declaration.rs | 14 ++- .../primary/object_initializer/tests.rs | 40 +++--- .../parser/statement/declaration/lexical.rs | 67 +++++++--- .../parser/statement/declaration/mod.rs | 12 +- .../parser/statement/declaration/tests.rs | 12 +- .../statement/iteration/for_statement.rs | 2 +- boa/src/syntax/parser/statement/mod.rs | 2 +- 10 files changed, 252 insertions(+), 88 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index c4a7540d15d..1fa149a1235 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,10 +1,13 @@ -use crate::builtins::function::make_builtin_fn; -use crate::builtins::{Array, Value}; -use crate::object::{ObjectData, PROTOTYPE}; -use crate::property::Property; -use crate::{Context, Result}; +use crate::{ + builtins::{function::make_builtin_fn, Array, Value}, + object::{ObjectData, PROTOTYPE}, + property::Property, + Context, Result, +}; use gc::{Finalize, Trace}; use std::borrow::Borrow; +use crate::object::Object; +use crate::builtins::function::{Function, FunctionFlags, BuiltInFunction}; #[derive(Debug, Clone, Finalize, Trace)] pub enum ArrayIterationKind { @@ -30,13 +33,12 @@ impl ArrayIterator { } pub(crate) fn new_array_iterator( - interpreter: &Context, + ctx: &Context, array: Value, kind: ArrayIterationKind, ) -> Result { let array_iterator = Value::new_object(Some( - &interpreter - .realm() + &ctx.realm() .environment .get_global_object() .expect("Could not get global object"), @@ -46,27 +48,41 @@ impl ArrayIterator { .as_object_mut() .expect("array iterator object") .set_prototype_instance( - interpreter - .realm() + ctx.realm() .environment .get_binding_value("Object") .expect("Object was not initialized") .borrow() .get_field(PROTOTYPE), ); - make_builtin_fn(Self::next, "next", &array_iterator, 0, interpreter); + make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx); + let mut function = Object::function( + Function::BuiltIn(BuiltInFunction(|v, _, _| Ok(v.clone())), FunctionFlags::CALLABLE), + ctx + .global_object() + .get_field("Function") + .get_field("prototype"), + ); + function.insert_field("length", Value::from(0)); + let symbol_iterator = ctx + .get_well_known_symbol("iterator") + .expect("Symbol.iterator not initialised"); + array_iterator.set_field( + symbol_iterator, + Value::from(function), + ); Ok(array_iterator) } - pub(crate) fn next(this: &Value, _args: &[Value], interpreter: &mut Context) -> Result { + pub(crate) fn next(this: &Value, _args: &[Value], ctx: &mut Context) -> Result { 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 == Value::undefined() { + if array_iterator.array.is_undefined() { return Ok(Self::create_iter_result_object( - interpreter, + ctx, Value::undefined(), true, )); @@ -75,12 +91,12 @@ impl ArrayIterator { .array .get_field("length") .as_number() - .ok_or_else(|| interpreter.construct_type_error("Not an array"))? + .ok_or_else(|| ctx.construct_type_error("Not an array"))? as i32; if array_iterator.next_index >= len { array_iterator.array = Value::undefined(); return Ok(Self::create_iter_result_object( - interpreter, + ctx, Value::undefined(), true, )); @@ -88,46 +104,40 @@ impl ArrayIterator { array_iterator.next_index = index + 1; match array_iterator.kind { ArrayIterationKind::Key => Ok(Self::create_iter_result_object( - interpreter, + ctx, Value::integer(index), false, )), ArrayIterationKind::Value => { let element_value = array_iterator.array.get_field(index); - Ok(Self::create_iter_result_object( - interpreter, - element_value, - false, - )) + Ok(Self::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( - &interpreter - .realm() + &ctx.realm() .environment .get_global_object() .expect("Could not get global object"), )), &[Value::integer(index), element_value], - interpreter, + ctx, )?; Ok(result) } } } else { - interpreter.throw_type_error("`this` is not an ArrayIterator") + ctx.throw_type_error("`this` is not an ArrayIterator") } } else { - interpreter.throw_type_error("`this` is not an ArrayIterator") + ctx.throw_type_error("`this` is not an ArrayIterator") } } - fn create_iter_result_object(interpreter: &mut Context, value: Value, done: bool) -> Value { + fn create_iter_result_object(ctx: &mut Context, value: Value, done: bool) -> Value { let object = Value::new_object(Some( - &interpreter - .realm() + &ctx.realm() .environment .get_global_object() .expect("Could not get global object"), diff --git a/boa/src/exec/declaration/mod.rs b/boa/src/exec/declaration/mod.rs index a882ea78aa5..b34f491ca9f 100644 --- a/boa/src/exec/declaration/mod.rs +++ b/boa/src/exec/declaration/mod.rs @@ -81,7 +81,11 @@ impl Executable for VarDeclList { impl Executable for ConstDeclList { fn run(&self, interpreter: &mut Context) -> Result { 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() diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 718c8cac3c7..50b28819cf6 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -1,6 +1,8 @@ //! Iteration node execution. use super::{Context, Executable, InterpreterState}; +use crate::environment::lexical_environment::VariableScope; +use crate::syntax::ast::Node; use crate::{ environment::lexical_environment::new_declarative_environment, syntax::ast::node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, @@ -171,17 +173,11 @@ impl Executable for ForOfLoop { .get_property( interpreter .get_well_known_symbol("iterator") - .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?, + .ok_or_else(|| interpreter.construct_type_error("Symbol.iterator not initialised"))?, ) .and_then(|mut p| p.value.take()) .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - { - let env = &mut interpreter.realm_mut().environment; - env.push(new_declarative_environment(Some( - env.get_current_environment_ref().clone(), - ))); - } //let variable = self.variable().run(interpreter)?; let next_function = iterator_object .get_property("next") @@ -189,8 +185,13 @@ impl Executable for ForOfLoop { .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; let mut result = Value::undefined(); - self.variable().run(interpreter)?; loop { + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } let next = interpreter.call(&next_function, &iterator_object, &[])?; let done = next .get_property("done") @@ -208,7 +209,101 @@ impl Executable for ForOfLoop { .ok_or_else(|| { interpreter.construct_type_error("Could not find property `value`") })?; - interpreter.set_value(self.variable(), next_result)?; + + 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) => { @@ -234,8 +329,8 @@ impl Executable for ForOfLoop { // Continue execution. } } + let _ = interpreter.realm_mut().environment.pop(); } - let _ = interpreter.realm_mut().environment.pop(); Ok(result) } } diff --git a/boa/src/syntax/ast/node/declaration.rs b/boa/src/syntax/ast/node/declaration.rs index ea73685bd03..04e30f5ac91 100644 --- a/boa/src/syntax/ast/node/declaration.rs +++ b/boa/src/syntax/ast/node/declaration.rs @@ -410,25 +410,29 @@ impl From for Node { #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct ConstDecl { name: Identifier, - init: Node, + init: Option, } 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(name: N, init: I) -> Self + pub(in crate::syntax) fn new(name: N, init: Option) -> Self where N: Into, I: Into, { Self { name: name.into(), - init: init.into(), + init: init.map(|n| n.into()), } } @@ -438,7 +442,7 @@ impl ConstDecl { } /// Gets the initialization node for the variable, if any. - pub fn init(&self) -> &Node { + pub fn init(&self) -> &Option { &self.init } } diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs index 2a58d44cac1..60292bc920c 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/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()], ); } diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs index e816f5eb485..20ad78c98be 100644 --- a/boa/src/syntax/parser/statement/declaration/lexical.rs +++ b/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(allow_in: I, allow_yield: Y, allow_await: A) -> Self + pub(super) fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + const_init_required: bool, + ) -> Self where I: Into, Y: Into, @@ -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(allow_in: I, allow_yield: Y, allow_await: A, is_const: bool) -> Self + fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + is_const: bool, + const_init_required: bool, + ) -> Self where I: Into, Y: Into, @@ -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) => { diff --git a/boa/src/syntax/parser/statement/declaration/mod.rs b/boa/src/syntax/parser/statement/declaration/mod.rs index cba21cdc027..d1c1c9dd985 100644 --- a/boa/src/syntax/parser/statement/declaration/mod.rs +++ b/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(allow_yield: Y, allow_await: A) -> Self + pub(super) fn new(allow_yield: Y, allow_await: A, const_init_required: bool) -> Self where Y: Into, A: Into, @@ -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), } diff --git a/boa/src/syntax/parser/statement/declaration/tests.rs b/boa/src/syntax/parser/statement/declaration/tests.rs index fbc2f646858..5ed9bd2d146 100644 --- a/boa/src/syntax/parser/statement/declaration/tests.rs +++ b/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()], ); diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index c74c85a5319..0c388c8853e 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/for_statement.rs @@ -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)?), diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 6b93bdc7e88..230fb941400 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -350,7 +350,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) From 6209208fca182da9d5d8813d97d5b9d54c94b2af Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:24:30 +0100 Subject: [PATCH 10/35] Use cached well known symbols --- boa/src/builtins/array/array_iterator.rs | 21 +++++++++------------ boa/src/builtins/array/mod.rs | 4 +--- boa/src/exec/iteration/mod.rs | 7 +------ 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 1fa149a1235..bd29b7f988a 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,3 +1,5 @@ +use crate::builtins::function::{BuiltInFunction, Function, FunctionFlags}; +use crate::object::Object; use crate::{ builtins::{function::make_builtin_fn, Array, Value}, object::{ObjectData, PROTOTYPE}, @@ -6,8 +8,6 @@ use crate::{ }; use gc::{Finalize, Trace}; use std::borrow::Borrow; -use crate::object::Object; -use crate::builtins::function::{Function, FunctionFlags, BuiltInFunction}; #[derive(Debug, Clone, Finalize, Trace)] pub enum ArrayIterationKind { @@ -57,21 +57,18 @@ impl ArrayIterator { ); make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx); let mut function = Object::function( - Function::BuiltIn(BuiltInFunction(|v, _, _| Ok(v.clone())), FunctionFlags::CALLABLE), - ctx - .global_object() + Function::BuiltIn( + BuiltInFunction(|v, _, _| Ok(v.clone())), + FunctionFlags::CALLABLE, + ), + ctx.global_object() .get_field("Function") .get_field("prototype"), ); function.insert_field("length", Value::from(0)); - let symbol_iterator = ctx - .get_well_known_symbol("iterator") - .expect("Symbol.iterator not initialised"); - array_iterator.set_field( - symbol_iterator, - Value::from(function), - ); + let symbol_iterator = ctx.well_known_symbols().iterator_symbol(); + array_iterator.set_field(symbol_iterator, Value::from(function)); Ok(array_iterator) } diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 3e3394b6709..20e5690488d 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -1149,9 +1149,7 @@ impl Array { ); make_builtin_fn(Self::values, "values", &prototype, 0, interpreter); - let symbol_iterator = interpreter - .get_well_known_symbol("iterator") - .expect("Symbol.iterator not initialised"); + let symbol_iterator = interpreter.well_known_symbols().iterator_symbol(); prototype.set_property( symbol_iterator, Property::default().value(prototype.get_field("values")), diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 50b28819cf6..71e6eb64fd7 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -170,15 +170,10 @@ impl Executable for ForOfLoop { let _timer = BoaProfiler::global().start_event("ForOf", "exec"); let iterable = self.iterable().run(interpreter)?; let iterator_function = iterable - .get_property( - interpreter - .get_well_known_symbol("iterator") - .ok_or_else(|| interpreter.construct_type_error("Symbol.iterator not initialised"))?, - ) + .get_property(interpreter.well_known_symbols().iterator_symbol()) .and_then(|mut p| p.value.take()) .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - //let variable = self.variable().run(interpreter)?; let next_function = iterator_object .get_property("next") .and_then(|mut p| p.value.take()) From f32f09029e0b20c2da2d068823cfd0be11713633 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:33:19 +0100 Subject: [PATCH 11/35] Nest use statements --- boa/src/builtins/array/array_iterator.rs | 9 +++++---- boa/src/builtins/array/mod.rs | 2 +- boa/src/exec/iteration/mod.rs | 9 +++++---- boa/src/syntax/parser/statement/mod.rs | 8 +++++--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index bd29b7f988a..bd4a7a05ee3 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,8 +1,9 @@ -use crate::builtins::function::{BuiltInFunction, Function, FunctionFlags}; -use crate::object::Object; use crate::{ - builtins::{function::make_builtin_fn, Array, Value}, - object::{ObjectData, PROTOTYPE}, + builtins::{ + function::{make_builtin_fn, BuiltInFunction, Function, FunctionFlags}, + Array, Value, + }, + object::{Object, ObjectData, PROTOTYPE}, property::Property, Context, Result, }; diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 20e5690488d..d476401f509 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -14,8 +14,8 @@ pub mod array_iterator; mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; -use crate::builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator}; use crate::{ + builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator}, object::{ObjectData, PROTOTYPE}, property::{Attribute, Property}, value::{same_value_zero, Value}, diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 71e6eb64fd7..7e917bca10f 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -1,11 +1,12 @@ //! Iteration node execution. use super::{Context, Executable, InterpreterState}; -use crate::environment::lexical_environment::VariableScope; -use crate::syntax::ast::Node; use crate::{ - environment::lexical_environment::new_declarative_environment, - syntax::ast::node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, + environment::lexical_environment::{new_declarative_environment, VariableScope}, + syntax::ast::{ + node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, + Node, + }, BoaProfiler, Result, Value, }; diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 230fb941400..67a60a8e59c 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -37,10 +37,12 @@ use self::{ use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}; -use crate::syntax::lexer::TokenKind; use crate::{ - syntax::ast::{node, Keyword, Node, Punctuator}, - BoaProfiler, + syntax::{ + lexer::TokenKind, + ast::{node, Keyword, Node, Punctuator} + }, + BoaProfiler }; use std::io::Read; From 08d4a93e997df168e577070acada8214e06b323a Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:40:39 +0100 Subject: [PATCH 12/35] Nest use statements --- boa/src/object/mod.rs | 6 ++++-- boa/src/syntax/parser/statement/mod.rs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index b65f44f7660..849859b5e8c 100644 --- a/boa/src/object/mod.rs +++ b/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, + BigInt, Date, RegExp, + }, property::{Property, PropertyKey}, value::{RcBigInt, RcString, RcSymbol, Value}, BoaProfiler, @@ -15,7 +18,6 @@ mod gcobject; mod internal_methods; mod iter; -use crate::builtins::array::array_iterator::ArrayIterator; pub use gcobject::{GcObject, Ref, RefMut}; pub use iter::*; diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 67a60a8e59c..fdfe91a76e9 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -39,10 +39,10 @@ use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser use crate::{ syntax::{ + ast::{node, Keyword, Node, Punctuator}, lexer::TokenKind, - ast::{node, Keyword, Node, Punctuator} }, - BoaProfiler + BoaProfiler, }; use std::io::Read; From a6afab70e6b5c882e594f0b479fe06d746c53c90 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 23:44:44 +0100 Subject: [PATCH 13/35] Add tests. --- boa/src/exec/iteration/mod.rs | 4 +- boa/src/exec/iteration/tests.rs | 129 +++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 4 deletions(-) diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 7e917bca10f..59b2f8d4e38 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -318,9 +318,7 @@ impl Executable for ForOfLoop { .set_current_state(InterpreterState::Executing); // after breaking out of the block, continue execution of the loop } - InterpreterState::Return => { - return interpreter.throw_syntax_error("return not in function") - } + InterpreterState::Return => return Ok(result), InterpreterState::Executing => { // Continue execution. } diff --git a/boa/src/exec/iteration/tests.rs b/boa/src/exec/iteration/tests.rs index 864665b3af4..a605bef6385 100644 --- a/boa/src/exec/iteration/tests.rs +++ b/boa/src/exec/iteration/tests.rs @@ -1,4 +1,4 @@ -use crate::exec; +use crate::{exec, forward, Context}; #[test] fn while_loop_late_break() { @@ -191,3 +191,130 @@ 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"); +} From e1ba773f9bc63c5ddcbc8422ef78b3b096716c44 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Fri, 25 Sep 2020 19:32:52 +0100 Subject: [PATCH 14/35] Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use. --- boa/src/builtins/array/array_iterator.rs | 120 +++++++++---------- boa/src/builtins/array/mod.rs | 50 +++++++- boa/src/builtins/array/tests.rs | 132 +++++++++++++++++++++ boa/src/builtins/iterable/mod.rs | 141 +++++++++++++++++++++++ boa/src/builtins/mod.rs | 3 +- boa/src/context.rs | 11 +- boa/src/exec/iteration/mod.rs | 29 +---- 7 files changed, 399 insertions(+), 87 deletions(-) create mode 100644 boa/src/builtins/iterable/mod.rs diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index bd4a7a05ee3..5fee1c23a59 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,14 +1,10 @@ use crate::{ - builtins::{ - function::{make_builtin_fn, BuiltInFunction, Function, FunctionFlags}, - Array, Value, - }, - object::{Object, ObjectData, PROTOTYPE}, - property::Property, - Context, Result, + builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, Value}, + object::ObjectData, + property::{Attribute, Property}, + BoaProfiler, Context, Result, }; use gc::{Finalize, Trace}; -use std::borrow::Borrow; #[derive(Debug, Clone, Finalize, Trace)] pub enum ArrayIterationKind { @@ -17,6 +13,12 @@ pub enum ArrayIterationKind { 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, @@ -25,6 +27,8 @@ pub struct ArrayIterator { } impl ArrayIterator { + pub(crate) const NAME: &'static str = "ArrayIterator"; + fn new(array: Value, kind: ArrayIterationKind) -> Self { ArrayIterator { array, @@ -33,7 +37,15 @@ impl ArrayIterator { } } - pub(crate) fn new_array_iterator( + /// 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, @@ -48,42 +60,25 @@ impl ArrayIterator { array_iterator .as_object_mut() .expect("array iterator object") - .set_prototype_instance( - ctx.realm() - .environment - .get_binding_value("Object") - .expect("Object was not initialized") - .borrow() - .get_field(PROTOTYPE), - ); - make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx); - let mut function = Object::function( - Function::BuiltIn( - BuiltInFunction(|v, _, _| Ok(v.clone())), - FunctionFlags::CALLABLE, - ), - ctx.global_object() - .get_field("Function") - .get_field("prototype"), - ); - function.insert_field("length", Value::from(0)); - - let symbol_iterator = ctx.well_known_symbols().iterator_symbol(); - array_iterator.set_field(symbol_iterator, Value::from(function)); + .set_prototype_instance(ctx.iterator_prototypes().array_iterator()); 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 { 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(Self::create_iter_result_object( - ctx, - Value::undefined(), - true, - )); + return Ok(create_iter_result_object(ctx, Value::undefined(), true)); } let len = array_iterator .array @@ -93,22 +88,16 @@ impl ArrayIterator { as i32; if array_iterator.next_index >= len { array_iterator.array = Value::undefined(); - return Ok(Self::create_iter_result_object( - ctx, - Value::undefined(), - true, - )); + return Ok(create_iter_result_object(ctx, Value::undefined(), true)); } array_iterator.next_index = index + 1; match array_iterator.kind { - ArrayIterationKind::Key => Ok(Self::create_iter_result_object( - ctx, - Value::integer(index), - false, - )), + ArrayIterationKind::Key => { + Ok(create_iter_result_object(ctx, Value::integer(index), false)) + } ArrayIterationKind::Value => { let element_value = array_iterator.array.get_field(index); - Ok(Self::create_iter_result_object(ctx, element_value, false)) + Ok(create_iter_result_object(ctx, element_value, false)) } ArrayIterationKind::KeyAndValue => { let element_value = array_iterator.array.get_field(index); @@ -122,7 +111,7 @@ impl ArrayIterator { &[Value::integer(index), element_value], ctx, )?; - Ok(result) + Ok(create_iter_result_object(ctx, result, false)) } } } else { @@ -133,17 +122,28 @@ impl ArrayIterator { } } - fn create_iter_result_object(ctx: &mut Context, value: Value, done: bool) -> Value { - let object = Value::new_object(Some( - &ctx.realm() - .environment - .get_global_object() - .expect("Could not get 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 + /// 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 } } diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index d476401f509..4b3e40c453e 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -1093,12 +1093,58 @@ 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 { - ArrayIterator::new_array_iterator(interpreter, this.clone(), ArrayIterationKind::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 { + 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 { + ArrayIterator::create_array_iterator( + interpreter, + this.clone(), + ArrayIterationKind::KeyAndValue, + ) } /// Initialise the `Array` object on the global object. @@ -1148,6 +1194,8 @@ impl Array { 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( diff --git a/boa/src/builtins/array/tests.rs b/boa/src/builtins/array/tests.rs index fa066ad979e..876de0e8537 100644 --- a/boa/src/builtins/array/tests.rs +++ b/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"); +} diff --git a/boa/src/builtins/iterable/mod.rs b/boa/src/builtins/iterable/mod.rs new file mode 100644 index 00000000000..00615185a0e --- /dev/null +++ b/boa/src/builtins/iterable/mod.rs @@ -0,0 +1,141 @@ +use crate::builtins::function::{BuiltInFunction, Function, FunctionFlags}; +use crate::builtins::ArrayIterator; +use crate::object::{Object, PROTOTYPE}; +use crate::BoaProfiler; +use crate::{property::Property, Context, Value}; + +#[derive(Debug, Default)] +pub struct IteratorPrototypes { + iterator_prototype: Value, + array_iterator: Value, +} + +impl IteratorPrototypes { + pub fn init(ctx: &mut Context) -> Self { + let iterator_prototype = create_iterator_prototype(ctx); + Self { + iterator_prototype: iterator_prototype.clone(), + array_iterator: ArrayIterator::create_prototype(ctx, iterator_prototype), + } + } + + pub fn array_iterator(&self) -> Value { + self.array_iterator.clone() + } + + pub fn iterator_prototype(&self) -> Value { + self.iterator_prototype.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.realm() + .environment + .get_global_object() + .expect("Could not get 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 { + 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 { + 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 + } +} diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 521173c6fb8..da592c010e4 100644 --- a/boa/src/builtins/mod.rs +++ b/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, diff --git a/boa/src/context.rs b/boa/src/context.rs index cb2b6abf9f6..24328e83346 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -1,5 +1,6 @@ //! Javascript context. +use crate::builtins::iterable::IteratorPrototypes; use crate::{ builtins::{ self, @@ -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 + } } diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 59b2f8d4e38..be5bd8db718 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -2,6 +2,7 @@ use super::{Context, Executable, InterpreterState}; use crate::{ + builtins::iterable::get_iterator, environment::lexical_environment::{new_declarative_environment, VariableScope}, syntax::ast::{ node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, @@ -170,15 +171,7 @@ impl Executable for ForOfLoop { fn run(&self, interpreter: &mut Context) -> Result { let _timer = BoaProfiler::global().start_event("ForOf", "exec"); let iterable = self.iterable().run(interpreter)?; - let iterator_function = iterable - .get_property(interpreter.well_known_symbols().iterator_symbol()) - .and_then(|mut p| p.value.take()) - .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; - let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - let next_function = iterator_object - .get_property("next") - .and_then(|mut p| p.value.take()) - .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; + let iterator = get_iterator(interpreter, iterable)?; let mut result = Value::undefined(); loop { @@ -188,23 +181,11 @@ impl Executable for ForOfLoop { env.get_current_environment_ref().clone(), ))); } - let next = interpreter.call(&next_function, &iterator_object, &[])?; - let done = next - .get_property("done") - .and_then(|mut p| p.value.take()) - .and_then(|v| v.as_boolean()) - .ok_or_else(|| { - interpreter.construct_type_error("Could not find property `done`") - })?; - if done { + let iterator_result = iterator.next(interpreter)?; + if iterator_result.is_done() { break; } - let next_result = next - .get_property("value") - .and_then(|mut p| p.value.take()) - .ok_or_else(|| { - interpreter.construct_type_error("Could not find property `value`") - })?; + let next_result = iterator_result.value(); match self.variable() { Node::Identifier(ref name) => { From 66f4c792bac084aec9d9925e464a2f1b26fbebd9 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Thu, 17 Sep 2020 23:59:57 +0100 Subject: [PATCH 15/35] Initial implementation of for...of loop --- boa/src/builtins/array/array_iterator.rs | 141 ++++++++++++++++++ boa/src/builtins/array/mod.rs | 19 +++ boa/src/builtins/mod.rs | 2 +- boa/src/exec/iteration/mod.rs | 79 +++++++++- boa/src/exec/mod.rs | 1 + boa/src/object/mod.rs | 29 ++++ boa/src/syntax/ast/keyword.rs | 12 ++ boa/src/syntax/ast/node/iteration.rs | 53 +++++++ boa/src/syntax/ast/node/mod.rs | 6 +- .../statement/iteration/for_statement.rs | 16 +- boa/src/value/mod.rs | 15 ++ 11 files changed, 365 insertions(+), 8 deletions(-) create mode 100644 boa/src/builtins/array/array_iterator.rs diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs new file mode 100644 index 00000000000..c4a7540d15d --- /dev/null +++ b/boa/src/builtins/array/array_iterator.rs @@ -0,0 +1,141 @@ +use crate::builtins::function::make_builtin_fn; +use crate::builtins::{Array, Value}; +use crate::object::{ObjectData, PROTOTYPE}; +use crate::property::Property; +use crate::{Context, Result}; +use gc::{Finalize, Trace}; +use std::borrow::Borrow; + +#[derive(Debug, Clone, Finalize, Trace)] +pub enum ArrayIterationKind { + Key, + Value, + KeyAndValue, +} + +#[derive(Debug, Clone, Finalize, Trace)] +pub struct ArrayIterator { + array: Value, + next_index: i32, + kind: ArrayIterationKind, +} + +impl ArrayIterator { + fn new(array: Value, kind: ArrayIterationKind) -> Self { + ArrayIterator { + array, + kind, + next_index: 0, + } + } + + pub(crate) fn new_array_iterator( + interpreter: &Context, + array: Value, + kind: ArrayIterationKind, + ) -> Result { + let array_iterator = Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get global object"), + )); + array_iterator.set_data(ObjectData::ArrayIterator(Self::new(array, kind))); + array_iterator + .as_object_mut() + .expect("array iterator object") + .set_prototype_instance( + interpreter + .realm() + .environment + .get_binding_value("Object") + .expect("Object was not initialized") + .borrow() + .get_field(PROTOTYPE), + ); + make_builtin_fn(Self::next, "next", &array_iterator, 0, interpreter); + + Ok(array_iterator) + } + + pub(crate) fn next(this: &Value, _args: &[Value], interpreter: &mut Context) -> Result { + 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 == Value::undefined() { + return Ok(Self::create_iter_result_object( + interpreter, + Value::undefined(), + true, + )); + } + let len = array_iterator + .array + .get_field("length") + .as_number() + .ok_or_else(|| interpreter.construct_type_error("Not an array"))? + as i32; + if array_iterator.next_index >= len { + array_iterator.array = Value::undefined(); + return Ok(Self::create_iter_result_object( + interpreter, + Value::undefined(), + true, + )); + } + array_iterator.next_index = index + 1; + match array_iterator.kind { + ArrayIterationKind::Key => Ok(Self::create_iter_result_object( + interpreter, + Value::integer(index), + false, + )), + ArrayIterationKind::Value => { + let element_value = array_iterator.array.get_field(index); + Ok(Self::create_iter_result_object( + interpreter, + element_value, + false, + )) + } + ArrayIterationKind::KeyAndValue => { + let element_value = array_iterator.array.get_field(index); + let result = Array::make_array( + &Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get global object"), + )), + &[Value::integer(index), element_value], + interpreter, + )?; + Ok(result) + } + } + } else { + interpreter.throw_type_error("`this` is not an ArrayIterator") + } + } else { + interpreter.throw_type_error("`this` is not an ArrayIterator") + } + } + + fn create_iter_result_object(interpreter: &mut Context, value: Value, done: bool) -> Value { + let object = Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get 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 + } +} diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index f90f3dbea62..682ffdcdd11 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -9,10 +9,12 @@ //! [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}; use crate::{ object::{ObjectData, PROTOTYPE}, property::{Attribute, Property}, @@ -1091,6 +1093,14 @@ impl Array { Ok(accumulator) } + pub(crate) fn values( + this: &Value, + _args: &[Value], + interpreter: &mut Context, + ) -> Result { + ArrayIterator::new_array_iterator(interpreter, this.clone(), ArrayIterationKind::Value) + } + /// Initialise the `Array` object on the global object. #[inline] pub(crate) fn init(interpreter: &mut Context) -> (&'static str, Value) { @@ -1137,6 +1147,15 @@ impl Array { 2, interpreter, ); + make_builtin_fn(Self::values, "values", &prototype, 0, interpreter); + + let symbol_iterator = interpreter + .get_well_known_symbol("iterator") + .expect("Symbol.iterator not initialised"); + prototype.set_property( + symbol_iterator, + Property::default().value(prototype.get_field("values")), + ); let array = make_constructor_fn( Self::NAME, diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 9e1241d84a1..521173c6fb8 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -49,6 +49,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 +60,6 @@ pub fn init(interpreter: &mut Context) { Number::init, RegExp::init, String::init, - Symbol::init, Console::init, // Global error types. Error::init, diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index a6720788590..62431e982fd 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -3,7 +3,7 @@ use super::{Context, Executable, InterpreterState}; use crate::{ environment::lexical_environment::new_declarative_environment, - syntax::ast::node::{DoWhileLoop, ForLoop, WhileLoop}, + syntax::ast::node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, BoaProfiler, Result, Value, }; @@ -166,3 +166,80 @@ impl Executable for DoWhileLoop { Ok(result) } } + +impl Executable for ForOfLoop { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("ForOf", "exec"); + let iterable = self.iterable().run(interpreter)?; + let iterator_function = iterable + .get_property( + interpreter + .get_well_known_symbol("iterator") + .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?, + ) + .and_then(|mut p| p.value.take()) + .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; + let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } + //let variable = self.variable().run(interpreter)?; + let next_function = iterator_object + .get_property("next") + .and_then(|mut p| p.value.take()) + .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; + let mut result = Value::undefined(); + + self.variable().run(interpreter)?; + loop { + let next = interpreter.call(&next_function, &iterator_object, &[])?; + let done = next + .get_property("done") + .and_then(|mut p| p.value.take()) + .and_then(|v| v.as_boolean()) + .ok_or_else(|| { + interpreter.construct_type_error("Could not find property `done`") + })?; + if done { + break; + } + let next_result = next + .get_property("value") + .and_then(|mut p| p.value.take()) + .ok_or_else(|| { + interpreter.construct_type_error("Could not find property `value`") + })?; + interpreter.set_value(self.variable(), next_result)?; + 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 interpreter.throw_syntax_error("return not in function") + } + InterpreterState::Executing => { + // Continue execution. + } + } + } + let _ = interpreter.realm_mut().environment.pop(); + Ok(result) + } +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 5d12878414d..e4f00e71a0e 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -94,6 +94,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), diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index fe92ddc76c6..48e647b8825 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -15,6 +15,7 @@ mod gcobject; mod internal_methods; mod iter; +use crate::builtins::array::array_iterator::ArrayIterator; pub use gcobject::{GcObject, Ref, RefMut}; pub use iter::*; @@ -62,6 +63,7 @@ pub struct Object { #[derive(Debug, Trace, Finalize)] pub enum ObjectData { Array, + ArrayIterator(ArrayIterator), Map(OrderedMap), RegExp(Box), BigInt(RcBigInt), @@ -84,6 +86,7 @@ impl Display for ObjectData { "{}", match self { Self::Array => "Array", + Self::ArrayIterator(_) => "ArrayIterator", Self::Function(_) => "Function", Self::RegExp(_) => "RegExp", Self::Map(_) => "Map", @@ -252,6 +255,28 @@ 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, + } + } + /// Checks if it is a `Map` object.pub #[inline] pub fn is_map(&self) -> bool { @@ -454,4 +479,8 @@ impl Object { _ => None, } } + + pub fn get_string_property(&self, key: &str) -> Option<&Property> { + self.string_properties.get(key) + } } diff --git a/boa/src/syntax/ast/keyword.rs b/boa/src/syntax/ast/keyword.rs index f4d003bcbc7..566d25b3224 100644 --- a/boa/src/syntax/ast/keyword.rs +++ b/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), diff --git a/boa/src/syntax/ast/node/iteration.rs b/boa/src/syntax/ast/node/iteration.rs index 8b2dcdfc8de..38945e64263 100644 --- a/boa/src/syntax/ast/node/iteration.rs +++ b/boa/src/syntax/ast/node/iteration.rs @@ -331,3 +331,56 @@ impl From for Node { Self::Continue(cont) } } + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ForOfLoop { + variable: Box, + iterable: Box, + body: Box, +} + +impl ForOfLoop { + pub fn new(variable: V, iterable: I, body: B) -> Self + where + V: Into, + I: Into, + B: Into, + { + 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(super) 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 for Node { + fn from(for_of: ForOfLoop) -> Node { + Self::ForOfLoop(for_of) + } +} diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 7042451acbc..97e4958a2b1 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/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, @@ -113,6 +113,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), @@ -208,6 +211,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), diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index 8e53392fb73..c74c85a5319 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/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 TokenParser for ForStatement where R: Read, { - type Output = ForLoop; + type Output = Node; fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("ForStatement", "Parsing"); @@ -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()) } } diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index cb3d53e3628..54ede6aa4dc 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -317,6 +317,13 @@ impl Value { matches!(self, Self::Symbol(_)) } + pub fn as_symbol(&self) -> Option { + 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 +405,14 @@ impl Value { matches!(self, Self::Boolean(_)) } + #[inline] + pub fn as_boolean(&self) -> Option { + match self { + Self::Boolean(boolean) => Some(*boolean), + _ => None, + } + } + /// Returns true if the value is a bigint. #[inline] pub fn is_bigint(&self) -> bool { From 5228e56966eae489259aaa4356226f4f43fd0c04 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:19:44 +0100 Subject: [PATCH 16/35] Extend for...of to support var, let, and const lhs --- boa/src/builtins/array/array_iterator.rs | 70 ++++++----- boa/src/exec/declaration/mod.rs | 6 +- boa/src/exec/iteration/mod.rs | 115 ++++++++++++++++-- boa/src/syntax/ast/node/declaration.rs | 14 ++- .../primary/object_initializer/tests.rs | 40 +++--- .../parser/statement/declaration/lexical.rs | 67 +++++++--- .../parser/statement/declaration/mod.rs | 12 +- .../parser/statement/declaration/tests.rs | 12 +- .../statement/iteration/for_statement.rs | 2 +- boa/src/syntax/parser/statement/mod.rs | 2 +- 10 files changed, 252 insertions(+), 88 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index c4a7540d15d..1fa149a1235 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,10 +1,13 @@ -use crate::builtins::function::make_builtin_fn; -use crate::builtins::{Array, Value}; -use crate::object::{ObjectData, PROTOTYPE}; -use crate::property::Property; -use crate::{Context, Result}; +use crate::{ + builtins::{function::make_builtin_fn, Array, Value}, + object::{ObjectData, PROTOTYPE}, + property::Property, + Context, Result, +}; use gc::{Finalize, Trace}; use std::borrow::Borrow; +use crate::object::Object; +use crate::builtins::function::{Function, FunctionFlags, BuiltInFunction}; #[derive(Debug, Clone, Finalize, Trace)] pub enum ArrayIterationKind { @@ -30,13 +33,12 @@ impl ArrayIterator { } pub(crate) fn new_array_iterator( - interpreter: &Context, + ctx: &Context, array: Value, kind: ArrayIterationKind, ) -> Result { let array_iterator = Value::new_object(Some( - &interpreter - .realm() + &ctx.realm() .environment .get_global_object() .expect("Could not get global object"), @@ -46,27 +48,41 @@ impl ArrayIterator { .as_object_mut() .expect("array iterator object") .set_prototype_instance( - interpreter - .realm() + ctx.realm() .environment .get_binding_value("Object") .expect("Object was not initialized") .borrow() .get_field(PROTOTYPE), ); - make_builtin_fn(Self::next, "next", &array_iterator, 0, interpreter); + make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx); + let mut function = Object::function( + Function::BuiltIn(BuiltInFunction(|v, _, _| Ok(v.clone())), FunctionFlags::CALLABLE), + ctx + .global_object() + .get_field("Function") + .get_field("prototype"), + ); + function.insert_field("length", Value::from(0)); + let symbol_iterator = ctx + .get_well_known_symbol("iterator") + .expect("Symbol.iterator not initialised"); + array_iterator.set_field( + symbol_iterator, + Value::from(function), + ); Ok(array_iterator) } - pub(crate) fn next(this: &Value, _args: &[Value], interpreter: &mut Context) -> Result { + pub(crate) fn next(this: &Value, _args: &[Value], ctx: &mut Context) -> Result { 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 == Value::undefined() { + if array_iterator.array.is_undefined() { return Ok(Self::create_iter_result_object( - interpreter, + ctx, Value::undefined(), true, )); @@ -75,12 +91,12 @@ impl ArrayIterator { .array .get_field("length") .as_number() - .ok_or_else(|| interpreter.construct_type_error("Not an array"))? + .ok_or_else(|| ctx.construct_type_error("Not an array"))? as i32; if array_iterator.next_index >= len { array_iterator.array = Value::undefined(); return Ok(Self::create_iter_result_object( - interpreter, + ctx, Value::undefined(), true, )); @@ -88,46 +104,40 @@ impl ArrayIterator { array_iterator.next_index = index + 1; match array_iterator.kind { ArrayIterationKind::Key => Ok(Self::create_iter_result_object( - interpreter, + ctx, Value::integer(index), false, )), ArrayIterationKind::Value => { let element_value = array_iterator.array.get_field(index); - Ok(Self::create_iter_result_object( - interpreter, - element_value, - false, - )) + Ok(Self::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( - &interpreter - .realm() + &ctx.realm() .environment .get_global_object() .expect("Could not get global object"), )), &[Value::integer(index), element_value], - interpreter, + ctx, )?; Ok(result) } } } else { - interpreter.throw_type_error("`this` is not an ArrayIterator") + ctx.throw_type_error("`this` is not an ArrayIterator") } } else { - interpreter.throw_type_error("`this` is not an ArrayIterator") + ctx.throw_type_error("`this` is not an ArrayIterator") } } - fn create_iter_result_object(interpreter: &mut Context, value: Value, done: bool) -> Value { + fn create_iter_result_object(ctx: &mut Context, value: Value, done: bool) -> Value { let object = Value::new_object(Some( - &interpreter - .realm() + &ctx.realm() .environment .get_global_object() .expect("Could not get global object"), diff --git a/boa/src/exec/declaration/mod.rs b/boa/src/exec/declaration/mod.rs index a882ea78aa5..b34f491ca9f 100644 --- a/boa/src/exec/declaration/mod.rs +++ b/boa/src/exec/declaration/mod.rs @@ -81,7 +81,11 @@ impl Executable for VarDeclList { impl Executable for ConstDeclList { fn run(&self, interpreter: &mut Context) -> Result { 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() diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 62431e982fd..00c289d7d89 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -1,6 +1,8 @@ //! Iteration node execution. use super::{Context, Executable, InterpreterState}; +use crate::environment::lexical_environment::VariableScope; +use crate::syntax::ast::Node; use crate::{ environment::lexical_environment::new_declarative_environment, syntax::ast::node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, @@ -175,17 +177,11 @@ impl Executable for ForOfLoop { .get_property( interpreter .get_well_known_symbol("iterator") - .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?, + .ok_or_else(|| interpreter.construct_type_error("Symbol.iterator not initialised"))?, ) .and_then(|mut p| p.value.take()) .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - { - let env = &mut interpreter.realm_mut().environment; - env.push(new_declarative_environment(Some( - env.get_current_environment_ref().clone(), - ))); - } //let variable = self.variable().run(interpreter)?; let next_function = iterator_object .get_property("next") @@ -193,8 +189,13 @@ impl Executable for ForOfLoop { .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; let mut result = Value::undefined(); - self.variable().run(interpreter)?; loop { + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } let next = interpreter.call(&next_function, &iterator_object, &[])?; let done = next .get_property("done") @@ -212,7 +213,101 @@ impl Executable for ForOfLoop { .ok_or_else(|| { interpreter.construct_type_error("Could not find property `value`") })?; - interpreter.set_value(self.variable(), next_result)?; + + 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) => { @@ -238,8 +333,8 @@ impl Executable for ForOfLoop { // Continue execution. } } + let _ = interpreter.realm_mut().environment.pop(); } - let _ = interpreter.realm_mut().environment.pop(); Ok(result) } } diff --git a/boa/src/syntax/ast/node/declaration.rs b/boa/src/syntax/ast/node/declaration.rs index ea73685bd03..04e30f5ac91 100644 --- a/boa/src/syntax/ast/node/declaration.rs +++ b/boa/src/syntax/ast/node/declaration.rs @@ -410,25 +410,29 @@ impl From for Node { #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct ConstDecl { name: Identifier, - init: Node, + init: Option, } 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(name: N, init: I) -> Self + pub(in crate::syntax) fn new(name: N, init: Option) -> Self where N: Into, I: Into, { Self { name: name.into(), - init: init.into(), + init: init.map(|n| n.into()), } } @@ -438,7 +442,7 @@ impl ConstDecl { } /// Gets the initialization node for the variable, if any. - pub fn init(&self) -> &Node { + pub fn init(&self) -> &Option { &self.init } } diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs index 2a58d44cac1..60292bc920c 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/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()], ); } diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs index e816f5eb485..20ad78c98be 100644 --- a/boa/src/syntax/parser/statement/declaration/lexical.rs +++ b/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(allow_in: I, allow_yield: Y, allow_await: A) -> Self + pub(super) fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + const_init_required: bool, + ) -> Self where I: Into, Y: Into, @@ -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(allow_in: I, allow_yield: Y, allow_await: A, is_const: bool) -> Self + fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + is_const: bool, + const_init_required: bool, + ) -> Self where I: Into, Y: Into, @@ -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) => { diff --git a/boa/src/syntax/parser/statement/declaration/mod.rs b/boa/src/syntax/parser/statement/declaration/mod.rs index cba21cdc027..d1c1c9dd985 100644 --- a/boa/src/syntax/parser/statement/declaration/mod.rs +++ b/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(allow_yield: Y, allow_await: A) -> Self + pub(super) fn new(allow_yield: Y, allow_await: A, const_init_required: bool) -> Self where Y: Into, A: Into, @@ -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), } diff --git a/boa/src/syntax/parser/statement/declaration/tests.rs b/boa/src/syntax/parser/statement/declaration/tests.rs index fbc2f646858..5ed9bd2d146 100644 --- a/boa/src/syntax/parser/statement/declaration/tests.rs +++ b/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()], ); diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index c74c85a5319..0c388c8853e 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/for_statement.rs @@ -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)?), diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 08b419f4d11..2b8eaf9802d 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -373,7 +373,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) From b49e03f7cac6ae4ce3334f43ea6a16c9eca02fef Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:24:30 +0100 Subject: [PATCH 17/35] Use cached well known symbols --- boa/src/builtins/array/array_iterator.rs | 21 +++++++++------------ boa/src/builtins/array/mod.rs | 4 +--- boa/src/exec/iteration/mod.rs | 7 +------ 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 1fa149a1235..bd29b7f988a 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,3 +1,5 @@ +use crate::builtins::function::{BuiltInFunction, Function, FunctionFlags}; +use crate::object::Object; use crate::{ builtins::{function::make_builtin_fn, Array, Value}, object::{ObjectData, PROTOTYPE}, @@ -6,8 +8,6 @@ use crate::{ }; use gc::{Finalize, Trace}; use std::borrow::Borrow; -use crate::object::Object; -use crate::builtins::function::{Function, FunctionFlags, BuiltInFunction}; #[derive(Debug, Clone, Finalize, Trace)] pub enum ArrayIterationKind { @@ -57,21 +57,18 @@ impl ArrayIterator { ); make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx); let mut function = Object::function( - Function::BuiltIn(BuiltInFunction(|v, _, _| Ok(v.clone())), FunctionFlags::CALLABLE), - ctx - .global_object() + Function::BuiltIn( + BuiltInFunction(|v, _, _| Ok(v.clone())), + FunctionFlags::CALLABLE, + ), + ctx.global_object() .get_field("Function") .get_field("prototype"), ); function.insert_field("length", Value::from(0)); - let symbol_iterator = ctx - .get_well_known_symbol("iterator") - .expect("Symbol.iterator not initialised"); - array_iterator.set_field( - symbol_iterator, - Value::from(function), - ); + let symbol_iterator = ctx.well_known_symbols().iterator_symbol(); + array_iterator.set_field(symbol_iterator, Value::from(function)); Ok(array_iterator) } diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 682ffdcdd11..a2b12a50692 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -1149,9 +1149,7 @@ impl Array { ); make_builtin_fn(Self::values, "values", &prototype, 0, interpreter); - let symbol_iterator = interpreter - .get_well_known_symbol("iterator") - .expect("Symbol.iterator not initialised"); + let symbol_iterator = interpreter.well_known_symbols().iterator_symbol(); prototype.set_property( symbol_iterator, Property::default().value(prototype.get_field("values")), diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 00c289d7d89..2259ae4579f 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -174,15 +174,10 @@ impl Executable for ForOfLoop { let _timer = BoaProfiler::global().start_event("ForOf", "exec"); let iterable = self.iterable().run(interpreter)?; let iterator_function = iterable - .get_property( - interpreter - .get_well_known_symbol("iterator") - .ok_or_else(|| interpreter.construct_type_error("Symbol.iterator not initialised"))?, - ) + .get_property(interpreter.well_known_symbols().iterator_symbol()) .and_then(|mut p| p.value.take()) .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - //let variable = self.variable().run(interpreter)?; let next_function = iterator_object .get_property("next") .and_then(|mut p| p.value.take()) From 1db94ab979c21e1b2209dd8e5613e844f82371f0 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:33:19 +0100 Subject: [PATCH 18/35] Nest use statements --- boa/src/builtins/array/array_iterator.rs | 9 +++++---- boa/src/builtins/array/mod.rs | 2 +- boa/src/exec/iteration/mod.rs | 9 +++++---- boa/src/syntax/parser/statement/mod.rs | 8 +++++--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index bd29b7f988a..bd4a7a05ee3 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,8 +1,9 @@ -use crate::builtins::function::{BuiltInFunction, Function, FunctionFlags}; -use crate::object::Object; use crate::{ - builtins::{function::make_builtin_fn, Array, Value}, - object::{ObjectData, PROTOTYPE}, + builtins::{ + function::{make_builtin_fn, BuiltInFunction, Function, FunctionFlags}, + Array, Value, + }, + object::{Object, ObjectData, PROTOTYPE}, property::Property, Context, Result, }; diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index a2b12a50692..4919bde0a21 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -14,8 +14,8 @@ pub mod array_iterator; mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; -use crate::builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator}; use crate::{ + builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator}, object::{ObjectData, PROTOTYPE}, property::{Attribute, Property}, value::{same_value_zero, Value}, diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 2259ae4579f..4d557fbba6c 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -1,11 +1,12 @@ //! Iteration node execution. use super::{Context, Executable, InterpreterState}; -use crate::environment::lexical_environment::VariableScope; -use crate::syntax::ast::Node; use crate::{ - environment::lexical_environment::new_declarative_environment, - syntax::ast::node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, + environment::lexical_environment::{new_declarative_environment, VariableScope}, + syntax::ast::{ + node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, + Node, + }, BoaProfiler, Result, Value, }; diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 2b8eaf9802d..33bdf20fcad 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -38,10 +38,12 @@ use self::{ use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}; -use crate::syntax::lexer::{InputElement, TokenKind}; use crate::{ - syntax::ast::{node, Keyword, Node, Punctuator}, - BoaProfiler, + syntax::{ + lexer::{InputElement, TokenKind}, + ast::{node, Keyword, Node, Punctuator} + }, + BoaProfiler }; use labelled_stm::LabelledStatement; From 3737ca75d2a0fc94aef98a3f978288d79c2403d2 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:40:39 +0100 Subject: [PATCH 19/35] Nest use statements --- boa/src/object/mod.rs | 6 ++++-- boa/src/syntax/parser/statement/mod.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 48e647b8825..ccfe441f520 100644 --- a/boa/src/object/mod.rs +++ b/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, + BigInt, Date, RegExp, + }, property::{Property, PropertyKey}, value::{RcBigInt, RcString, RcSymbol, Value}, BoaProfiler, @@ -15,7 +18,6 @@ mod gcobject; mod internal_methods; mod iter; -use crate::builtins::array::array_iterator::ArrayIterator; pub use gcobject::{GcObject, Ref, RefMut}; pub use iter::*; diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 33bdf20fcad..f01242ec51e 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -43,7 +43,7 @@ use crate::{ lexer::{InputElement, TokenKind}, ast::{node, Keyword, Node, Punctuator} }, - BoaProfiler + BoaProfiler, }; use labelled_stm::LabelledStatement; From f1d36390cefd5a1b50d7d38442e54b00610eff44 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 23:44:44 +0100 Subject: [PATCH 20/35] Add tests. --- boa/src/exec/iteration/mod.rs | 4 +- boa/src/exec/iteration/tests.rs | 129 +++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 4 deletions(-) diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 4d557fbba6c..8a87b187e4a 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -322,9 +322,7 @@ impl Executable for ForOfLoop { .set_current_state(InterpreterState::Executing); // after breaking out of the block, continue execution of the loop } - InterpreterState::Return => { - return interpreter.throw_syntax_error("return not in function") - } + InterpreterState::Return => return Ok(result), InterpreterState::Executing => { // Continue execution. } diff --git a/boa/src/exec/iteration/tests.rs b/boa/src/exec/iteration/tests.rs index 571fe1defef..7340eb3ce42 100644 --- a/boa/src/exec/iteration/tests.rs +++ b/boa/src/exec/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#" From a6e0dd2278f020d50ed4845d1a76158200082444 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Fri, 25 Sep 2020 19:32:52 +0100 Subject: [PATCH 21/35] Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use. --- boa/src/builtins/array/array_iterator.rs | 120 +++++++++---------- boa/src/builtins/array/mod.rs | 50 +++++++- boa/src/builtins/array/tests.rs | 132 +++++++++++++++++++++ boa/src/builtins/iterable/mod.rs | 141 +++++++++++++++++++++++ boa/src/builtins/mod.rs | 3 +- boa/src/context.rs | 11 +- boa/src/exec/iteration/mod.rs | 29 +---- 7 files changed, 399 insertions(+), 87 deletions(-) create mode 100644 boa/src/builtins/iterable/mod.rs diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index bd4a7a05ee3..5fee1c23a59 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,14 +1,10 @@ use crate::{ - builtins::{ - function::{make_builtin_fn, BuiltInFunction, Function, FunctionFlags}, - Array, Value, - }, - object::{Object, ObjectData, PROTOTYPE}, - property::Property, - Context, Result, + builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, Value}, + object::ObjectData, + property::{Attribute, Property}, + BoaProfiler, Context, Result, }; use gc::{Finalize, Trace}; -use std::borrow::Borrow; #[derive(Debug, Clone, Finalize, Trace)] pub enum ArrayIterationKind { @@ -17,6 +13,12 @@ pub enum ArrayIterationKind { 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, @@ -25,6 +27,8 @@ pub struct ArrayIterator { } impl ArrayIterator { + pub(crate) const NAME: &'static str = "ArrayIterator"; + fn new(array: Value, kind: ArrayIterationKind) -> Self { ArrayIterator { array, @@ -33,7 +37,15 @@ impl ArrayIterator { } } - pub(crate) fn new_array_iterator( + /// 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, @@ -48,42 +60,25 @@ impl ArrayIterator { array_iterator .as_object_mut() .expect("array iterator object") - .set_prototype_instance( - ctx.realm() - .environment - .get_binding_value("Object") - .expect("Object was not initialized") - .borrow() - .get_field(PROTOTYPE), - ); - make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx); - let mut function = Object::function( - Function::BuiltIn( - BuiltInFunction(|v, _, _| Ok(v.clone())), - FunctionFlags::CALLABLE, - ), - ctx.global_object() - .get_field("Function") - .get_field("prototype"), - ); - function.insert_field("length", Value::from(0)); - - let symbol_iterator = ctx.well_known_symbols().iterator_symbol(); - array_iterator.set_field(symbol_iterator, Value::from(function)); + .set_prototype_instance(ctx.iterator_prototypes().array_iterator()); 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 { 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(Self::create_iter_result_object( - ctx, - Value::undefined(), - true, - )); + return Ok(create_iter_result_object(ctx, Value::undefined(), true)); } let len = array_iterator .array @@ -93,22 +88,16 @@ impl ArrayIterator { as i32; if array_iterator.next_index >= len { array_iterator.array = Value::undefined(); - return Ok(Self::create_iter_result_object( - ctx, - Value::undefined(), - true, - )); + return Ok(create_iter_result_object(ctx, Value::undefined(), true)); } array_iterator.next_index = index + 1; match array_iterator.kind { - ArrayIterationKind::Key => Ok(Self::create_iter_result_object( - ctx, - Value::integer(index), - false, - )), + ArrayIterationKind::Key => { + Ok(create_iter_result_object(ctx, Value::integer(index), false)) + } ArrayIterationKind::Value => { let element_value = array_iterator.array.get_field(index); - Ok(Self::create_iter_result_object(ctx, element_value, false)) + Ok(create_iter_result_object(ctx, element_value, false)) } ArrayIterationKind::KeyAndValue => { let element_value = array_iterator.array.get_field(index); @@ -122,7 +111,7 @@ impl ArrayIterator { &[Value::integer(index), element_value], ctx, )?; - Ok(result) + Ok(create_iter_result_object(ctx, result, false)) } } } else { @@ -133,17 +122,28 @@ impl ArrayIterator { } } - fn create_iter_result_object(ctx: &mut Context, value: Value, done: bool) -> Value { - let object = Value::new_object(Some( - &ctx.realm() - .environment - .get_global_object() - .expect("Could not get 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 + /// 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 } } diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 4919bde0a21..2d25b0270d3 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -1093,12 +1093,58 @@ 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 { - ArrayIterator::new_array_iterator(interpreter, this.clone(), ArrayIterationKind::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 { + 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 { + ArrayIterator::create_array_iterator( + interpreter, + this.clone(), + ArrayIterationKind::KeyAndValue, + ) } /// Initialise the `Array` object on the global object. @@ -1148,6 +1194,8 @@ impl Array { 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( diff --git a/boa/src/builtins/array/tests.rs b/boa/src/builtins/array/tests.rs index fa066ad979e..876de0e8537 100644 --- a/boa/src/builtins/array/tests.rs +++ b/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"); +} diff --git a/boa/src/builtins/iterable/mod.rs b/boa/src/builtins/iterable/mod.rs new file mode 100644 index 00000000000..00615185a0e --- /dev/null +++ b/boa/src/builtins/iterable/mod.rs @@ -0,0 +1,141 @@ +use crate::builtins::function::{BuiltInFunction, Function, FunctionFlags}; +use crate::builtins::ArrayIterator; +use crate::object::{Object, PROTOTYPE}; +use crate::BoaProfiler; +use crate::{property::Property, Context, Value}; + +#[derive(Debug, Default)] +pub struct IteratorPrototypes { + iterator_prototype: Value, + array_iterator: Value, +} + +impl IteratorPrototypes { + pub fn init(ctx: &mut Context) -> Self { + let iterator_prototype = create_iterator_prototype(ctx); + Self { + iterator_prototype: iterator_prototype.clone(), + array_iterator: ArrayIterator::create_prototype(ctx, iterator_prototype), + } + } + + pub fn array_iterator(&self) -> Value { + self.array_iterator.clone() + } + + pub fn iterator_prototype(&self) -> Value { + self.iterator_prototype.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.realm() + .environment + .get_global_object() + .expect("Could not get 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 { + 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 { + 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 + } +} diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 521173c6fb8..da592c010e4 100644 --- a/boa/src/builtins/mod.rs +++ b/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, diff --git a/boa/src/context.rs b/boa/src/context.rs index cb2b6abf9f6..24328e83346 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -1,5 +1,6 @@ //! Javascript context. +use crate::builtins::iterable::IteratorPrototypes; use crate::{ builtins::{ self, @@ -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 + } } diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 8a87b187e4a..d80a559ff42 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -2,6 +2,7 @@ use super::{Context, Executable, InterpreterState}; use crate::{ + builtins::iterable::get_iterator, environment::lexical_environment::{new_declarative_environment, VariableScope}, syntax::ast::{ node::{DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, @@ -174,15 +175,7 @@ impl Executable for ForOfLoop { fn run(&self, interpreter: &mut Context) -> Result { let _timer = BoaProfiler::global().start_event("ForOf", "exec"); let iterable = self.iterable().run(interpreter)?; - let iterator_function = iterable - .get_property(interpreter.well_known_symbols().iterator_symbol()) - .and_then(|mut p| p.value.take()) - .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; - let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - let next_function = iterator_object - .get_property("next") - .and_then(|mut p| p.value.take()) - .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; + let iterator = get_iterator(interpreter, iterable)?; let mut result = Value::undefined(); loop { @@ -192,23 +185,11 @@ impl Executable for ForOfLoop { env.get_current_environment_ref().clone(), ))); } - let next = interpreter.call(&next_function, &iterator_object, &[])?; - let done = next - .get_property("done") - .and_then(|mut p| p.value.take()) - .and_then(|v| v.as_boolean()) - .ok_or_else(|| { - interpreter.construct_type_error("Could not find property `done`") - })?; - if done { + let iterator_result = iterator.next(interpreter)?; + if iterator_result.is_done() { break; } - let next_result = next - .get_property("value") - .and_then(|mut p| p.value.take()) - .ok_or_else(|| { - interpreter.construct_type_error("Could not find property `value`") - })?; + let next_result = iterator_result.value(); match self.variable() { Node::Identifier(ref name) => { From 6287bb28272e906464518f5821b88b278d56d616 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Thu, 17 Sep 2020 23:59:57 +0100 Subject: [PATCH 22/35] Initial implementation of for...of loop --- boa/src/builtins/array/mod.rs | 4 +- boa/src/exec/iteration/mod.rs | 77 +++++++++++++++++++++++++++++++++++ boa/src/object/mod.rs | 1 + 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 2d25b0270d3..4b3e40c453e 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -962,7 +962,7 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduce /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce pub(crate) fn reduce(this: &Value, args: &[Value], interpreter: &mut Context) -> Result { - let this: Value = this.to_object(interpreter)?.into(); + let this = this.to_object(interpreter)?; let callback = match args.get(0) { Some(value) if value.is_function() => value, _ => return interpreter.throw_type_error("Reduce was called without a callback"), @@ -1024,7 +1024,7 @@ impl Array { args: &[Value], interpreter: &mut Context, ) -> Result { - let this: Value = this.to_object(interpreter)?.into(); + let this = this.to_object(interpreter)?; let callback = match args.get(0) { Some(value) if value.is_function() => value, _ => return interpreter.throw_type_error("reduceRight was called without a callback"), diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index d80a559ff42..8aca28faf9a 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -313,3 +313,80 @@ impl Executable for ForOfLoop { Ok(result) } } + +impl Executable for ForOfLoop { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("ForOf", "exec"); + let iterable = self.iterable().run(interpreter)?; + let iterator_function = iterable + .get_property( + interpreter + .get_well_known_symbol("iterator") + .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?, + ) + .and_then(|mut p| p.value.take()) + .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; + let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } + //let variable = self.variable().run(interpreter)?; + let next_function = iterator_object + .get_property("next") + .and_then(|mut p| p.value.take()) + .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; + let mut result = Value::undefined(); + + self.variable().run(interpreter)?; + loop { + let next = interpreter.call(&next_function, &iterator_object, &[])?; + let done = next + .get_property("done") + .and_then(|mut p| p.value.take()) + .and_then(|v| v.as_boolean()) + .ok_or_else(|| { + interpreter.construct_type_error("Could not find property `done`") + })?; + if done { + break; + } + let next_result = next + .get_property("value") + .and_then(|mut p| p.value.take()) + .ok_or_else(|| { + interpreter.construct_type_error("Could not find property `value`") + })?; + interpreter.set_value(self.variable(), next_result)?; + 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 interpreter.throw_syntax_error("return not in function") + } + InterpreterState::Executing => { + // Continue execution. + } + } + } + let _ = interpreter.realm_mut().environment.pop(); + Ok(result) + } +} diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index ccfe441f520..84c920a1b6d 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -18,6 +18,7 @@ mod gcobject; mod internal_methods; mod iter; +use crate::builtins::array::array_iterator::ArrayIterator; pub use gcobject::{GcObject, Ref, RefMut}; pub use iter::*; From 2ca4003d1e20e1085229433b5013e5b22e53c9de Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:19:44 +0100 Subject: [PATCH 23/35] Extend for...of to support var, let, and const lhs --- boa/src/exec/iteration/mod.rs | 115 +++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 10 deletions(-) diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 8aca28faf9a..1060f697fb7 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -1,6 +1,8 @@ //! Iteration node execution. use super::{Context, Executable, InterpreterState}; +use crate::environment::lexical_environment::VariableScope; +use crate::syntax::ast::Node; use crate::{ builtins::iterable::get_iterator, environment::lexical_environment::{new_declarative_environment, VariableScope}, @@ -322,17 +324,11 @@ impl Executable for ForOfLoop { .get_property( interpreter .get_well_known_symbol("iterator") - .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?, + .ok_or_else(|| interpreter.construct_type_error("Symbol.iterator not initialised"))?, ) .and_then(|mut p| p.value.take()) .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - { - let env = &mut interpreter.realm_mut().environment; - env.push(new_declarative_environment(Some( - env.get_current_environment_ref().clone(), - ))); - } //let variable = self.variable().run(interpreter)?; let next_function = iterator_object .get_property("next") @@ -340,8 +336,13 @@ impl Executable for ForOfLoop { .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; let mut result = Value::undefined(); - self.variable().run(interpreter)?; loop { + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } let next = interpreter.call(&next_function, &iterator_object, &[])?; let done = next .get_property("done") @@ -359,7 +360,101 @@ impl Executable for ForOfLoop { .ok_or_else(|| { interpreter.construct_type_error("Could not find property `value`") })?; - interpreter.set_value(self.variable(), next_result)?; + + 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) => { @@ -385,8 +480,8 @@ impl Executable for ForOfLoop { // Continue execution. } } + let _ = interpreter.realm_mut().environment.pop(); } - let _ = interpreter.realm_mut().environment.pop(); Ok(result) } } From 42a074198466f7188cc2f4cbaafcd921fe5eafc6 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:24:30 +0100 Subject: [PATCH 24/35] Use cached well known symbols --- boa/src/exec/iteration/mod.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 1060f697fb7..625c820b8ef 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -321,15 +321,10 @@ impl Executable for ForOfLoop { let _timer = BoaProfiler::global().start_event("ForOf", "exec"); let iterable = self.iterable().run(interpreter)?; let iterator_function = iterable - .get_property( - interpreter - .get_well_known_symbol("iterator") - .ok_or_else(|| interpreter.construct_type_error("Symbol.iterator not initialised"))?, - ) + .get_property(interpreter.well_known_symbols().iterator_symbol()) .and_then(|mut p| p.value.take()) .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - //let variable = self.variable().run(interpreter)?; let next_function = iterator_object .get_property("next") .and_then(|mut p| p.value.take()) From 57a67b26ce1aff5f22c8bc6debdc809b331d0254 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Thu, 17 Sep 2020 23:59:57 +0100 Subject: [PATCH 25/35] Initial implementation of for...of loop --- boa/src/exec/iteration/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 625c820b8ef..57d3305bfea 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -1,8 +1,6 @@ //! Iteration node execution. use super::{Context, Executable, InterpreterState}; -use crate::environment::lexical_environment::VariableScope; -use crate::syntax::ast::Node; use crate::{ builtins::iterable::get_iterator, environment::lexical_environment::{new_declarative_environment, VariableScope}, @@ -469,7 +467,7 @@ impl Executable for ForOfLoop { // after breaking out of the block, continue execution of the loop } InterpreterState::Return => { - return interpreter.throw_syntax_error("return not in function") + return Ok(result); } InterpreterState::Executing => { // Continue execution. From a451f4ba515551e2555c89a46c17c22a64c91ea3 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Wed, 23 Sep 2020 22:40:39 +0100 Subject: [PATCH 26/35] Nest use statements --- boa/src/object/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 84c920a1b6d..ccfe441f520 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -18,7 +18,6 @@ mod gcobject; mod internal_methods; mod iter; -use crate::builtins::array::array_iterator::ArrayIterator; pub use gcobject::{GcObject, Ref, RefMut}; pub use iter::*; From 8f80692d60e422d7fc81dd6157cc241dae674724 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Tue, 29 Sep 2020 17:41:30 +0100 Subject: [PATCH 27/35] Add string iterator --- boa/src/builtins/array/array_iterator.rs | 9 +- boa/src/builtins/iterable/mod.rs | 40 ++++++--- boa/src/builtins/string/mod.rs | 67 +++++++++++++-- boa/src/builtins/string/string_iterator.rs | 94 ++++++++++++++++++++ boa/src/builtins/string/tests.rs | 99 ++++++++++++++++++++++ boa/src/object/gcobject.rs | 2 +- boa/src/object/mod.rs | 16 ++-- boa/src/value/mod.rs | 10 ++- 8 files changed, 303 insertions(+), 34 deletions(-) create mode 100644 boa/src/builtins/string/string_iterator.rs diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 5fee1c23a59..62ac7e9ad8c 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -60,7 +60,7 @@ impl ArrayIterator { array_iterator .as_object_mut() .expect("array iterator object") - .set_prototype_instance(ctx.iterator_prototypes().array_iterator()); + .set_prototype_instance(ctx.iterator_prototypes().array_iterator().into()); Ok(array_iterator) } @@ -102,12 +102,7 @@ impl ArrayIterator { ArrayIterationKind::KeyAndValue => { let element_value = array_iterator.array.get_field(index); let result = Array::make_array( - &Value::new_object(Some( - &ctx.realm() - .environment - .get_global_object() - .expect("Could not get global object"), - )), + &Value::new_object(Some(ctx.global_object())), &[Value::integer(index), element_value], ctx, )?; diff --git a/boa/src/builtins/iterable/mod.rs b/boa/src/builtins/iterable/mod.rs index 00615185a0e..45b57dd655c 100644 --- a/boa/src/builtins/iterable/mod.rs +++ b/boa/src/builtins/iterable/mod.rs @@ -1,31 +1,49 @@ -use crate::builtins::function::{BuiltInFunction, Function, FunctionFlags}; -use crate::builtins::ArrayIterator; -use crate::object::{Object, PROTOTYPE}; -use crate::BoaProfiler; -use crate::{property::Property, Context, Value}; +use crate::builtins::string::string_iterator::StringIterator; +use crate::object::GcObject; +use crate::{ + builtins::{ + function::{BuiltInFunction, Function, FunctionFlags}, + ArrayIterator, + }, + object::{Object, PROTOTYPE}, + property::Property, + BoaProfiler, Context, Value, +}; #[derive(Debug, Default)] pub struct IteratorPrototypes { - iterator_prototype: Value, - array_iterator: Value, + 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.clone(), - array_iterator: ArrayIterator::create_prototype(ctx, iterator_prototype), + 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) -> Value { + pub fn array_iterator(&self) -> GcObject { self.array_iterator.clone() } - pub fn iterator_prototype(&self) -> Value { + pub fn iterator_prototype(&self) -> GcObject { self.iterator_prototype.clone() } + + pub fn string_iterator(&self) -> GcObject { + self.string_iterator.clone() + } } /// CreateIterResultObject( value, done ) diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index b69487e23f2..413d9085fdf 100644 --- a/boa/src/builtins/string/mod.rs +++ b/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)); @@ -923,7 +957,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 @@ -941,12 +975,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 = 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] )` @@ -1063,6 +1099,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 { + 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) { @@ -1119,6 +1159,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, diff --git a/boa/src/builtins/string/string_iterator.rs b/boa/src/builtins/string/string_iterator.rs new file mode 100644 index 00000000000..8d7e8fd7b1a --- /dev/null +++ b/boa/src/builtins/string/string_iterator.rs @@ -0,0 +1,94 @@ +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 { + let string_iterator = Value::new_object(Some( + &ctx.realm() + .environment + .get_global_object() + .expect("Could not get 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 { + 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 + } +} diff --git a/boa/src/builtins/string/tests.rs b/boa/src/builtins/string/tests.rs index 5778e37e097..3bc8ea19511 100644 --- a/boa/src/builtins/string/tests.rs +++ b/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"); +} diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index aa6e262b2cc..546aed91a68 100644 --- a/boa/src/object/gcobject.rs +++ b/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>); // This is needed for the call method since we cannot mutate the function itself since we diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index ccfe441f520..3776f675132 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -3,7 +3,7 @@ use crate::{ builtins::{ array::array_iterator::ArrayIterator, function::Function, map::ordered_map::OrderedMap, - BigInt, Date, RegExp, + string::string_iterator::StringIterator, BigInt, Date, RegExp, }, property::{Property, PropertyKey}, value::{RcBigInt, RcString, RcSymbol, Value}, @@ -72,6 +72,7 @@ pub enum ObjectData { Boolean(bool), Function(Function), String(RcString), + StringIterator(StringIterator), Number(f64), Symbol(RcSymbol), Error, @@ -93,6 +94,7 @@ impl Display for ObjectData { Self::RegExp(_) => "RegExp", Self::Map(_) => "Map", Self::String(_) => "String", + Self::StringIterator(_) => "StringIterator", Self::Symbol(_) => "Symbol", Self::Error => "Error", Self::Ordinary => "Ordinary", @@ -279,6 +281,14 @@ impl Object { } } + #[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 { @@ -481,8 +491,4 @@ impl Object { _ => None, } } - - pub fn get_string_property(&self, key: &str) -> Option<&Property> { - self.string_properties.get(key) - } } diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index 54ede6aa4dc..992ada73935 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -303,6 +303,14 @@ impl Value { } } + #[inline] + pub fn as_gc_object(&self) -> Option { + match self { + Self::Object(o) => Some(o.clone()), + _ => None, + } + } + #[inline] pub fn as_object_mut(&self) -> Option> { match *self { @@ -931,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 { From 384c8246db4cdd021f020ef2499f63d0a214a29b Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Tue, 29 Sep 2020 18:05:46 +0100 Subject: [PATCH 28/35] Clean up merge --- boa/src/builtins/array/mod.rs | 4 +- boa/src/exec/iteration/mod.rs | 165 ------------------------- boa/src/syntax/parser/statement/mod.rs | 2 +- 3 files changed, 3 insertions(+), 168 deletions(-) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 4b3e40c453e..2d25b0270d3 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -962,7 +962,7 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduce /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce pub(crate) fn reduce(this: &Value, args: &[Value], interpreter: &mut Context) -> Result { - let this = this.to_object(interpreter)?; + let this: Value = this.to_object(interpreter)?.into(); let callback = match args.get(0) { Some(value) if value.is_function() => value, _ => return interpreter.throw_type_error("Reduce was called without a callback"), @@ -1024,7 +1024,7 @@ impl Array { args: &[Value], interpreter: &mut Context, ) -> Result { - let this = this.to_object(interpreter)?; + let this: Value = this.to_object(interpreter)?.into(); let callback = match args.get(0) { Some(value) if value.is_function() => value, _ => return interpreter.throw_type_error("reduceRight was called without a callback"), diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index 57d3305bfea..d80a559ff42 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -313,168 +313,3 @@ impl Executable for ForOfLoop { Ok(result) } } - -impl Executable for ForOfLoop { - fn run(&self, interpreter: &mut Context) -> Result { - let _timer = BoaProfiler::global().start_event("ForOf", "exec"); - let iterable = self.iterable().run(interpreter)?; - let iterator_function = iterable - .get_property(interpreter.well_known_symbols().iterator_symbol()) - .and_then(|mut p| p.value.take()) - .ok_or_else(|| interpreter.construct_type_error("Not an iterable"))?; - let iterator_object = interpreter.call(&iterator_function, &iterable, &[])?; - let next_function = iterator_object - .get_property("next") - .and_then(|mut p| p.value.take()) - .ok_or_else(|| interpreter.construct_type_error("Could not find property `next`"))?; - 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 next = interpreter.call(&next_function, &iterator_object, &[])?; - let done = next - .get_property("done") - .and_then(|mut p| p.value.take()) - .and_then(|v| v.as_boolean()) - .ok_or_else(|| { - interpreter.construct_type_error("Could not find property `done`") - })?; - if done { - break; - } - let next_result = next - .get_property("value") - .and_then(|mut p| p.value.take()) - .ok_or_else(|| { - interpreter.construct_type_error("Could not find property `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) - } -} diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index f01242ec51e..35b0d7c66fc 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -40,8 +40,8 @@ use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser use crate::{ syntax::{ + ast::{node, Keyword, Node, Punctuator}, lexer::{InputElement, TokenKind}, - ast::{node, Keyword, Node, Punctuator} }, BoaProfiler, }; From 29e1deee4054549abe7c1bb32bd66d3dadf90398 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Fri, 2 Oct 2020 21:26:30 +0100 Subject: [PATCH 29/35] Use ctx.global_iterator() --- boa/src/builtins/array/array_iterator.rs | 7 +------ boa/src/builtins/iterable/mod.rs | 7 +------ boa/src/builtins/string/string_iterator.rs | 7 +------ 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 62ac7e9ad8c..cb807d3721d 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -50,12 +50,7 @@ impl ArrayIterator { array: Value, kind: ArrayIterationKind, ) -> Result { - let array_iterator = Value::new_object(Some( - &ctx.realm() - .environment - .get_global_object() - .expect("Could not get global object"), - )); + 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() diff --git a/boa/src/builtins/iterable/mod.rs b/boa/src/builtins/iterable/mod.rs index 45b57dd655c..f4dd4bbf5fa 100644 --- a/boa/src/builtins/iterable/mod.rs +++ b/boa/src/builtins/iterable/mod.rs @@ -50,12 +50,7 @@ impl IteratorPrototypes { /// /// 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.realm() - .environment - .get_global_object() - .expect("Could not get global object"), - )); + 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); diff --git a/boa/src/builtins/string/string_iterator.rs b/boa/src/builtins/string/string_iterator.rs index 8d7e8fd7b1a..c75f6b679ce 100644 --- a/boa/src/builtins/string/string_iterator.rs +++ b/boa/src/builtins/string/string_iterator.rs @@ -22,12 +22,7 @@ impl StringIterator { } pub fn create_string_iterator(ctx: &mut Context, string: Value) -> Result { - let string_iterator = Value::new_object(Some( - &ctx.realm() - .environment - .get_global_object() - .expect("Could not get global object"), - )); + let string_iterator = Value::new_object(Some(ctx.global_object())); string_iterator.set_data(ObjectData::StringIterator(Self::new(string))); string_iterator .as_object_mut() From 8e91a22b8cd4d1fdcf9a653df3b8cb629608aab6 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Fri, 2 Oct 2020 21:51:21 +0100 Subject: [PATCH 30/35] Merge upstream --- .../node/declaration/const_decl_list/mod.rs | 20 +- .../ast/node/iteration/for_of_loop/mod.rs | 208 ++++++++++++++++++ boa/src/syntax/ast/node/iteration/mod.rs | 4 +- boa/src/syntax/ast/node/mod.rs | 1 + 4 files changed, 226 insertions(+), 7 deletions(-) create mode 100644 boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs diff --git a/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs b/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs index 40d95ef2308..103d4de69ed 100644 --- a/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs +++ b/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 { 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 for Node { #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct ConstDecl { name: Identifier, - init: Node, + init: Option, } 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(name: N, init: I) -> Self + pub(in crate::syntax) fn new(name: N, init: Option) -> Self where N: Into, I: Into, { 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 { &self.init } } diff --git a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs new file mode 100644 index 00000000000..f076786bae0 --- /dev/null +++ b/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, + iterable: Box, + body: Box, +} + +impl ForOfLoop { + pub fn new(variable: V, iterable: I, body: B) -> Self + where + V: Into, + I: Into, + B: Into, + { + 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 for Node { + fn from(for_of: ForOfLoop) -> Node { + Self::ForOfLoop(for_of) + } +} + +impl Executable for ForOfLoop { + fn run(&self, interpreter: &mut Context) -> Result { + 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) + } +} diff --git a/boa/src/syntax/ast/node/iteration/mod.rs b/boa/src/syntax/ast/node/iteration/mod.rs index 73a227bb8f5..7fa4f8afb64 100644 --- a/boa/src/syntax/ast/node/iteration/mod.rs +++ b/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; diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 14b0e1efb75..5771c485fdc 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -267,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), From 0abe3c7dbfcdd5814ba3d99d0a807e26ec88a977 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Fri, 2 Oct 2020 22:02:00 +0100 Subject: [PATCH 31/35] Use u32 as array next index --- boa/src/builtins/array/array_iterator.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index cb807d3721d..b16f175d78e 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -22,7 +22,7 @@ pub enum ArrayIterationKind { #[derive(Debug, Clone, Finalize, Trace)] pub struct ArrayIterator { array: Value, - next_index: i32, + next_index: u32, kind: ArrayIterationKind, } @@ -80,7 +80,7 @@ impl ArrayIterator { .get_field("length") .as_number() .ok_or_else(|| ctx.construct_type_error("Not an array"))? - as i32; + as u32; if array_iterator.next_index >= len { array_iterator.array = Value::undefined(); return Ok(create_iter_result_object(ctx, Value::undefined(), true)); @@ -88,7 +88,7 @@ impl ArrayIterator { array_iterator.next_index = index + 1; match array_iterator.kind { ArrayIterationKind::Key => { - Ok(create_iter_result_object(ctx, Value::integer(index), false)) + Ok(create_iter_result_object(ctx, Value::number(index), false)) } ArrayIterationKind::Value => { let element_value = array_iterator.array.get_field(index); @@ -98,7 +98,7 @@ impl ArrayIterator { let element_value = array_iterator.array.get_field(index); let result = Array::make_array( &Value::new_object(Some(ctx.global_object())), - &[Value::integer(index), element_value], + &[Value::number(index), element_value], ctx, )?; Ok(create_iter_result_object(ctx, result, false)) From 9b43518c8330cb54499823e640a910979219eba8 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Fri, 2 Oct 2020 22:04:00 +0100 Subject: [PATCH 32/35] Use into --- boa/src/builtins/array/array_iterator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index b16f175d78e..29beb9ad60d 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -88,7 +88,7 @@ impl ArrayIterator { array_iterator.next_index = index + 1; match array_iterator.kind { ArrayIterationKind::Key => { - Ok(create_iter_result_object(ctx, Value::number(index), false)) + Ok(create_iter_result_object(ctx, index.into(), false)) } ArrayIterationKind::Value => { let element_value = array_iterator.array.get_field(index); @@ -98,7 +98,7 @@ impl ArrayIterator { let element_value = array_iterator.array.get_field(index); let result = Array::make_array( &Value::new_object(Some(ctx.global_object())), - &[Value::number(index), element_value], + &[index.into(), element_value], ctx, )?; Ok(create_iter_result_object(ctx, result, false)) From 3a5d75f20309d80f7ca137f2be51ee3d595097ef Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Fri, 2 Oct 2020 23:15:28 +0100 Subject: [PATCH 33/35] Use boa::Result --- boa/src/builtins/iterable/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/boa/src/builtins/iterable/mod.rs b/boa/src/builtins/iterable/mod.rs index f4dd4bbf5fa..881f223ef50 100644 --- a/boa/src/builtins/iterable/mod.rs +++ b/boa/src/builtins/iterable/mod.rs @@ -1,13 +1,13 @@ -use crate::builtins::string::string_iterator::StringIterator; -use crate::object::GcObject; use crate::{ + builtins::string::string_iterator::StringIterator, builtins::{ function::{BuiltInFunction, Function, FunctionFlags}, ArrayIterator, }, + object::GcObject, object::{Object, PROTOTYPE}, property::Property, - BoaProfiler, Context, Value, + BoaProfiler, Context, Result, Value, }; #[derive(Debug, Default)] @@ -59,7 +59,7 @@ pub fn create_iter_result_object(ctx: &mut Context, value: Value, done: bool) -> } /// Get an iterator record -pub fn get_iterator(ctx: &mut Context, iterable: Value) -> Result { +pub fn get_iterator(ctx: &mut Context, iterable: Value) -> Result { let iterator_function = iterable .get_property(ctx.well_known_symbols().iterator_symbol()) .and_then(|mut p| p.value.take()) @@ -118,7 +118,7 @@ impl IteratorRecord { /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-iteratornext - pub(crate) fn next(&self, ctx: &mut Context) -> Result { + pub(crate) fn next(&self, ctx: &mut Context) -> Result { let next = ctx.call(&self.next_function, &self.iterator_object, &[])?; let done = next .get_property("done") From 69c76c4f11569a0a5153092cbd6a2d3615547be9 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Fri, 2 Oct 2020 23:22:06 +0100 Subject: [PATCH 34/35] Tidy up use statement --- boa/src/context.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/boa/src/context.rs b/boa/src/context.rs index cdbdbcc5236..252feca0fc0 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -1,12 +1,12 @@ //! Javascript context. -use crate::builtins::iterable::IteratorPrototypes; use crate::{ builtins::{ + iterable::IteratorPrototypes, self, function::{Function, FunctionFlags, NativeFunction}, symbol::{Symbol, WellKnownSymbols}, - Console, + Console }, class::{Class, ClassBuilder}, exec::Interpreter, @@ -24,7 +24,9 @@ use crate::{ Parser, }, value::{PreferredType, RcString, RcSymbol, Type, Value}, - BoaProfiler, Executable, Result, + BoaProfiler, + Executable, + Result }; use std::result::Result as StdResult; From 875a383cfc70219f60b4d7be9a96e54d918292a2 Mon Sep 17 00:00:00 2001 From: joshwd36 Date: Fri, 2 Oct 2020 23:22:20 +0100 Subject: [PATCH 35/35] Tidy up use statement --- boa/src/context.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/boa/src/context.rs b/boa/src/context.rs index 252feca0fc0..d6b95188dfe 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -2,11 +2,11 @@ use crate::{ builtins::{ - iterable::IteratorPrototypes, self, function::{Function, FunctionFlags, NativeFunction}, + iterable::IteratorPrototypes, symbol::{Symbol, WellKnownSymbols}, - Console + Console, }, class::{Class, ClassBuilder}, exec::Interpreter, @@ -24,9 +24,7 @@ use crate::{ Parser, }, value::{PreferredType, RcString, RcSymbol, Type, Value}, - BoaProfiler, - Executable, - Result + BoaProfiler, Executable, Result, }; use std::result::Result as StdResult;