diff --git a/boa/src/builtins/iterable/mod.rs b/boa/src/builtins/iterable/mod.rs index d99ee418cd..908cbf6b91 100644 --- a/boa/src/builtins/iterable/mod.rs +++ b/boa/src/builtins/iterable/mod.rs @@ -1,6 +1,7 @@ use crate::{ builtins::string::string_iterator::StringIterator, builtins::ArrayIterator, + builtins::ForInIterator, builtins::MapIterator, object::{GcObject, ObjectInitializer}, property::{Attribute, DataDescriptor}, @@ -13,6 +14,7 @@ pub struct IteratorPrototypes { array_iterator: GcObject, string_iterator: GcObject, map_iterator: GcObject, + for_in_iterator: GcObject, } impl IteratorPrototypes { @@ -28,9 +30,12 @@ impl IteratorPrototypes { string_iterator: StringIterator::create_prototype(context, iterator_prototype.clone()) .as_object() .expect("String Iterator Prototype is not an object"), - map_iterator: MapIterator::create_prototype(context, iterator_prototype) + map_iterator: MapIterator::create_prototype(context, iterator_prototype.clone()) .as_object() .expect("Map Iterator Prototype is not an object"), + for_in_iterator: ForInIterator::create_prototype(context, iterator_prototype) + .as_object() + .expect("For In Iterator Prototype is not an object"), } } @@ -49,9 +54,15 @@ impl IteratorPrototypes { self.string_iterator.clone() } + #[inline] pub fn map_iterator(&self) -> GcObject { self.map_iterator.clone() } + + #[inline] + pub fn for_in_iterator(&self) -> GcObject { + self.for_in_iterator.clone() + } } /// CreateIterResultObject( value, done ) @@ -110,7 +121,7 @@ pub struct IteratorRecord { } impl IteratorRecord { - fn new(iterator_object: Value, next_function: Value) -> Self { + pub fn new(iterator_object: Value, next_function: Value) -> Self { Self { iterator_object, next_function, diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index ae5af7f269..79d19fc87b 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -37,6 +37,7 @@ pub(crate) use self::{ math::Math, nan::NaN, number::Number, + object::for_in_iterator::ForInIterator, object::Object as BuiltInObjectObject, regexp::RegExp, string::String, diff --git a/boa/src/builtins/object/for_in_iterator.rs b/boa/src/builtins/object/for_in_iterator.rs new file mode 100644 index 0000000000..d5ea5ee61e --- /dev/null +++ b/boa/src/builtins/object/for_in_iterator.rs @@ -0,0 +1,145 @@ +use crate::property::PropertyKey; +use crate::value::RcString; +use crate::{ + builtins::{function::make_builtin_fn, iterable::create_iter_result_object}, + gc::{Finalize, Trace}, + object::ObjectData, + property::{Attribute, DataDescriptor}, + BoaProfiler, Context, Result, Value, +}; +use rustc_hash::FxHashSet; +use std::collections::VecDeque; + +/// The ForInIterator object represents an iteration over some specific object. +/// It implements the iterator protocol. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-for-in-iterator-objects +#[derive(Debug, Clone, Finalize, Trace)] +pub struct ForInIterator { + object: Value, + visited_keys: FxHashSet, + remaining_keys: VecDeque, + object_was_visited: bool, +} + +impl ForInIterator { + pub(crate) const NAME: &'static str = "ForInIterator"; + + fn new(object: Value) -> Self { + ForInIterator { + object, + visited_keys: FxHashSet::default(), + remaining_keys: VecDeque::default(), + object_was_visited: false, + } + } + + /// CreateForInIterator( object ) + /// + /// Creates a new iterator over the given object. + /// + /// More information: + /// - [ECMA reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-createforiniterator + pub(crate) fn create_for_in_iterator(context: &Context, object: Value) -> Result { + let for_in_iterator = Value::new_object(context); + for_in_iterator.set_data(ObjectData::ForInIterator(Self::new(object))); + for_in_iterator + .as_object() + .expect("for in iterator object") + .set_prototype_instance(context.iterator_prototypes().for_in_iterator().into()); + Ok(for_in_iterator) + } + + /// %ForInIteratorPrototype%.next( ) + /// + /// Gets the next result in the object. + /// + /// More information: + /// - [ECMA reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next + pub(crate) fn next(this: &Value, _: &[Value], context: &mut Context) -> Result { + if let Value::Object(ref o) = this { + let mut for_in_iterator = o.borrow_mut(); + if let Some(iterator) = for_in_iterator.as_for_in_iterator_mut() { + let mut object = iterator.object.to_object(context)?; + loop { + if !iterator.object_was_visited { + let keys = object.own_property_keys(); + for k in keys { + match k { + PropertyKey::String(ref k) => { + iterator.remaining_keys.push_back(k.clone()); + } + PropertyKey::Index(i) => { + iterator.remaining_keys.push_back(i.to_string().into()); + } + _ => {} + } + } + iterator.object_was_visited = true; + } + while let Some(r) = iterator.remaining_keys.pop_front() { + if !iterator.visited_keys.contains(&r) { + if let Some(desc) = + object.get_own_property(&PropertyKey::from(r.clone())) + { + iterator.visited_keys.insert(r.clone()); + if desc.enumerable() { + return Ok(create_iter_result_object( + context, + Value::from(r.to_string()), + false, + )); + } + } + } + } + match object.prototype_instance().to_object(context) { + Ok(o) => { + object = o; + } + _ => { + return Ok(create_iter_result_object(context, Value::undefined(), true)) + } + } + iterator.object = Value::from(object.clone()); + iterator.object_was_visited = false; + } + } else { + context.throw_type_error("`this` is not a ForInIterator") + } + } else { + context.throw_type_error("`this` is not an ForInIterator") + } + } + + /// Create the %ArrayIteratorPrototype% object + /// + /// More information: + /// - [ECMA reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%-object + pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: Value) -> Value { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + // Create prototype + let for_in_iterator = Value::new_object(context); + make_builtin_fn(Self::next, "next", &for_in_iterator, 0, context); + for_in_iterator + .as_object() + .expect("for in iterator prototype object") + .set_prototype_instance(iterator_prototype); + + let to_string_tag = context.well_known_symbols().to_string_tag_symbol(); + let to_string_tag_property = + DataDescriptor::new("For In Iterator", Attribute::CONFIGURABLE); + for_in_iterator.set_property(to_string_tag, to_string_tag_property); + for_in_iterator + } +} diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index ea07ee0c8b..1219d54785 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -23,6 +23,7 @@ use crate::{ BoaProfiler, Context, Result, }; +pub mod for_in_iterator; #[cfg(test)] mod tests; diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 02ef2b7d74..08c30a5beb 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -29,6 +29,7 @@ mod gcobject; mod internal_methods; mod iter; +use crate::builtins::object::for_in_iterator::ForInIterator; pub use gcobject::{GcObject, RecursionLimiter, Ref, RefMut}; pub use iter::*; @@ -84,6 +85,7 @@ pub enum ObjectData { RegExp(Box), BigInt(RcBigInt), Boolean(bool), + ForInIterator(ForInIterator), Function(Function), String(RcString), StringIterator(StringIterator), @@ -104,6 +106,7 @@ impl Display for ObjectData { match self { Self::Array => "Array", Self::ArrayIterator(_) => "ArrayIterator", + Self::ForInIterator(_) => "ForInIterator", Self::Function(_) => "Function", Self::RegExp(_) => "RegExp", Self::Map(_) => "Map", @@ -311,6 +314,22 @@ impl Object { } } + #[inline] + pub fn as_for_in_iterator(&self) -> Option<&ForInIterator> { + match &self.data { + ObjectData::ForInIterator(iter) => Some(iter), + _ => None, + } + } + + #[inline] + pub fn as_for_in_iterator_mut(&mut self) -> Option<&mut ForInIterator> { + match &mut self.data { + ObjectData::ForInIterator(iter) => Some(iter), + _ => None, + } + } + /// Checks if it is a `Map` object.pub #[inline] pub fn is_map(&self) -> bool { diff --git a/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs b/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs index 296473a063..19a828d658 100644 --- a/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs @@ -42,6 +42,10 @@ impl DoWhileLoop { self.label.as_ref().map(Box::as_ref) } + pub fn set_label(&mut self, label: Box) { + self.label = Some(label); + } + /// Creates a `DoWhileLoop` AST node. pub fn new(body: B, condition: C) -> Self where @@ -68,50 +72,16 @@ impl DoWhileLoop { impl Executable for DoWhileLoop { fn run(&self, context: &mut Context) -> Result { - let mut result = self.body().run(context)?; - match context.executor().get_current_state() { - InterpreterState::Break(_label) => { - // TODO break to label. - - // Loops 'consume' breaks. - context - .executor() - .set_current_state(InterpreterState::Executing); - return Ok(result); - } - InterpreterState::Continue(_label) => { - // TODO continue to label; - context - .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. - } - } - - while self.cond().run(context)?.to_boolean() { + let mut result; + loop { result = self.body().run(context)?; match context.executor().get_current_state() { - InterpreterState::Break(_label) => { - // TODO break to label. - - // Loops 'consume' breaks. - context - .executor() - .set_current_state(InterpreterState::Executing); + InterpreterState::Break(label) => { + handle_state_with_labels!(self, label, context, break); break; } - InterpreterState::Continue(_label) => { - // TODO continue to label. - context - .executor() - .set_current_state(InterpreterState::Executing); - // after breaking out of the block, continue execution of the loop + InterpreterState::Continue(label) => { + handle_state_with_labels!(self, label, context, continue); } InterpreterState::Return => { return Ok(result); @@ -120,6 +90,9 @@ impl Executable for DoWhileLoop { // Continue execution. } } + if !self.cond().run(context)?.to_boolean() { + break; + } } Ok(result) } diff --git a/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs new file mode 100644 index 0000000000..9841aedb3c --- /dev/null +++ b/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs @@ -0,0 +1,243 @@ +use crate::{ + builtins::{iterable::IteratorRecord, ForInIterator}, + environment::lexical_environment::{new_declarative_environment, VariableScope}, + exec::{Executable, InterpreterState}, + gc::{Finalize, Trace}, + syntax::ast::node::Node, + BoaProfiler, Context, Result, Value, +}; +use std::fmt; + +#[cfg(feature = "deser")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ForInLoop { + variable: Box, + expr: Box, + body: Box, + label: Option>, +} + +impl ForInLoop { + pub fn new(variable: V, expr: I, body: B) -> Self + where + V: Into, + I: Into, + B: Into, + { + Self { + variable: Box::new(variable.into()), + expr: Box::new(expr.into()), + body: Box::new(body.into()), + label: None, + } + } + + pub fn variable(&self) -> &Node { + &self.variable + } + + pub fn expr(&self) -> &Node { + &self.expr + } + + pub fn body(&self) -> &Node { + &self.body + } + + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(Box::as_ref) + } + + pub fn set_label(&mut self, label: Box) { + self.label = Some(label); + } + + pub fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { + write!(f, "for ({} in {}) {{", self.variable, self.expr,)?; + self.body().display(f, indentation + 1)?; + f.write_str("}") + } +} + +impl fmt::Display for ForInLoop { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(for_in: ForInLoop) -> Node { + Self::ForInLoop(for_in) + } +} + +impl Executable for ForInLoop { + fn run(&self, context: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("ForIn", "exec"); + let object = self.expr().run(context)?; + let mut result = Value::undefined(); + + if object.is_null_or_undefined() { + return Ok(result); + } + let object = object.to_object(context)?; + let for_in_iterator = ForInIterator::create_for_in_iterator(context, Value::from(object))?; + let next_function = for_in_iterator + .get_property("next") + .map(|p| p.as_data_descriptor().unwrap().value()) + .ok_or_else(|| context.construct_type_error("Could not find property `next`"))?; + let iterator = IteratorRecord::new(for_in_iterator, next_function); + + loop { + { + let env = &mut context.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } + let iterator_result = iterator.next(context)?; + if iterator_result.is_done() { + break; + } + let next_result = iterator_result.value(); + + match self.variable() { + Node::Identifier(ref name) => { + let environment = &mut context.realm_mut().environment; + + if environment.has_binding(name.as_ref()) { + // Binding already exists + environment + .set_mutable_binding(name.as_ref(), next_result.clone(), true) + .map_err(|e| e.to_error(context))?; + } else { + environment + .create_mutable_binding( + name.as_ref().to_owned(), + true, + VariableScope::Function, + ) + .map_err(|e| e.to_error(context))?; + let environment = &mut context.realm_mut().environment; + environment + .initialize_binding(name.as_ref(), next_result.clone()) + .map_err(|e| e.to_error(context))?; + } + } + Node::VarDeclList(ref list) => match list.as_ref() { + [var] => { + let environment = &mut context.realm_mut().environment; + + if var.init().is_some() { + return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer"); + } + + if environment.has_binding(var.name()) { + environment + .set_mutable_binding(var.name(), next_result, true) + .map_err(|e| e.to_error(context))?; + } else { + environment + .create_mutable_binding( + var.name().to_owned(), + false, + VariableScope::Function, + ) + .map_err(|e| e.to_error(context))?; + let environment = &mut context.realm_mut().environment; + environment + .initialize_binding(var.name(), next_result) + .map_err(|e| e.to_error(context))?; + } + } + _ => { + return context.throw_syntax_error( + "only one variable can be declared in the head of a for-in loop", + ) + } + }, + Node::LetDeclList(ref list) => match list.as_ref() { + [var] => { + let environment = &mut context.realm_mut().environment; + + if var.init().is_some() { + return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer"); + } + + environment + .create_mutable_binding( + var.name().to_owned(), + false, + VariableScope::Block, + ) + .map_err(|e| e.to_error(context))?; + let environment = &mut context.realm_mut().environment; + environment + .initialize_binding(var.name(), next_result) + .map_err(|e| e.to_error(context))?; + } + _ => { + return context.throw_syntax_error( + "only one variable can be declared in the head of a for-in loop", + ) + } + }, + Node::ConstDeclList(ref list) => match list.as_ref() { + [var] => { + let environment = &mut context.realm_mut().environment; + + if var.init().is_some() { + return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer"); + } + + environment + .create_immutable_binding( + var.name().to_owned(), + false, + VariableScope::Block, + ) + .map_err(|e| e.to_error(context))?; + let environment = &mut context.realm_mut().environment; + environment + .initialize_binding(var.name(), next_result) + .map_err(|e| e.to_error(context))?; + } + _ => { + return context.throw_syntax_error( + "only one variable can be declared in the head of a for-in loop", + ) + } + }, + Node::Assign(_) => { + return context.throw_syntax_error( + "a declaration in the head of a for-in loop can't have an initializer", + ); + } + _ => { + return context + .throw_syntax_error("unknown left hand side in head of for-in loop") + } + } + + result = self.body().run(context)?; + match context.executor().get_current_state() { + InterpreterState::Break(label) => { + handle_state_with_labels!(self, label, context, break); + break; + } + InterpreterState::Continue(label) => { + handle_state_with_labels!(self, label, context, continue); + } + InterpreterState::Return => return Ok(result), + InterpreterState::Executing => { + // Continue execution. + } + } + let _ = context.realm_mut().environment.pop(); + } + Ok(result) + } +} 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 index 5e40fc1e34..f0611c90ea 100644 --- a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs @@ -17,6 +17,7 @@ pub struct ForOfLoop { variable: Box, iterable: Box, body: Box, + label: Option>, } impl ForOfLoop { @@ -30,6 +31,7 @@ impl ForOfLoop { variable: Box::new(variable.into()), iterable: Box::new(iterable.into()), body: Box::new(body.into()), + label: None, } } @@ -45,6 +47,14 @@ impl ForOfLoop { &self.body } + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(Box::as_ref) + } + + pub fn set_label(&mut self, label: Box) { + self.label = Some(label); + } + 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)?; @@ -205,21 +215,12 @@ impl Executable for ForOfLoop { result = self.body().run(context)?; match context.executor().get_current_state() { - InterpreterState::Break(_label) => { - // TODO break to label. - - // Loops 'consume' breaks. - context - .executor() - .set_current_state(InterpreterState::Executing); + InterpreterState::Break(label) => { + handle_state_with_labels!(self, label, context, break); break; } - InterpreterState::Continue(_label) => { - // TODO continue to label. - context - .executor() - .set_current_state(InterpreterState::Executing); - // after breaking out of the block, continue execution of the loop + InterpreterState::Continue(label) => { + handle_state_with_labels!(self, label, context, continue); } InterpreterState::Return => return Ok(result), InterpreterState::Executing => { diff --git a/boa/src/syntax/ast/node/iteration/mod.rs b/boa/src/syntax/ast/node/iteration/mod.rs index 7fa4f8afb6..362e2cb4d0 100644 --- a/boa/src/syntax/ast/node/iteration/mod.rs +++ b/boa/src/syntax/ast/node/iteration/mod.rs @@ -1,8 +1,8 @@ //! Iteration nodes pub use self::{ - continue_node::Continue, do_while_loop::DoWhileLoop, for_loop::ForLoop, for_of_loop::ForOfLoop, - while_loop::WhileLoop, + continue_node::Continue, do_while_loop::DoWhileLoop, for_in_loop::ForInLoop, for_loop::ForLoop, + for_of_loop::ForOfLoop, while_loop::WhileLoop, }; #[cfg(test)] @@ -32,6 +32,7 @@ macro_rules! handle_state_with_labels { pub mod continue_node; pub mod do_while_loop; +pub mod for_in_loop; pub mod for_loop; pub mod for_of_loop; pub mod while_loop; diff --git a/boa/src/syntax/ast/node/iteration/tests.rs b/boa/src/syntax/ast/node/iteration/tests.rs index 0cbaa55947..9dc9bb6154 100644 --- a/boa/src/syntax/ast/node/iteration/tests.rs +++ b/boa/src/syntax/ast/node/iteration/tests.rs @@ -353,3 +353,152 @@ fn for_loop_continue_label() { "#; assert_eq!(&exec(scenario), "10"); } + +#[test] +fn for_in_declaration() { + let mut context = Context::new(); + + let init = r#" + let result = []; + let obj = { a: "a", b: 2}; + var i; + for (i in obj) { + result = result.concat([i]); + } + "#; + eprintln!("{}", forward(&mut context, init)); + + assert_eq!( + forward( + &mut context, + "result.length === 2 && result.includes('a') && result.includes('b')" + ), + "true" + ); +} + +#[test] +fn for_in_var_object() { + let mut context = Context::new(); + + let init = r#" + let result = []; + let obj = { a: "a", b: 2}; + for (var i in obj) { + result = result.concat([i]); + } + "#; + eprintln!("{}", forward(&mut context, init)); + + assert_eq!( + forward( + &mut context, + "result.length === 2 && result.includes('a') && result.includes('b')" + ), + "true" + ); +} + +#[test] +fn for_in_var_array() { + let mut context = Context::new(); + + let init = r#" + let result = []; + let arr = ["a", "b"]; + for (var i in arr) { + result = result.concat([i]); + } + "#; + eprintln!("{}", forward(&mut context, init)); + + assert_eq!( + forward( + &mut context, + "result.length === 2 && result.includes('0') && result.includes('1')" + ), + "true" + ); +} + +#[test] +fn for_in_let_object() { + let mut context = Context::new(); + + let init = r#" + let result = []; + let obj = { a: "a", b: 2}; + for (let i in obj) { + result = result.concat([i]); + } + "#; + eprintln!("{}", forward(&mut context, init)); + + assert_eq!( + forward( + &mut context, + "result.length === 2 && result.includes('a') && result.includes('b')" + ), + "true" + ); +} + +#[test] +fn for_in_const_array() { + let mut context = Context::new(); + + let init = r#" + let result = []; + let arr = ["a", "b"]; + for (const i in arr) { + result = result.concat([i]); + } + "#; + eprintln!("{}", forward(&mut context, init)); + + assert_eq!( + forward( + &mut context, + "result.length === 2 && result.includes('0') && result.includes('1')" + ), + "true" + ); +} + +#[test] +fn for_in_break_label() { + let scenario = r#" + var str = ""; + + outer: for (let i in [1, 2]) { + inner: for (let b in [2, 3, 4]) { + if (b === "1") { + break outer; + } + str = str + b; + } + str = str + i; + } + str + "#; + assert_eq!(&exec(scenario), "\"0\"") +} + +#[test] +fn for_in_continue_label() { + let scenario = r#" + var str = ""; + + outer: for (let i in [1, 2]) { + inner: for (let b in [2, 3, 4]) { + if (b === "1") { + continue outer; + } + str = str + b; + } + str = str + i; + } + str + "#; + assert_eq!(&exec(scenario), "\"00\"") +} diff --git a/boa/src/syntax/ast/node/iteration/while_loop/mod.rs b/boa/src/syntax/ast/node/iteration/while_loop/mod.rs index b4bdfb486b..bc4c3d23bd 100644 --- a/boa/src/syntax/ast/node/iteration/while_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/while_loop/mod.rs @@ -41,6 +41,10 @@ impl WhileLoop { self.label.as_ref().map(Box::as_ref) } + pub fn set_label(&mut self, label: Box) { + self.label = Some(label); + } + /// Creates a `WhileLoop` AST node. pub fn new(condition: C, body: B) -> Self where diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index efc32fbb6d..bd6992327e 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -33,7 +33,7 @@ pub use self::{ }, field::{GetConstField, GetField}, identifier::Identifier, - iteration::{Continue, DoWhileLoop, ForLoop, ForOfLoop, WhileLoop}, + iteration::{Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop}, new::New, object::Object, operator::{Assign, BinOp, UnaryOp}, @@ -130,6 +130,9 @@ pub enum Node { /// A `for` statement. [More information](./iteration/struct.ForLoop.html). ForLoop(ForLoop), + /// A `for...of` or `for..in` statement. [More information](./iteration/struct.ForIn.html). + ForInLoop(ForInLoop), + /// A `for...of` statement. [More information](./iteration/struct.ForOf.html). ForOfLoop(ForOfLoop), @@ -230,6 +233,7 @@ impl Node { 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::ForInLoop(ref for_in) => for_in.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), @@ -290,6 +294,7 @@ impl Executable for Node { Node::DoWhileLoop(ref do_while) => do_while.run(context), Node::ForLoop(ref for_loop) => for_loop.run(context), Node::ForOfLoop(ref for_of_loop) => for_of_loop.run(context), + Node::ForInLoop(ref for_in_loop) => for_in_loop.run(context), Node::If(ref if_smt) => if_smt.run(context), Node::ConditionalOp(ref op) => op.run(context), Node::Switch(ref switch) => switch.run(context), diff --git a/boa/src/syntax/parser/error.rs b/boa/src/syntax/parser/error.rs index 6d16535def..efb2c0a464 100644 --- a/boa/src/syntax/parser/error.rs +++ b/boa/src/syntax/parser/error.rs @@ -98,6 +98,7 @@ impl ParseError { } /// Creates a new `Unimplemented` parsing error. + #[allow(dead_code)] pub(super) fn unimplemented(message: &'static str, position: Position) -> Self { Self::Unimplemented { message, position } } diff --git a/boa/src/syntax/parser/expression/mod.rs b/boa/src/syntax/parser/expression/mod.rs index 3d59270df0..7dbbffd48d 100644 --- a/boa/src/syntax/parser/expression/mod.rs +++ b/boa/src/syntax/parser/expression/mod.rs @@ -473,20 +473,55 @@ impl RelationalExpression { } } -expression!( - RelationalExpression, - ShiftExpression, - [ - Punctuator::LessThan, - Punctuator::GreaterThan, - Punctuator::LessThanOrEq, - Punctuator::GreaterThanOrEq, - Keyword::InstanceOf, - Keyword::In - ], - [allow_yield, allow_await], - None:: -); +impl TokenParser for RelationalExpression +where + R: Read, +{ + type Output = Node; + + fn parse(self, cursor: &mut Cursor) -> ParseResult { + let _timer = BoaProfiler::global().start_event("Relation Expression", "Parsing"); + + if None::.is_some() { + cursor.set_goal(None::.unwrap()); + } + + let mut lhs = ShiftExpression::new(self.allow_yield, self.allow_await).parse(cursor)?; + while let Some(tok) = cursor.peek(0)? { + match *tok.kind() { + TokenKind::Punctuator(op) + if op == Punctuator::LessThan + || op == Punctuator::GreaterThan + || op == Punctuator::LessThanOrEq + || op == Punctuator::GreaterThanOrEq => + { + let _ = cursor.next().expect("token disappeared"); + lhs = BinOp::new( + op.as_binop().expect("Could not get binary operation."), + lhs, + ShiftExpression::new(self.allow_yield, self.allow_await).parse(cursor)?, + ) + .into(); + } + TokenKind::Keyword(op) + if op == Keyword::InstanceOf + || (op == Keyword::In && self.allow_in == AllowIn(true)) => + { + let _ = cursor.next().expect("token disappeared"); + lhs = BinOp::new( + op.as_binop().expect("Could not get binary operation."), + lhs, + ShiftExpression::new(self.allow_yield, self.allow_await).parse(cursor)?, + ) + .into(); + } + _ => break, + } + } + + Ok(lhs) + } +} /// Parses a bitwise shift expression. /// diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index 8b87b77128..71b0ea3c17 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, ForOfLoop, Node}, + node::{ForInLoop, ForLoop, ForOfLoop, Node}, Const, Keyword, Punctuator, }, parser::{ @@ -85,16 +85,18 @@ where 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)?), + _ => Some(Expression::new(false, self.allow_yield, self.allow_await).parse(cursor)?), }; match cursor.peek(0)? { - Some(tok) if tok.kind() == &TokenKind::Keyword(Keyword::In) => { - // TODO: for...in - return Err(ParseError::unimplemented( - "for...in loops", - tok.span().start(), - )); + Some(tok) if tok.kind() == &TokenKind::Keyword(Keyword::In) && init.is_some() => { + let _ = cursor.next(); + let expr = + Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "for in statement")?; + let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor)?; + return Ok(ForInLoop::new(init.unwrap(), expr, body).into()); } Some(tok) if tok.kind() == &TokenKind::Keyword(Keyword::Of) && init.is_some() => { let _ = cursor.next(); diff --git a/boa/src/syntax/parser/statement/labelled_stm/mod.rs b/boa/src/syntax/parser/statement/labelled_stm/mod.rs index 231a5c0b8a..0e970846c0 100644 --- a/boa/src/syntax/parser/statement/labelled_stm/mod.rs +++ b/boa/src/syntax/parser/statement/labelled_stm/mod.rs @@ -60,7 +60,12 @@ where } fn set_label_for_node(stmt: &mut Node, name: Box) { - if let Node::ForLoop(ref mut for_loop) = stmt { - for_loop.set_label(name) + match stmt { + Node::ForLoop(ref mut for_loop) => for_loop.set_label(name), + Node::ForOfLoop(ref mut for_of_loop) => for_of_loop.set_label(name), + Node::ForInLoop(ref mut for_in_loop) => for_in_loop.set_label(name), + Node::DoWhileLoop(ref mut do_while_loop) => do_while_loop.set_label(name), + Node::WhileLoop(ref mut while_loop) => while_loop.set_label(name), + _ => (), } }