From 85aa61906a3d5529a479827bc57fbf424bb8604b Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Sat, 21 Aug 2021 21:20:34 +0200 Subject: [PATCH] Implement destructuring assignments (#1406) --- boa/src/bytecompiler.rs | 89 +- boa/src/object/gcobject.rs | 71 ++ boa/src/property/mod.rs | 2 +- boa/src/syntax/ast/node/declaration/mod.rs | 910 +++++++++++++++++- boa/src/syntax/ast/node/declaration/tests.rs | 41 + .../ast/node/iteration/for_in_loop/mod.rs | 94 +- .../ast/node/iteration/for_of_loop/mod.rs | 95 +- boa/src/syntax/ast/node/statement_list/mod.rs | 32 +- .../async_function_expression/tests.rs | 6 +- .../primary/function_expression/tests.rs | 6 +- .../primary/object_initializer/tests.rs | 14 +- boa/src/syntax/parser/function/tests.rs | 16 +- .../syntax/parser/statement/block/tests.rs | 14 +- .../parser/statement/declaration/lexical.rs | 99 +- .../parser/statement/declaration/tests.rs | 116 ++- .../parser/statement/iteration/tests.rs | 16 +- boa/src/syntax/parser/statement/mod.rs | 593 +++++++++++- .../syntax/parser/statement/switch/tests.rs | 8 +- .../syntax/parser/statement/try_stm/tests.rs | 30 +- .../syntax/parser/statement/variable/mod.rs | 76 +- boa/src/syntax/parser/tests.rs | 28 +- 21 files changed, 2105 insertions(+), 251 deletions(-) diff --git a/boa/src/bytecompiler.rs b/boa/src/bytecompiler.rs index 059f4a6f19..6c5a78bc51 100644 --- a/boa/src/bytecompiler.rs +++ b/boa/src/bytecompiler.rs @@ -1,6 +1,6 @@ use crate::{ syntax::ast::{ - node::{GetConstField, GetField, Identifier, StatementList}, + node::{Declaration, GetConstField, GetField, Identifier, StatementList}, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, Const, Node, }, @@ -588,35 +588,80 @@ impl ByteCompiler { match node { Node::VarDeclList(list) => { for decl in list.as_ref() { - let index = self.get_or_insert_name(decl.name()); - self.emit(Opcode::DefVar, &[index]); - - if let Some(expr) = decl.init() { - self.compile_expr(expr, true); - self.emit(Opcode::InitLexical, &[index]); - }; + match decl { + Declaration::Identifier { ident, .. } => { + let index = self.get_or_insert_name(ident.as_ref()); + self.emit(Opcode::DefVar, &[index]); + + if let Some(expr) = decl.init() { + self.compile_expr(expr, true); + self.emit(Opcode::InitLexical, &[index]); + }; + } + Declaration::Pattern(pattern) => { + for ident in pattern.idents() { + let index = self.get_or_insert_name(ident); + self.emit(Opcode::DefVar, &[index]); + + if let Some(expr) = decl.init() { + self.compile_expr(expr, true); + self.emit(Opcode::InitLexical, &[index]); + }; + } + } + } } } Node::LetDeclList(list) => { for decl in list.as_ref() { - let index = self.get_or_insert_name(decl.name()); - self.emit(Opcode::DefLet, &[index]); - - if let Some(expr) = decl.init() { - self.compile_expr(expr, true); - self.emit(Opcode::InitLexical, &[index]); - }; + match decl { + Declaration::Identifier { ident, .. } => { + let index = self.get_or_insert_name(ident.as_ref()); + self.emit(Opcode::DefLet, &[index]); + + if let Some(expr) = decl.init() { + self.compile_expr(expr, true); + self.emit(Opcode::InitLexical, &[index]); + }; + } + Declaration::Pattern(pattern) => { + for ident in pattern.idents() { + let index = self.get_or_insert_name(ident); + self.emit(Opcode::DefLet, &[index]); + + if let Some(expr) = decl.init() { + self.compile_expr(expr, true); + self.emit(Opcode::InitLexical, &[index]); + }; + } + } + } } } Node::ConstDeclList(list) => { for decl in list.as_ref() { - let index = self.get_or_insert_name(decl.name()); - self.emit(Opcode::DefConst, &[index]); - - if let Some(expr) = decl.init() { - self.compile_expr(expr, true); - self.emit(Opcode::InitLexical, &[index]); - }; + match decl { + Declaration::Identifier { ident, .. } => { + let index = self.get_or_insert_name(ident.as_ref()); + self.emit(Opcode::DefConst, &[index]); + + if let Some(expr) = decl.init() { + self.compile_expr(expr, true); + self.emit(Opcode::InitLexical, &[index]); + }; + } + Declaration::Pattern(pattern) => { + for ident in pattern.idents() { + let index = self.get_or_insert_name(ident); + self.emit(Opcode::DefConst, &[index]); + + if let Some(expr) = decl.init() { + self.compile_expr(expr, true); + self.emit(Opcode::InitLexical, &[index]); + }; + } + } + } } } Node::If(node) => { diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index e6f0f3d5dc..0753056270 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -916,6 +916,77 @@ impl GcObject { // 16. Return desc. Ok(desc.build()) } + + /// `7.3.25 CopyDataProperties ( target, source, excludedItems )` + /// + /// More information: + /// - [ECMAScript][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-copydataproperties + #[inline] + pub fn copy_data_properties( + &mut self, + source: &JsValue, + excluded_keys: Vec, + context: &mut Context, + ) -> Result<()> + where + K: Into, + { + // 1. Assert: Type(target) is Object. + // 2. Assert: excludedItems is a List of property keys. + // 3. If source is undefined or null, return target. + if source.is_null_or_undefined() { + return Ok(()); + } + + // 4. Let from be ! ToObject(source). + let from = source + .to_object(context) + .expect("function ToObject should never complete abruptly here"); + + // 5. Let keys be ? from.[[OwnPropertyKeys]](). + // 6. For each element nextKey of keys, do + let excluded_keys: Vec = excluded_keys.into_iter().map(|e| e.into()).collect(); + for key in from.own_property_keys() { + // a. Let excluded be false. + let mut excluded = false; + + // b. For each element e of excludedItems, do + for e in &excluded_keys { + // i. If SameValue(e, nextKey) is true, then + if *e == key { + // 1. Set excluded to true. + excluded = true; + break; + } + } + // c. If excluded is false, then + if !excluded { + // i. Let desc be ? from.[[GetOwnProperty]](nextKey). + let desc = from.__get_own_property__(&key); + + // ii. If desc is not undefined and desc.[[Enumerable]] is true, then + if let Some(desc) = desc { + if let Some(enumerable) = desc.enumerable() { + if enumerable { + // 1. Let propValue be ? Get(from, nextKey). + let prop_value = from.__get__(&key, from.clone().into(), context)?; + + // 2. Perform ! CreateDataPropertyOrThrow(target, nextKey, propValue). + self.create_data_property_or_throw(key, prop_value, context) + .expect( + "CreateDataPropertyOrThrow should never complete abruptly here", + ); + } + } + } + } + } + + // 7. Return target. + Ok(()) + } } impl AsRef> for GcObject { diff --git a/boa/src/property/mod.rs b/boa/src/property/mod.rs index 74fc599084..b09b47f039 100644 --- a/boa/src/property/mod.rs +++ b/boa/src/property/mod.rs @@ -472,7 +472,7 @@ impl From for PropertyDescriptor { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ispropertykey -#[derive(Trace, Finalize, Debug, Clone)] +#[derive(Trace, Finalize, PartialEq, Debug, Clone)] pub enum PropertyKey { String(JsString), Symbol(JsSymbol), diff --git a/boa/src/syntax/ast/node/declaration/mod.rs b/boa/src/syntax/ast/node/declaration/mod.rs index b036a22a56..2ac3c5d5a8 100644 --- a/boa/src/syntax/ast/node/declaration/mod.rs +++ b/boa/src/syntax/ast/node/declaration/mod.rs @@ -1,5 +1,6 @@ //! Declaration nodes use crate::{ + builtins::{iterable::get_iterator, Array}, environment::lexical_environment::VariableScope, exec::Executable, gc::{Finalize, Trace}, @@ -101,32 +102,66 @@ impl Executable for DeclarationList { None => JsValue::undefined(), }; - if self.is_var() && context.has_binding(decl.name()) { - if decl.init().is_some() { - context.set_mutable_binding(decl.name(), val, true)?; + match &decl { + Declaration::Identifier { ident, init } => { + if self.is_var() && context.has_binding(ident.as_ref()) { + if init.is_some() { + context.set_mutable_binding(ident.as_ref(), val, true)?; + } + continue; + } + + match &self { + Const(_) => context.create_immutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?, + Let(_) => context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?, + Var(_) => context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Function, + )?, + } + + context.initialize_binding(ident.as_ref(), val)?; } - continue; - } + Declaration::Pattern(p) => { + for (ident, value) in p.run(None, context)? { + if self.is_var() && context.has_binding(ident.as_ref()) { + if !value.is_undefined() { + context.set_mutable_binding(ident.as_ref(), value, true)?; + } + continue; + } - match &self { - Const(_) => context.create_immutable_binding( - decl.name().to_owned(), - false, - VariableScope::Block, - )?, - Let(_) => context.create_mutable_binding( - decl.name().to_owned(), - false, - VariableScope::Block, - )?, - Var(_) => context.create_mutable_binding( - decl.name().to_owned(), - false, - VariableScope::Function, - )?, - } - - context.initialize_binding(decl.name(), val)?; + match &self { + Const(_) => context.create_immutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?, + Let(_) => context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?, + Var(_) => context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Function, + )?, + } + + context.initialize_binding(ident.as_ref(), value)?; + } + } + } } Ok(JsValue::undefined()) @@ -188,44 +223,841 @@ impl From for Box<[Declaration]> { } } -/// Individual declaration. +/// Declaration represents either an individual binding or a binding pattern. +/// +/// For `let` and `const` declarations this type represents a [LexicalBinding][spec1] +/// +/// For `var` declarations this type represents a [VariableDeclaration][spec2] +/// +/// More information: +/// - [ECMAScript reference: 14.3 Declarations and the Variable Statement][spec3] +/// +/// [spec1]: https://tc39.es/ecma262/#prod-LexicalBinding +/// [spec2]: https://tc39.es/ecma262/#prod-VariableDeclaration +/// [spec3]: https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement #[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] #[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct Declaration { - name: Identifier, - init: Option, +pub enum Declaration { + Identifier { + ident: Identifier, + init: Option, + }, + Pattern(DeclarationPattern), } impl fmt::Display for Declaration { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.name, f)?; - if let Some(ref init) = self.init { - write!(f, " = {}", init)?; + match &self { + Self::Identifier { ident, init } => { + fmt::Display::fmt(&ident, f)?; + if let Some(ref init) = &init { + write!(f, " = {}", init)?; + } + } + Self::Pattern(pattern) => { + fmt::Display::fmt(&pattern, f)?; + } } Ok(()) } } impl Declaration { - /// Creates a new variable declaration. - pub(in crate::syntax) fn new(name: N, init: I) -> Self + /// Creates a new variable declaration with a BindingIdentifier. + #[inline] + pub(in crate::syntax) fn new_with_identifier(ident: N, init: I) -> Self where N: Into, I: Into>, { - Self { - name: name.into(), + Self::Identifier { + ident: ident.into(), init: init.into(), } } - /// Gets the name of the variable. - pub fn name(&self) -> &str { - self.name.as_ref() + /// Creates a new variable declaration with an ObjectBindingPattern. + #[inline] + pub(in crate::syntax) fn new_with_object_pattern( + bindings: Vec, + init: I, + ) -> Self + where + I: Into>, + { + Self::Pattern(DeclarationPattern::Object(DeclarationPatternObject::new( + bindings, + init.into(), + ))) + } + + /// Creates a new variable declaration with an ArrayBindingPattern. + #[inline] + pub(in crate::syntax) fn new_with_array_pattern( + bindings: Vec, + init: I, + ) -> Self + where + I: Into>, + { + Self::Pattern(DeclarationPattern::Array(DeclarationPatternArray::new( + bindings, + init.into(), + ))) + } + + /// Gets the initialization node for the declaration, if any. + #[inline] + pub(crate) fn init(&self) -> Option<&Node> { + match &self { + Self::Identifier { init, .. } => init.as_ref(), + Self::Pattern(pattern) => pattern.init(), + } + } +} + +/// DeclarationPattern represents an object or array binding pattern. +/// +/// This enum mostly wraps the functionality of the specific binding pattern types. +/// +/// More information: +/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingPattern][spec1] +/// +/// [spec1]: https://tc39.es/ecma262/#prod-BindingPattern +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub enum DeclarationPattern { + Object(DeclarationPatternObject), + Array(DeclarationPatternArray), +} + +impl fmt::Display for DeclarationPattern { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + DeclarationPattern::Object(o) => { + fmt::Display::fmt(o, f)?; + } + DeclarationPattern::Array(a) => { + fmt::Display::fmt(a, f)?; + } + } + Ok(()) + } +} + +impl DeclarationPattern { + /// Initialize the values of an object/array binding pattern. + /// + /// This function only calls the specific initialization function for either the object or the array binding pattern. + /// For specific documentation and references to the ECMAScript spec, look at the called initialization functions. + #[inline] + pub(in crate::syntax) fn run( + &self, + init: Option, + context: &mut Context, + ) -> Result, JsValue)>> { + match &self { + DeclarationPattern::Object(pattern) => pattern.run(init, context), + DeclarationPattern::Array(pattern) => pattern.run(init, context), + } + } + + /// Gets the list of identifiers declared by the binding pattern. + /// + /// A single binding pattern may declare 0 to n identifiers. + #[inline] + pub fn idents(&self) -> Vec<&str> { + match &self { + DeclarationPattern::Object(pattern) => pattern.idents(), + DeclarationPattern::Array(pattern) => pattern.idents(), + } } - /// Gets the initialization node for the variable, if any. + /// Gets the initialization node for the binding pattern, if any. + #[inline] pub fn init(&self) -> Option<&Node> { + match &self { + DeclarationPattern::Object(pattern) => pattern.init(), + DeclarationPattern::Array(pattern) => pattern.init(), + } + } +} + +/// DeclarationPatternObject represents an object binding pattern. +/// +/// This struct holds a list of bindings, and an optional initializer for the binding pattern. +/// +/// More information: +/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - ObjectBindingPattern][spec1] +/// +/// [spec1]: https://tc39.es/ecma262/#prod-ObjectBindingPattern +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct DeclarationPatternObject { + bindings: Vec, + init: Option, +} + +impl fmt::Display for DeclarationPatternObject { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt("{", f)?; + for (i, binding) in self.bindings.iter().enumerate() { + if i == self.bindings.len() - 1 { + write!(f, "{} ", binding)?; + } else { + write!(f, "{},", binding)?; + } + } + fmt::Display::fmt("}", f)?; + if let Some(ref init) = self.init { + write!(f, " = {}", init)?; + } + Ok(()) + } +} + +impl DeclarationPatternObject { + /// Create a new object binding pattern. + #[inline] + pub(in crate::syntax) fn new( + bindings: Vec, + init: Option, + ) -> Self { + Self { bindings, init } + } + + /// Gets the initialization node for the object binding pattern, if any. + #[inline] + pub(in crate::syntax) fn init(&self) -> Option<&Node> { self.init.as_ref() } + + /// Initialize the values of an object binding pattern. + /// + /// More information: + /// - [ECMAScript reference: 8.5.2 Runtime Semantics: BindingInitialization][spec1] + /// - [ECMAScript reference:14.3.3.3 Runtime Semantics: KeyedBindingInitialization][spec2] + /// - [ECMAScript reference:14.3.3.2 Runtime Semantics: RestBindingInitialization][spec3] + /// + /// [spec1]: https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization + /// [spec2]: https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization + /// [spec3]: https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-restbindinginitialization + pub(in crate::syntax) fn run( + &self, + init: Option, + context: &mut Context, + ) -> Result, JsValue)>> { + let value = if let Some(value) = init { + value + } else if let Some(node) = &self.init { + node.run(context)? + } else { + JsValue::undefined() + }; + + if value.is_null() { + return Err(context.construct_type_error("Cannot destructure 'null' value")); + } + if value.is_undefined() { + return Err(context.construct_type_error("Cannot destructure 'undefined' value")); + } + + // 1. Perform ? RequireObjectCoercible(value). + let value = value.require_object_coercible(context)?; + let mut results = Vec::new(); + + // 2. Return the result of performing BindingInitialization for ObjectBindingPattern using value and environment as arguments. + for binding in &self.bindings { + use BindingPatternTypeObject::*; + + match binding { + // ObjectBindingPattern : { } + Empty => { + // 1. Return NormalCompletion(empty). + } + // SingleNameBinding : BindingIdentifier Initializer[opt] + SingleName { + ident, + property_name, + default_init, + } => { + // 1. Let bindingId be StringValue of BindingIdentifier. + // 2. Let lhs be ? ResolveBinding(bindingId, environment). + + // 3. Let v be ? GetV(value, propertyName). + let mut v = value.get_field(property_name.as_ref(), context)?; + + // 4. If Initializer is present and v is undefined, then + if let Some(init) = default_init { + if v.is_undefined() { + // TODO: a. not implemented yet: + // a. If IsAnonymousFunctionDefinition(Initializer) is true, then + // i. Set v to the result of performing NamedEvaluation for Initializer with argument bindingId. + + // b. Else, + // i. Let defaultValue be the result of evaluating Initializer. + // ii. Set v to ? GetValue(defaultValue). + v = init.run(context)?; + } + } + + // 5. If environment is undefined, return ? PutValue(lhs, v). + // 6. Return InitializeReferencedBinding(lhs, v). + results.push((ident.clone(), v)); + } + // BindingRestProperty : ... BindingIdentifier + RestProperty { + ident, + excluded_keys, + } => { + // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). + + // 2. Let restObj be ! OrdinaryObjectCreate(%Object.prototype%). + let mut rest_obj = context.construct_object(); + + // 3. Perform ? CopyDataProperties(restObj, value, excludedNames). + rest_obj.copy_data_properties(value, excluded_keys.clone(), context)?; + + // 4. If environment is undefined, return PutValue(lhs, restObj). + // 5. Return InitializeReferencedBinding(lhs, restObj). + results.push((ident.clone(), rest_obj.into())); + } + // BindingElement : BindingPattern Initializer[opt] + BindingPattern { + ident, + pattern, + default_init, + } => { + // 1. Let v be ? GetV(value, propertyName). + let mut v = value.get_field(ident.as_ref(), context)?; + + // 2. If Initializer is present and v is undefined, then + if let Some(init) = default_init { + if v.is_undefined() { + // a. Let defaultValue be the result of evaluating Initializer. + // b. Set v to ? GetValue(defaultValue). + v = init.run(context)?; + } + } + + // 3. Return the result of performing BindingInitialization for BindingPattern passing v and environment as arguments. + results.append(&mut pattern.run(Some(v), context)?); + } + } + } + + Ok(results) + } + + /// Gets the list of identifiers declared by the object binding pattern. + #[inline] + pub(in crate::syntax) fn idents(&self) -> Vec<&str> { + let mut idents = Vec::new(); + + for binding in &self.bindings { + use BindingPatternTypeObject::*; + + match binding { + Empty => {} + SingleName { + ident, + property_name: _, + default_init: _, + } => { + idents.push(ident.as_ref()); + } + RestProperty { + ident: property_name, + excluded_keys: _, + } => { + idents.push(property_name.as_ref()); + } + BindingPattern { + ident: _, + pattern, + default_init: _, + } => { + for ident in pattern.idents() { + idents.push(ident); + } + } + } + } + + idents + } +} + +/// DeclarationPatternArray represents an array binding pattern. +/// +/// This struct holds a list of bindings, and an optional initializer for the binding pattern. +/// +/// More information: +/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - ArrayBindingPattern][spec1] +/// +/// [spec1]: https://tc39.es/ecma262/#prod-ArrayBindingPattern +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct DeclarationPatternArray { + bindings: Vec, + init: Option, +} + +impl fmt::Display for DeclarationPatternArray { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt("[", f)?; + for (i, binding) in self.bindings.iter().enumerate() { + if i == self.bindings.len() - 1 { + match binding { + BindingPatternTypeArray::Elision => write!(f, "{}, ", binding)?, + _ => write!(f, "{} ", binding)?, + } + } else { + write!(f, "{},", binding)?; + } + } + fmt::Display::fmt("]", f)?; + if let Some(ref init) = self.init { + write!(f, " = {}", init)?; + } + Ok(()) + } +} + +impl DeclarationPatternArray { + /// Create a new array binding pattern. + #[inline] + pub(in crate::syntax) fn new( + bindings: Vec, + init: Option, + ) -> Self { + Self { bindings, init } + } + + /// Gets the initialization node for the array binding pattern, if any. + #[inline] + pub(in crate::syntax) fn init(&self) -> Option<&Node> { + self.init.as_ref() + } + + /// Initialize the values of an array binding pattern. + /// + /// More information: + /// - [ECMAScript reference: 8.5.2 Runtime Semantics: BindingInitialization][spec1] + /// - [ECMAScript reference: 8.5.3 Runtime Semantics: IteratorBindingInitialization][spec2] + /// + /// [spec1]: https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization + /// [spec2]: https://tc39.es/ecma262/#sec-runtime-semantics-iteratorbindinginitialization + pub(in crate::syntax) fn run( + &self, + init: Option, + context: &mut Context, + ) -> Result, JsValue)>> { + let value = if let Some(value) = init { + value + } else if let Some(node) = &self.init { + node.run(context)? + } else { + JsValue::undefined() + }; + + if value.is_null() { + return Err(context.construct_type_error("Cannot destructure 'null' value")); + } + if value.is_undefined() { + return Err(context.construct_type_error("Cannot destructure 'undefined' value")); + } + + // 1. Let iteratorRecord be ? GetIterator(value). + let iterator = get_iterator(context, value)?; + let mut result = Vec::new(); + + // 2. Let result be IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment. + for binding in &self.bindings { + use BindingPatternTypeArray::*; + + match binding { + // ArrayBindingPattern : [ ] + Empty => { + // 1. Return NormalCompletion(empty). + } + // ArrayBindingPattern : [ Elision ] + // Note: This captures all elisions due to our representation of a the binding pattern. + Elision => { + // 1. If iteratorRecord.[[Done]] is false, then + // a. Let next be IteratorStep(iteratorRecord). + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + // c. ReturnIfAbrupt(next). + // d. If next is false, set iteratorRecord.[[Done]] to true. + let _ = iterator.next(context)?; + + // 2. Return NormalCompletion(empty). + } + // SingleNameBinding : BindingIdentifier Initializer[opt] + SingleName { + ident, + default_init, + } => { + // 1. Let bindingId be StringValue of BindingIdentifier. + // 2. Let lhs be ? ResolveBinding(bindingId, environment). + + let next = iterator.next(context)?; + + // 3. If iteratorRecord.[[Done]] is false, then + // 4. If iteratorRecord.[[Done]] is true, let v be undefined. + let mut v = if !next.is_done() { + // a. Let next be IteratorStep(iteratorRecord). + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + // c. ReturnIfAbrupt(next). + // d. If next is false, set iteratorRecord.[[Done]] to true. + // e. Else, + // i. Let v be IteratorValue(next). + // ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true. + // iii. ReturnIfAbrupt(v). + next.value() + } else { + JsValue::undefined() + }; + + // 5. If Initializer is present and v is undefined, then + if let Some(init) = default_init { + if v.is_undefined() { + // TODO: a. not implemented yet: + // a. If IsAnonymousFunctionDefinition(Initializer) is true, then + // i. Set v to the result of performing NamedEvaluation for Initializer with argument bindingId. + + // b. Else, + // i. Let defaultValue be the result of evaluating Initializer. + // ii. Set v to ? GetValue(defaultValue). + v = init.run(context)? + } + } + + // 6. If environment is undefined, return ? PutValue(lhs, v). + // 7. Return InitializeReferencedBinding(lhs, v). + result.push((ident.clone(), v)); + } + // BindingElement : BindingPattern Initializer[opt] + BindingPattern { pattern } => { + let next = iterator.next(context)?; + + // 1. If iteratorRecord.[[Done]] is false, then + // 2. If iteratorRecord.[[Done]] is true, let v be undefined. + let v = if !next.is_done() { + // a. Let next be IteratorStep(iteratorRecord). + // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + // c. ReturnIfAbrupt(next). + // d. If next is false, set iteratorRecord.[[Done]] to true. + // e. Else, + // i. Let v be IteratorValue(next). + // ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true. + // iii. ReturnIfAbrupt(v). + Some(next.value()) + } else { + None + }; + + // 3. If Initializer is present and v is undefined, then + // a. Let defaultValue be the result of evaluating Initializer. + // b. Set v to ? GetValue(defaultValue). + + // 4. Return the result of performing BindingInitialization of BindingPattern with v and environment as the arguments. + result.append(&mut pattern.run(v, context)?); + } + // BindingRestElement : ... BindingIdentifier + SingleNameRest { ident } => { + // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). + // 2. Let A be ! ArrayCreate(0). + // 3. Let n be 0. + let a = Array::array_create(0, None, context) + .expect("Array creation with 0 length should never fail"); + + // 4. Repeat, + loop { + let next = iterator.next(context)?; + // a. If iteratorRecord.[[Done]] is false, then + // i. Let next be IteratorStep(iteratorRecord). + // ii. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + // iii. ReturnIfAbrupt(next). + // iv. If next is false, set iteratorRecord.[[Done]] to true. + + // b. If iteratorRecord.[[Done]] is true, then + if next.is_done() { + // i. If environment is undefined, return ? PutValue(lhs, A). + // ii. Return InitializeReferencedBinding(lhs, A). + break result.push((ident.clone(), a.clone().into())); + } + + // c. Let nextValue be IteratorValue(next). + // d. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. + // e. ReturnIfAbrupt(nextValue). + + // f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), nextValue). + // g. Set n to n + 1. + Array::add_to_array_object(&a.clone().into(), &[next.value()], context)?; + } + } + // BindingRestElement : ... BindingPattern + BindingPatternRest { pattern } => { + // 1. Let A be ! ArrayCreate(0). + // 2. Let n be 0. + let a = Array::array_create(0, None, context) + .expect("Array creation with 0 length should never fail"); + + // 3. Repeat, + loop { + // a. If iteratorRecord.[[Done]] is false, then + // i. Let next be IteratorStep(iteratorRecord). + // ii. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + // iii. ReturnIfAbrupt(next). + // iv. If next is false, set iteratorRecord.[[Done]] to true. + let next = iterator.next(context)?; + + // b. If iteratorRecord.[[Done]] is true, then + if next.is_done() { + // i. Return the result of performing BindingInitialization of BindingPattern with A and environment as the arguments. + break result + .append(&mut pattern.run(Some(a.clone().into()), context)?); + } + + // c. Let nextValue be IteratorValue(next). + // d. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. + // e. ReturnIfAbrupt(nextValue). + // f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), nextValue). + // g. Set n to n + 1. + Array::add_to_array_object(&a.clone().into(), &[next.value()], context)?; + } + } + } + } + + // 3. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, result). + // 4. Return result. + Ok(result) + } + + /// Gets the list of identifiers declared by the array binding pattern. + #[inline] + pub(in crate::syntax) fn idents(&self) -> Vec<&str> { + let mut idents = Vec::new(); + + for binding in &self.bindings { + use BindingPatternTypeArray::*; + + match binding { + Empty => {} + Elision => {} + SingleName { + ident, + default_init: _, + } => { + idents.push(ident.as_ref()); + } + BindingPattern { pattern } | BindingPatternRest { pattern } => { + let mut i = pattern.idents(); + idents.append(&mut i) + } + SingleNameRest { ident } => idents.push(ident), + } + } + + idents + } +} + +/// BindingPatternTypeObject represents the different types of bindings that an object binding pattern may contain. +/// +/// More information: +/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - ObjectBindingPattern][spec1] +/// +/// [spec1]: https://tc39.es/ecma262/#prod-ObjectBindingPattern +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub enum BindingPatternTypeObject { + /// Empty represents an empty object binding pattern e.g. `{ }`. + Empty, + + /// SingleName represents one of the following properties: + /// + /// - `SingleNameBinding` with an identifier and an optional default initializer. + /// - `BindingProperty` with an property name and a `SingleNameBinding` as the `BindingElement`. + /// + /// More information: + /// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - SingleNameBinding][spec1] + /// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingProperty][spec2] + /// + /// [spec1]: https://tc39.es/ecma262/#prod-SingleNameBinding + /// [spec2]: https://tc39.es/ecma262/#prod-BindingProperty + SingleName { + ident: Box, + property_name: Box, + default_init: Option, + }, + + /// RestProperty represents a `BindingRestProperty` with an identifier. + /// + /// It also includes a list of the property keys that should be excluded from the rest, + /// because they where already assigned. + /// + /// More information: + /// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingRestProperty][spec1] + /// + /// [spec1]: https://tc39.es/ecma262/#prod-BindingRestProperty + RestProperty { + ident: Box, + excluded_keys: Vec>, + }, + + /// BindingPattern represents a `BindingProperty` with a `BindingPattern` as the `BindingElement`. + /// + /// Additionally to the identifier of the new property and the nested binding pattern, + /// this may also include an optional default initializer. + /// + /// More information: + /// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingProperty][spec1] + /// + /// [spec1]: https://tc39.es/ecma262/#prod-BindingProperty + BindingPattern { + ident: Box, + pattern: DeclarationPattern, + default_init: Option, + }, +} + +impl fmt::Display for BindingPatternTypeObject { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + BindingPatternTypeObject::Empty => {} + BindingPatternTypeObject::SingleName { + ident, + property_name, + default_init, + } => { + if ident == property_name { + write!(f, " {}", ident)?; + } else { + write!(f, " {} : {}", property_name, ident)?; + } + if let Some(ref init) = default_init { + write!(f, " = {}", init)?; + } + } + BindingPatternTypeObject::RestProperty { + ident: property_name, + excluded_keys: _, + } => { + write!(f, " ... {}", property_name)?; + } + BindingPatternTypeObject::BindingPattern { + ident: property_name, + pattern, + default_init, + } => { + write!(f, " {} : {}", property_name, pattern)?; + if let Some(ref init) = default_init { + write!(f, " = {}", init)?; + } + } + } + Ok(()) + } +} + +/// BindingPatternTypeArray represents the different types of bindings that an array binding pattern may contain. +/// +/// More information: +/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - ArrayBindingPattern][spec1] +/// +/// [spec1]: https://tc39.es/ecma262/#prod-ArrayBindingPattern +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub enum BindingPatternTypeArray { + /// Empty represents an empty array binding pattern e.g. `[ ]`. + /// + /// This may occur because the `Elision` and `BindingRestElement` in the first type of + /// array binding pattern are both optional. + /// + /// More information: + /// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - ArrayBindingPattern][spec1] + /// + /// [spec1]: https://tc39.es/ecma262/#prod-ArrayBindingPattern + Empty, + + /// Elision represents the elision of an item in the array binding pattern. + /// + /// An `Elision` may occur at multiple points in the pattern and may be multiple elisions. + /// This variant strictly represents one elision. If there are multiple, this should be used multiple times. + /// + /// More information: + /// - [ECMAScript reference: 13.2.4 Array Initializer - Elision][spec1] + /// + /// [spec1]: https://tc39.es/ecma262/#prod-Elision + Elision, + + /// SingleName represents a `SingleNameBinding` with an identifier and an optional default initializer. + /// + /// More information: + /// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - SingleNameBinding][spec1] + /// + /// [spec1]: https://tc39.es/ecma262/#prod-SingleNameBinding + SingleName { + ident: Box, + default_init: Option, + }, + + /// BindingPattern represents a `BindingPattern` in a `BindingElement` of an array binding pattern. + /// + /// The pattern and the optional default initializer are both stored in the DeclarationPattern. + /// + /// More information: + /// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingElement][spec1] + /// + /// [spec1]: https://tc39.es/ecma262/#prod-BindingElement + BindingPattern { pattern: DeclarationPattern }, + + /// SingleNameRest represents a `BindingIdentifier` in a `BindingRestElement` of an array binding pattern. + /// + /// More information: + /// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingRestElement][spec1] + /// + /// [spec1]: https://tc39.es/ecma262/#prod-BindingRestElement + SingleNameRest { ident: Box }, + + /// SingleNameRest represents a `BindingPattern` in a `BindingRestElement` of an array binding pattern. + /// + /// More information: + /// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingRestElement][spec1] + /// + /// [spec1]: https://tc39.es/ecma262/#prod-BindingRestElement + BindingPatternRest { pattern: DeclarationPattern }, +} + +impl fmt::Display for BindingPatternTypeArray { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + BindingPatternTypeArray::Empty => {} + BindingPatternTypeArray::Elision => { + fmt::Display::fmt(" ", f)?; + } + BindingPatternTypeArray::SingleName { + ident, + default_init, + } => { + write!(f, " {}", ident)?; + if let Some(ref init) = default_init { + write!(f, " = {}", init)?; + } + } + BindingPatternTypeArray::BindingPattern { pattern } => { + write!(f, " {}", pattern)?; + } + BindingPatternTypeArray::SingleNameRest { ident } => { + write!(f, " ... {}", ident)?; + } + BindingPatternTypeArray::BindingPatternRest { pattern } => { + write!(f, " ... {}", pattern)?; + } + } + Ok(()) + } } diff --git a/boa/src/syntax/ast/node/declaration/tests.rs b/boa/src/syntax/ast/node/declaration/tests.rs index 7c6dec9a95..d45f2797d7 100644 --- a/boa/src/syntax/ast/node/declaration/tests.rs +++ b/boa/src/syntax/ast/node/declaration/tests.rs @@ -39,3 +39,44 @@ fn fmt() { "#, ); } + +#[test] +fn fmt_binding_pattern() { + super::super::test_formatting( + r#" + var { } = { + o: "1", + }; + var { o_v1 } = { + o_v1: "1", + }; + var { o_v2 = "1" } = { + o_v2: "2", + }; + var { a : o_v3 = "1" } = { + a: "2", + }; + var { ... o_rest_v1 } = { + a: "2", + }; + var { o_v4, o_v5, o_v6 = "1", a : o_v7 = "1", ... o_rest_v2 } = { + o_v4: "1", + o_v5: "1", + }; + var [] = []; + var [ , ] = []; + var [ a_v1 ] = [1, 2, 3]; + var [ a_v2, a_v3 ] = [1, 2, 3]; + var [ a_v2, , a_v3 ] = [1, 2, 3]; + var [ ... a_rest_v1 ] = [1, 2, 3]; + var [ a_v4, , ... a_rest_v2 ] = [1, 2, 3]; + var [ { a_v5 } ] = [{ + a_v5: 1, + }, { + a_v5: 2, + }, { + a_v5: 3, + }]; + "#, + ); +} 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 index b8f14c866b..2b800e7ce0 100644 --- a/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs @@ -6,7 +6,7 @@ use crate::{ }, exec::{Executable, InterpreterState}, gc::{Finalize, Trace}, - syntax::ast::node::Node, + syntax::ast::node::{Declaration, Node}, BoaProfiler, Context, JsValue, Result, }; use std::fmt; @@ -130,15 +130,37 @@ impl Executable for ForInLoop { return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer"); } - if context.has_binding(var.name()) { - context.set_mutable_binding(var.name(), next_result, true)?; - } else { - context.create_mutable_binding( - var.name().to_owned(), - false, - VariableScope::Function, - )?; - context.initialize_binding(var.name(), next_result)?; + match &var { + Declaration::Identifier { ident, .. } => { + if context.has_binding(ident.as_ref()) { + context.set_mutable_binding( + ident.as_ref(), + next_result, + true, + )?; + } else { + context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Function, + )?; + context.initialize_binding(ident.as_ref(), next_result)?; + } + } + Declaration::Pattern(p) => { + for (ident, value) in p.run(Some(next_result), context)? { + if context.has_binding(ident.as_ref()) { + context.set_mutable_binding(ident.as_ref(), value, true)?; + } else { + context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Function, + )?; + context.initialize_binding(ident.as_ref(), value)?; + } + } + } } } _ => { @@ -153,12 +175,26 @@ impl Executable for ForInLoop { return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer"); } - context.create_mutable_binding( - var.name().to_owned(), - false, - VariableScope::Block, - )?; - context.initialize_binding(var.name(), next_result)?; + match &var { + Declaration::Identifier { ident, .. } => { + context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?; + context.initialize_binding(ident.as_ref(), next_result)?; + } + Declaration::Pattern(p) => { + for (ident, value) in p.run(Some(next_result), context)? { + context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?; + context.initialize_binding(ident.as_ref(), value)?; + } + } + } } _ => { return context.throw_syntax_error( @@ -172,12 +208,26 @@ impl Executable for ForInLoop { return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer"); } - context.create_immutable_binding( - var.name().to_owned(), - false, - VariableScope::Block, - )?; - context.initialize_binding(var.name(), next_result)?; + match &var { + Declaration::Identifier { ident, .. } => { + context.create_immutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?; + context.initialize_binding(ident.as_ref(), next_result)?; + } + Declaration::Pattern(p) => { + for (ident, value) in p.run(Some(next_result), context)? { + context.create_immutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?; + context.initialize_binding(ident.as_ref(), value)?; + } + } + } } _ => { return context.throw_syntax_error( 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 aa507adbbf..5552fc81f2 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 @@ -6,7 +6,7 @@ use crate::{ }, exec::{Executable, InterpreterState}, gc::{Finalize, Trace}, - syntax::ast::node::Node, + syntax::ast::node::{Declaration, Node}, BoaProfiler, Context, JsValue, Result, }; use std::fmt; @@ -118,15 +118,37 @@ impl Executable for ForOfLoop { return context.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer"); } - if context.has_binding(var.name()) { - context.set_mutable_binding(var.name(), next_result, true)?; - } else { - context.create_mutable_binding( - var.name().to_owned(), - false, - VariableScope::Function, - )?; - context.initialize_binding(var.name(), next_result)?; + match &var { + Declaration::Identifier { ident, .. } => { + if context.has_binding(ident.as_ref()) { + context.set_mutable_binding( + ident.as_ref(), + next_result, + true, + )?; + } else { + context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Function, + )?; + context.initialize_binding(ident.as_ref(), next_result)?; + } + } + Declaration::Pattern(p) => { + for (ident, value) in p.run(Some(next_result), context)? { + if context.has_binding(ident.as_ref()) { + context.set_mutable_binding(ident.as_ref(), value, true)?; + } else { + context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Function, + )?; + context.initialize_binding(ident.as_ref(), value)?; + } + } + } } } _ => { @@ -141,13 +163,26 @@ impl Executable for ForOfLoop { return context.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer"); } - context.create_mutable_binding( - var.name().to_owned(), - false, - VariableScope::Block, - )?; - - context.initialize_binding(var.name(), next_result)?; + match &var { + Declaration::Identifier { ident, .. } => { + context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?; + context.initialize_binding(ident.as_ref(), next_result)?; + } + Declaration::Pattern(p) => { + for (ident, value) in p.run(Some(next_result), context)? { + context.create_mutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?; + context.initialize_binding(ident.as_ref(), value)?; + } + } + } } _ => { return context.throw_syntax_error( @@ -161,12 +196,26 @@ impl Executable for ForOfLoop { return context.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer"); } - context.create_immutable_binding( - var.name().to_owned(), - false, - VariableScope::Block, - )?; - context.initialize_binding(var.name(), next_result)?; + match &var { + Declaration::Identifier { ident, .. } => { + context.create_immutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?; + context.initialize_binding(ident.as_ref(), next_result)?; + } + Declaration::Pattern(p) => { + for (ident, value) in p.run(Some(next_result), context)? { + context.create_immutable_binding( + ident.to_string(), + false, + VariableScope::Block, + )?; + context.initialize_binding(ident.as_ref(), value)?; + } + } + } } _ => { return context.throw_syntax_error( diff --git a/boa/src/syntax/ast/node/statement_list/mod.rs b/boa/src/syntax/ast/node/statement_list/mod.rs index 579a75a934..1cd8cc5416 100644 --- a/boa/src/syntax/ast/node/statement_list/mod.rs +++ b/boa/src/syntax/ast/node/statement_list/mod.rs @@ -3,7 +3,7 @@ use crate::{ exec::{Executable, InterpreterState}, gc::{empty_trace, Finalize, Trace}, - syntax::ast::node::Node, + syntax::ast::node::{Declaration, Node}, BoaProfiler, Context, JsValue, Result, }; use std::{collections::HashSet, fmt, ops::Deref, rc::Rc}; @@ -57,10 +57,21 @@ impl StatementList { for stmt in self.items() { if let Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) = stmt { for decl in decl_list.as_ref() { - if !set.insert(decl.name()) { - // It is a Syntax Error if the LexicallyDeclaredNames of StatementList contains any duplicate entries. - // https://tc39.es/ecma262/#sec-block-static-semantics-early-errors - unreachable!("Redeclaration of {}", decl.name()); + // It is a Syntax Error if the LexicallyDeclaredNames of StatementList contains any duplicate entries. + // https://tc39.es/ecma262/#sec-block-static-semantics-early-errors + match decl { + Declaration::Identifier { ident, .. } => { + if !set.insert(ident.as_ref()) { + unreachable!("Redeclaration of {}", ident.as_ref()); + } + } + Declaration::Pattern(p) => { + for ident in p.idents() { + if !set.insert(ident) { + unreachable!("Redeclaration of {}", ident); + } + } + } } } } @@ -83,7 +94,16 @@ impl StatementList { for stmt in self.items() { if let Node::VarDeclList(decl_list) = stmt { for decl in decl_list.as_ref() { - set.insert(decl.name()); + match decl { + Declaration::Identifier { ident, .. } => { + set.insert(ident.as_ref()); + } + Declaration::Pattern(p) => { + for ident in p.idents() { + set.insert(ident.as_ref()); + } + } + } } } } diff --git a/boa/src/syntax/parser/expression/primary/async_function_expression/tests.rs b/boa/src/syntax/parser/expression/primary/async_function_expression/tests.rs index 27954a2a7d..111d868f4c 100644 --- a/boa/src/syntax/parser/expression/primary/async_function_expression/tests.rs +++ b/boa/src/syntax/parser/expression/primary/async_function_expression/tests.rs @@ -15,7 +15,7 @@ fn check_async_expression() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "add", Some( AsyncFunctionExpr::new::>, _, StatementList>( @@ -43,14 +43,14 @@ fn check_nested_async_expression() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "a", Some( AsyncFunctionExpr::new::>, _, StatementList>( None, [], vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "b", Some( AsyncFunctionExpr::new::>, _, StatementList>( diff --git a/boa/src/syntax/parser/expression/primary/function_expression/tests.rs b/boa/src/syntax/parser/expression/primary/function_expression/tests.rs index d1f7d03e11..d7c143227a 100644 --- a/boa/src/syntax/parser/expression/primary/function_expression/tests.rs +++ b/boa/src/syntax/parser/expression/primary/function_expression/tests.rs @@ -15,7 +15,7 @@ fn check_function_expression() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "add", Some( FunctionExpr::new::>, _, StatementList>( @@ -43,14 +43,14 @@ fn check_nested_function_expression() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "a", Some( FunctionExpr::new::>, _, StatementList>( None, [], vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "b", Some( FunctionExpr::new::>, _, StatementList>( 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 59f683696d..c01c3a9653 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs @@ -24,7 +24,7 @@ fn check_object_literal() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "x", Some(Object::from(object_properties).into()), )] @@ -53,7 +53,7 @@ fn check_object_short_function() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "x", Some(Object::from(object_properties).into()), )] @@ -86,7 +86,7 @@ fn check_object_short_function_arguments() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "x", Some(Object::from(object_properties).into()), )] @@ -114,7 +114,7 @@ fn check_object_getter() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "x", Some(Object::from(object_properties).into()), )] @@ -146,7 +146,7 @@ fn check_object_setter() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "x", Some(Object::from(object_properties).into()), )] @@ -170,7 +170,7 @@ fn check_object_short_function_get() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "x", Some(Object::from(object_properties).into()), )] @@ -194,7 +194,7 @@ fn check_object_short_function_set() { }; ", vec![DeclarationList::Const( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "x", Some(Object::from(object_properties).into()), )] diff --git a/boa/src/syntax/parser/function/tests.rs b/boa/src/syntax/parser/function/tests.rs index b6464dfce5..cd66a0d54c 100644 --- a/boa/src/syntax/parser/function/tests.rs +++ b/boa/src/syntax/parser/function/tests.rs @@ -193,7 +193,7 @@ fn check_arrow_assignment() { check_parser( "let foo = (a) => { return a };", vec![DeclarationList::Let( - vec![Declaration::new( + vec![Declaration::new_with_identifier( Identifier::from("foo"), Some( ArrowFunctionDecl::new( @@ -218,7 +218,7 @@ fn check_arrow_assignment_nobrackets() { check_parser( "let foo = (a) => a;", vec![DeclarationList::Let( - vec![Declaration::new( + vec![Declaration::new_with_identifier( Identifier::from("foo"), Some( ArrowFunctionDecl::new( @@ -243,7 +243,7 @@ fn check_arrow_assignment_noparenthesis() { check_parser( "let foo = a => { return a };", vec![DeclarationList::Let( - vec![Declaration::new( + vec![Declaration::new_with_identifier( Identifier::from("foo"), Some( ArrowFunctionDecl::new( @@ -268,7 +268,7 @@ fn check_arrow_assignment_noparenthesis_nobrackets() { check_parser( "let foo = a => a;", vec![DeclarationList::Let( - vec![Declaration::new( + vec![Declaration::new_with_identifier( Identifier::from("foo"), Some( ArrowFunctionDecl::new( @@ -293,7 +293,7 @@ fn check_arrow_assignment_2arg() { check_parser( "let foo = (a, b) => { return a };", vec![DeclarationList::Let( - vec![Declaration::new( + vec![Declaration::new_with_identifier( Identifier::from("foo"), Some( ArrowFunctionDecl::new( @@ -321,7 +321,7 @@ fn check_arrow_assignment_2arg_nobrackets() { check_parser( "let foo = (a, b) => a;", vec![DeclarationList::Let( - vec![Declaration::new( + vec![Declaration::new_with_identifier( Identifier::from("foo"), Some( ArrowFunctionDecl::new( @@ -349,7 +349,7 @@ fn check_arrow_assignment_3arg() { check_parser( "let foo = (a, b, c) => { return a };", vec![DeclarationList::Let( - vec![Declaration::new( + vec![Declaration::new_with_identifier( Identifier::from("foo"), Some( ArrowFunctionDecl::new( @@ -378,7 +378,7 @@ fn check_arrow_assignment_3arg_nobrackets() { check_parser( "let foo = (a, b, c) => a;", vec![DeclarationList::Let( - vec![Declaration::new( + vec![Declaration::new_with_identifier( Identifier::from("foo"), Some( ArrowFunctionDecl::new( diff --git a/boa/src/syntax/parser/statement/block/tests.rs b/boa/src/syntax/parser/statement/block/tests.rs index d5f4cf7e8f..25d8418e5c 100644 --- a/boa/src/syntax/parser/statement/block/tests.rs +++ b/boa/src/syntax/parser/statement/block/tests.rs @@ -33,8 +33,14 @@ fn non_empty() { a++; }", vec![ - DeclarationList::Var(vec![Declaration::new("a", Some(Const::from(10).into()))].into()) + DeclarationList::Var( + vec![Declaration::new_with_identifier( + "a", + Some(Const::from(10).into()), + )] .into(), + ) + .into(), UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(), ], ); @@ -56,7 +62,7 @@ fn non_empty() { ) .into(), DeclarationList::Var( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "a", Node::from(Call::new(Identifier::from("hello"), vec![])), )] @@ -85,7 +91,7 @@ fn hoisting() { ) .into(), DeclarationList::Var( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "a", Node::from(Call::new(Identifier::from("hello"), vec![])), )] @@ -106,7 +112,7 @@ fn hoisting() { vec![ Assign::new(Identifier::from("a"), Const::from(10)).into(), UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(), - DeclarationList::Var(vec![Declaration::new("a", None)].into()).into(), + DeclarationList::Var(vec![Declaration::new_with_identifier("a", None)].into()).into(), ], ); } diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs index 49af23b727..aca9b7bd1a 100644 --- a/boa/src/syntax/parser/statement/declaration/lexical.rs +++ b/boa/src/syntax/parser/statement/declaration/lexical.rs @@ -7,7 +7,6 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations -use crate::syntax::lexer::TokenKind; use crate::{ syntax::{ ast::{ @@ -17,10 +16,11 @@ use crate::{ }, Keyword, Punctuator, }, + lexer::TokenKind, parser::{ cursor::{Cursor, SemicolonResult}, expression::Initializer, - statement::BindingIdentifier, + statement::{ArrayBindingPattern, BindingIdentifier, ObjectBindingPattern}, AllowAwait, AllowIn, AllowYield, ParseError, ParseResult, TokenParser, }, }, @@ -154,14 +154,19 @@ where let mut const_decls = Vec::new(); loop { - let (ident, init) = - LexicalBinding::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor)?; + let decl = LexicalBinding::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; if self.is_const { if self.const_init_required { - if init.is_some() { - const_decls.push(Declaration::new(ident, init)); + let init_is_some = match &decl { + Declaration::Identifier { init, .. } if init.is_some() => true, + Declaration::Pattern(p) if p.init().is_some() => true, + _ => false, + }; + + if init_is_some { + const_decls.push(decl); } else { return Err(ParseError::expected( vec![TokenKind::Punctuator(Punctuator::Assign)], @@ -170,10 +175,10 @@ where )); } } else { - const_decls.push(Declaration::new(ident, init)) + const_decls.push(decl) } } else { - let_decls.push(Declaration::new(ident, init)); + let_decls.push(decl); } match cursor.peek_semicolon()? { @@ -243,26 +248,74 @@ impl TokenParser for LexicalBinding where R: Read, { - type Output = (Box, Option); + type Output = Declaration; fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("LexicalBinding", "Parsing"); - let ident = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + let peek_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; - let init = if let Some(t) = cursor.peek(0)? { - if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { - Some( - Initializer::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor)?, - ) - } else { - None + match peek_token.kind() { + TokenKind::Punctuator(Punctuator::OpenBlock) => { + let bindings = + ObjectBindingPattern::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + + let init = if let Some(t) = cursor.peek(0)? { + if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { + Some( + Initializer::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?, + ) + } else { + None + } + } else { + None + }; + + Ok(Declaration::new_with_object_pattern(bindings, init)) } - } else { - None - }; + TokenKind::Punctuator(Punctuator::OpenBracket) => { + let bindings = + ArrayBindingPattern::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; - Ok((ident, init)) + let init = if let Some(t) = cursor.peek(0)? { + if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { + Some( + Initializer::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?, + ) + } else { + None + } + } else { + None + }; + + Ok(Declaration::new_with_array_pattern(bindings, init)) + } + + _ => { + let ident = + BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + + let init = if let Some(t) = cursor.peek(0)? { + if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { + Some( + Initializer::new(true, self.allow_yield, self.allow_await) + .parse(cursor)?, + ) + } else { + None + } + } else { + None + }; + + Ok(Declaration::new_with_identifier(ident, init)) + } + } } } diff --git a/boa/src/syntax/parser/statement/declaration/tests.rs b/boa/src/syntax/parser/statement/declaration/tests.rs index 03168fcd44..c7d9fb8f03 100644 --- a/boa/src/syntax/parser/statement/declaration/tests.rs +++ b/boa/src/syntax/parser/statement/declaration/tests.rs @@ -11,10 +11,14 @@ use crate::syntax::{ fn var_declaration() { check_parser( "var a = 5;", - vec![ - DeclarationList::Var(vec![Declaration::new("a", Some(Const::from(5).into()))].into()) - .into(), - ], + vec![DeclarationList::Var( + vec![Declaration::new_with_identifier( + "a", + Some(Const::from(5).into()), + )] + .into(), + ) + .into()], ); } @@ -24,7 +28,11 @@ fn var_declaration_keywords() { check_parser( "var yield = 5;", vec![DeclarationList::Var( - vec![Declaration::new("yield", Some(Const::from(5).into()))].into(), + vec![Declaration::new_with_identifier( + "yield", + Some(Const::from(5).into()), + )] + .into(), ) .into()], ); @@ -32,7 +40,11 @@ fn var_declaration_keywords() { check_parser( "var await = 5;", vec![DeclarationList::Var( - vec![Declaration::new("await", Some(Const::from(5).into()))].into(), + vec![Declaration::new_with_identifier( + "await", + Some(Const::from(5).into()), + )] + .into(), ) .into()], ); @@ -43,10 +55,14 @@ fn var_declaration_keywords() { fn var_declaration_no_spaces() { check_parser( "var a=5;", - vec![ - DeclarationList::Var(vec![Declaration::new("a", Some(Const::from(5).into()))].into()) - .into(), - ], + vec![DeclarationList::Var( + vec![Declaration::new_with_identifier( + "a", + Some(Const::from(5).into()), + )] + .into(), + ) + .into()], ); } @@ -55,7 +71,7 @@ fn var_declaration_no_spaces() { fn empty_var_declaration() { check_parser( "var a;", - vec![DeclarationList::Var(vec![Declaration::new("a", None)].into()).into()], + vec![DeclarationList::Var(vec![Declaration::new_with_identifier("a", None)].into()).into()], ); } @@ -66,9 +82,9 @@ fn multiple_var_declaration() { "var a = 5, b, c = 6;", vec![DeclarationList::Var( vec![ - Declaration::new("a", Some(Const::from(5).into())), - Declaration::new("b", None), - Declaration::new("c", Some(Const::from(6).into())), + Declaration::new_with_identifier("a", Some(Const::from(5).into())), + Declaration::new_with_identifier("b", None), + Declaration::new_with_identifier("c", Some(Const::from(6).into())), ] .into(), ) @@ -81,10 +97,14 @@ fn multiple_var_declaration() { fn let_declaration() { check_parser( "let a = 5;", - vec![ - DeclarationList::Let(vec![Declaration::new("a", Node::from(Const::from(5)))].into()) - .into(), - ], + vec![DeclarationList::Let( + vec![Declaration::new_with_identifier( + "a", + Node::from(Const::from(5)), + )] + .into(), + ) + .into()], ); } @@ -94,7 +114,11 @@ fn let_declaration_keywords() { check_parser( "let yield = 5;", vec![DeclarationList::Let( - vec![Declaration::new("yield", Node::from(Const::from(5)))].into(), + vec![Declaration::new_with_identifier( + "yield", + Node::from(Const::from(5)), + )] + .into(), ) .into()], ); @@ -102,7 +126,11 @@ fn let_declaration_keywords() { check_parser( "let await = 5;", vec![DeclarationList::Let( - vec![Declaration::new("await", Node::from(Const::from(5)))].into(), + vec![Declaration::new_with_identifier( + "await", + Node::from(Const::from(5)), + )] + .into(), ) .into()], ); @@ -113,10 +141,14 @@ fn let_declaration_keywords() { fn let_declaration_no_spaces() { check_parser( "let a=5;", - vec![ - DeclarationList::Let(vec![Declaration::new("a", Node::from(Const::from(5)))].into()) - .into(), - ], + vec![DeclarationList::Let( + vec![Declaration::new_with_identifier( + "a", + Node::from(Const::from(5)), + )] + .into(), + ) + .into()], ); } @@ -125,7 +157,7 @@ fn let_declaration_no_spaces() { fn empty_let_declaration() { check_parser( "let a;", - vec![DeclarationList::Let(vec![Declaration::new("a", None)].into()).into()], + vec![DeclarationList::Let(vec![Declaration::new_with_identifier("a", None)].into()).into()], ); } @@ -136,9 +168,9 @@ fn multiple_let_declaration() { "let a = 5, b, c = 6;", vec![DeclarationList::Let( vec![ - Declaration::new("a", Node::from(Const::from(5))), - Declaration::new("b", None), - Declaration::new("c", Node::from(Const::from(6))), + Declaration::new_with_identifier("a", Node::from(Const::from(5))), + Declaration::new_with_identifier("b", None), + Declaration::new_with_identifier("c", Node::from(Const::from(6))), ] .into(), ) @@ -152,7 +184,11 @@ fn const_declaration() { check_parser( "const a = 5;", vec![DeclarationList::Const( - vec![Declaration::new("a", Node::from(Const::from(5)))].into(), + vec![Declaration::new_with_identifier( + "a", + Node::from(Const::from(5)), + )] + .into(), ) .into()], ); @@ -164,7 +200,11 @@ fn const_declaration_keywords() { check_parser( "const yield = 5;", vec![DeclarationList::Const( - vec![Declaration::new("yield", Node::from(Const::from(5)))].into(), + vec![Declaration::new_with_identifier( + "yield", + Node::from(Const::from(5)), + )] + .into(), ) .into()], ); @@ -172,7 +212,11 @@ fn const_declaration_keywords() { check_parser( "const await = 5;", vec![DeclarationList::Const( - vec![Declaration::new("await", Node::from(Const::from(5)))].into(), + vec![Declaration::new_with_identifier( + "await", + Node::from(Const::from(5)), + )] + .into(), ) .into()], ); @@ -184,7 +228,11 @@ fn const_declaration_no_spaces() { check_parser( "const a=5;", vec![DeclarationList::Const( - vec![Declaration::new("a", Node::from(Const::from(5)))].into(), + vec![Declaration::new_with_identifier( + "a", + Node::from(Const::from(5)), + )] + .into(), ) .into()], ); @@ -203,8 +251,8 @@ fn multiple_const_declaration() { "const a = 5, c = 6;", vec![DeclarationList::Const( vec![ - Declaration::new("a", Node::from(Const::from(5))), - Declaration::new("c", Node::from(Const::from(6))), + Declaration::new_with_identifier("a", Node::from(Const::from(5))), + Declaration::new_with_identifier("c", Node::from(Const::from(6))), ] .into(), ) diff --git a/boa/src/syntax/parser/statement/iteration/tests.rs b/boa/src/syntax/parser/statement/iteration/tests.rs index 7c3f41d530..b4854984b8 100644 --- a/boa/src/syntax/parser/statement/iteration/tests.rs +++ b/boa/src/syntax/parser/statement/iteration/tests.rs @@ -37,8 +37,14 @@ fn check_do_while_semicolon_insertion() { r#"var i = 0; do {console.log("hello");} while(i++ < 10) console.log("end");"#, vec![ - DeclarationList::Var(vec![Declaration::new("i", Some(Const::from(0).into()))].into()) + DeclarationList::Var( + vec![Declaration::new_with_identifier( + "i", + Some(Const::from(0).into()), + )] .into(), + ) + .into(), DoWhileLoop::new( Block::from(vec![Call::new( GetConstField::new(Identifier::from("console"), "log"), @@ -69,8 +75,14 @@ fn check_do_while_semicolon_insertion_no_space() { r#"var i = 0; do {console.log("hello");} while(i++ < 10)console.log("end");"#, vec![ - DeclarationList::Var(vec![Declaration::new("i", Some(Const::from(0).into()))].into()) + DeclarationList::Var( + vec![Declaration::new_with_identifier( + "i", + Some(Const::from(0).into()), + )] .into(), + ) + .into(), DoWhileLoop::new( Block::from(vec![Call::new( GetConstField::new(Identifier::from("console"), "log"), diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index d77eb58bde..6e42f34e86 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -35,12 +35,24 @@ use self::{ try_stm::TryStatement, variable::VariableStatement, }; +use crate::syntax::{ + ast::node::declaration::{ + DeclarationPattern, DeclarationPatternArray, DeclarationPatternObject, + }, + parser::expression::Initializer, +}; -use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}; +use super::{AllowAwait, AllowIn, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}; use crate::{ syntax::{ - ast::{node, Keyword, Node, Punctuator}, + ast::{ + node::{ + self, + declaration::{BindingPatternTypeArray, BindingPatternTypeObject}, + }, + Keyword, Node, Punctuator, + }, lexer::{Error as LexError, InputElement, Position, TokenKind}, parser::expression::await_expr::AwaitExpression, }, @@ -48,8 +60,8 @@ use crate::{ }; use labelled_stm::LabelledStatement; -use std::collections::HashSet; use std::io::Read; +use std::{collections::HashSet, vec}; /// Statement parsing. /// @@ -303,33 +315,82 @@ where for decl in decl_list.as_ref() { // if name in VarDeclaredNames or can't be added to // LexicallyDeclaredNames, raise an error - if var_declared_names.contains(decl.name()) - || !lexically_declared_names.insert(decl.name()) - { - return Err(ParseError::lex(LexError::Syntax( - format!("Redeclaration of variable `{}`", decl.name()).into(), - match cursor.peek(0)? { - Some(token) => token.span().end(), - None => Position::new(1, 1), - }, - ))); + match decl { + node::Declaration::Identifier { ident, .. } => { + if var_declared_names.contains(ident.as_ref()) + || !lexically_declared_names.insert(ident.as_ref()) + { + return Err(ParseError::lex(LexError::Syntax( + format!( + "Redeclaration of variable `{}`", + ident.as_ref() + ) + .into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + node::Declaration::Pattern(p) => { + for ident in p.idents() { + if var_declared_names.contains(ident) + || !lexically_declared_names.insert(ident.as_ref()) + { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of variable `{}`", ident) + .into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + } } } } Node::VarDeclList(decl_list) => { for decl in decl_list.as_ref() { - // if name in LexicallyDeclaredNames, raise an error - if lexically_declared_names.contains(decl.name()) { - return Err(ParseError::lex(LexError::Syntax( - format!("Redeclaration of variable `{}`", decl.name()).into(), - match cursor.peek(0)? { - Some(token) => token.span().end(), - None => Position::new(1, 1), - }, - ))); + match decl { + node::Declaration::Identifier { ident, .. } => { + // if name in LexicallyDeclaredNames, raise an error + if lexically_declared_names.contains(ident.as_ref()) { + return Err(ParseError::lex(LexError::Syntax( + format!( + "Redeclaration of variable `{}`", + ident.as_ref() + ) + .into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + // otherwise, add to VarDeclaredNames + var_declared_names.insert(ident.as_ref()); + } + node::Declaration::Pattern(p) => { + for ident in p.idents() { + // if name in LexicallyDeclaredNames, raise an error + if lexically_declared_names.contains(ident) { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of variable `{}`", ident) + .into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + // otherwise, add to VarDeclaredNames + var_declared_names.insert(ident.as_ref()); + } + } } - // otherwise, add to VarDeclaredNames - var_declared_names.insert(decl.name()); } } _ => (), @@ -487,3 +548,487 @@ where } } } + +/// ObjectBindingPattern pattern parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ObjectBindingPattern +#[derive(Debug, Clone, Copy)] +pub(super) struct ObjectBindingPattern { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ObjectBindingPattern { + /// Creates a new `ObjectBindingPattern` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ObjectBindingPattern +where + R: Read, +{ + type Output = Vec; + + fn parse(self, cursor: &mut Cursor) -> Result { + let _timer = BoaProfiler::global().start_event("ObjectBindingPattern", "Parsing"); + + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "object binding pattern", + )?; + + let mut patterns = Vec::new(); + let mut property_names = Vec::new(); + let mut rest_property_name = None; + + loop { + let property_name = match cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() { + TokenKind::Punctuator(Punctuator::CloseBlock) => { + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "object binding pattern", + )?; + break; + } + TokenKind::Punctuator(Punctuator::Spread) => { + cursor.expect( + TokenKind::Punctuator(Punctuator::Spread), + "object binding pattern", + )?; + rest_property_name = Some( + BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?, + ); + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "object binding pattern", + )?; + break; + } + _ => BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?, + }; + + property_names.push(property_name.clone()); + + if let Some(peek_token) = cursor.peek(0)? { + match peek_token.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + let init = + Initializer::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + patterns.push(BindingPatternTypeObject::SingleName { + ident: property_name.clone(), + property_name, + default_init: Some(init), + }); + } + TokenKind::Punctuator(Punctuator::Colon) => { + cursor.expect( + TokenKind::Punctuator(Punctuator::Colon), + "object binding pattern", + )?; + + if let Some(peek_token) = cursor.peek(0)? { + match peek_token.kind() { + TokenKind::Punctuator(Punctuator::OpenBlock) => { + let bindings = ObjectBindingPattern::new( + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor)?; + + if let Some(peek_token) = cursor.peek(0)? { + match peek_token.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + let init = Initializer::new( + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor)?; + patterns.push( + BindingPatternTypeObject::BindingPattern { + ident: property_name, + pattern: DeclarationPattern::Object( + DeclarationPatternObject::new( + bindings, None, + ), + ), + default_init: Some(init), + }, + ); + } + _ => { + patterns.push( + BindingPatternTypeObject::BindingPattern { + ident: property_name, + pattern: DeclarationPattern::Object( + DeclarationPatternObject::new( + bindings, None, + ), + ), + default_init: None, + }, + ); + } + } + } + } + TokenKind::Punctuator(Punctuator::OpenBracket) => { + let bindings = ArrayBindingPattern::new( + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor)?; + + if let Some(peek_token) = cursor.peek(0)? { + match peek_token.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + let init = Initializer::new( + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor)?; + patterns.push( + BindingPatternTypeObject::BindingPattern { + ident: property_name, + pattern: DeclarationPattern::Array( + DeclarationPatternArray::new( + bindings, None, + ), + ), + default_init: Some(init), + }, + ); + } + _ => { + patterns.push( + BindingPatternTypeObject::BindingPattern { + ident: property_name, + pattern: DeclarationPattern::Array( + DeclarationPatternArray::new( + bindings, None, + ), + ), + default_init: None, + }, + ); + } + } + } + } + _ => { + // TODO: Currently parses only BindingIdentifier. + // Should parse https://tc39.es/ecma262/#prod-PropertyName + let ident = + BindingIdentifier::new(self.allow_yield, self.allow_await) + .parse(cursor)?; + + if let Some(peek_token) = cursor.peek(0)? { + match peek_token.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + let init = Initializer::new( + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor)?; + patterns.push( + BindingPatternTypeObject::SingleName { + ident, + property_name, + default_init: Some(init), + }, + ); + } + _ => { + patterns.push( + BindingPatternTypeObject::SingleName { + ident, + property_name, + default_init: None, + }, + ); + } + } + } + } + } + } + } + _ => { + patterns.push(BindingPatternTypeObject::SingleName { + ident: property_name.clone(), + property_name, + default_init: None, + }); + } + } + } + + if let Some(peek_token) = cursor.peek(0)? { + if let TokenKind::Punctuator(Punctuator::Comma) = peek_token.kind() { + cursor.expect( + TokenKind::Punctuator(Punctuator::Comma), + "object binding pattern", + )?; + } + } + } + + if let Some(rest) = rest_property_name { + if patterns.is_empty() { + Ok(vec![BindingPatternTypeObject::RestProperty { + ident: rest, + excluded_keys: property_names, + }]) + } else { + patterns.push(BindingPatternTypeObject::RestProperty { + ident: rest, + excluded_keys: property_names, + }); + Ok(patterns) + } + } else if patterns.is_empty() { + Ok(vec![BindingPatternTypeObject::Empty]) + } else { + Ok(patterns) + } + } +} + +/// ArrayBindingPattern pattern parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ArrayBindingPattern +#[derive(Debug, Clone, Copy)] +pub(super) struct ArrayBindingPattern { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ArrayBindingPattern { + /// Creates a new `ArrayBindingPattern` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ArrayBindingPattern +where + R: Read, +{ + type Output = Vec; + + fn parse(self, cursor: &mut Cursor) -> Result { + let _timer = BoaProfiler::global().start_event("ArrayBindingPattern", "Parsing"); + + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBracket), + "array binding pattern", + )?; + + let mut patterns = Vec::new(); + let mut last_elision_or_first = true; + + loop { + match cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() { + TokenKind::Punctuator(Punctuator::CloseBracket) => { + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBracket), + "array binding pattern", + )?; + break; + } + TokenKind::Punctuator(Punctuator::Comma) => { + cursor.expect( + TokenKind::Punctuator(Punctuator::Comma), + "array binding pattern", + )?; + if last_elision_or_first { + patterns.push(BindingPatternTypeArray::Elision); + } else { + last_elision_or_first = true; + } + continue; + } + TokenKind::Punctuator(Punctuator::Spread) => { + cursor.expect( + TokenKind::Punctuator(Punctuator::Spread), + "array binding pattern", + )?; + + match cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() { + TokenKind::Punctuator(Punctuator::OpenBlock) => { + let bindings = ObjectBindingPattern::new( + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor)?; + patterns.push(BindingPatternTypeArray::BindingPatternRest { + pattern: DeclarationPattern::Object(DeclarationPatternObject::new( + bindings, None, + )), + }); + } + TokenKind::Punctuator(Punctuator::OpenBracket) => { + let bindings = ArrayBindingPattern::new( + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor)?; + patterns.push(BindingPatternTypeArray::BindingPatternRest { + pattern: DeclarationPattern::Array(DeclarationPatternArray::new( + bindings, None, + )), + }); + } + _ => { + let rest_property_name = + BindingIdentifier::new(self.allow_yield, self.allow_await) + .parse(cursor)?; + patterns.push(BindingPatternTypeArray::SingleNameRest { + ident: rest_property_name, + }); + } + } + + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBracket), + "array binding pattern", + )?; + break; + } + TokenKind::Punctuator(Punctuator::OpenBlock) => { + last_elision_or_first = false; + + let bindings = ObjectBindingPattern::new( + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor)?; + + match cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + let default_init = + Initializer::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + patterns.push(BindingPatternTypeArray::BindingPattern { + pattern: DeclarationPattern::Object(DeclarationPatternObject::new( + bindings, + Some(default_init), + )), + }); + } + _ => { + patterns.push(BindingPatternTypeArray::BindingPattern { + pattern: DeclarationPattern::Object(DeclarationPatternObject::new( + bindings, None, + )), + }); + } + } + } + TokenKind::Punctuator(Punctuator::OpenBracket) => { + last_elision_or_first = false; + + let bindings = + ArrayBindingPattern::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + + match cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + let default_init = + Initializer::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + patterns.push(BindingPatternTypeArray::BindingPattern { + pattern: DeclarationPattern::Array(DeclarationPatternArray::new( + bindings, + Some(default_init), + )), + }); + } + _ => { + patterns.push(BindingPatternTypeArray::BindingPattern { + pattern: DeclarationPattern::Array(DeclarationPatternArray::new( + bindings, None, + )), + }); + } + } + } + _ => { + last_elision_or_first = false; + + let ident = + BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + match cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + let default_init = + Initializer::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + patterns.push(BindingPatternTypeArray::SingleName { + ident, + default_init: Some(default_init), + }) + } + _ => { + patterns.push(BindingPatternTypeArray::SingleName { + ident, + default_init: None, + }); + } + } + } + } + + if let Some(peek_token) = cursor.peek(0)? { + if let TokenKind::Punctuator(Punctuator::Comma) = peek_token.kind() { + cursor.expect( + TokenKind::Punctuator(Punctuator::Comma), + "array binding pattern", + )?; + if last_elision_or_first { + patterns.push(BindingPatternTypeArray::Elision); + } else { + last_elision_or_first = true; + } + } + } + } + + Ok(patterns) + } +} diff --git a/boa/src/syntax/parser/statement/switch/tests.rs b/boa/src/syntax/parser/statement/switch/tests.rs index bdc49c85f1..18a6c52cee 100644 --- a/boa/src/syntax/parser/statement/switch/tests.rs +++ b/boa/src/syntax/parser/statement/switch/tests.rs @@ -151,8 +151,14 @@ fn check_seperated_switch() { check_parser( s, vec![ - DeclarationList::Let(vec![Declaration::new("a", Node::from(Const::from(10)))].into()) + DeclarationList::Let( + vec![Declaration::new_with_identifier( + "a", + Node::from(Const::from(10)), + )] .into(), + ) + .into(), Switch::new( Identifier::from("a"), vec![ diff --git a/boa/src/syntax/parser/statement/try_stm/tests.rs b/boa/src/syntax/parser/statement/try_stm/tests.rs index 58efb61879..ef5464a60d 100644 --- a/boa/src/syntax/parser/statement/try_stm/tests.rs +++ b/boa/src/syntax/parser/statement/try_stm/tests.rs @@ -20,7 +20,11 @@ fn check_inline_with_var_decl_inside_try() { "try { var x = 1; } catch(e) {}", vec![Try::new( vec![DeclarationList::Var( - vec![Declaration::new("x", Some(Const::from(1).into()))].into(), + vec![Declaration::new_with_identifier( + "x", + Some(Const::from(1).into()), + )] + .into(), ) .into()], Some(Catch::new("e", vec![])), @@ -36,13 +40,21 @@ fn check_inline_with_var_decl_inside_catch() { "try { var x = 1; } catch(e) { var x = 1; }", vec![Try::new( vec![DeclarationList::Var( - vec![Declaration::new("x", Some(Const::from(1).into()))].into(), + vec![Declaration::new_with_identifier( + "x", + Some(Const::from(1).into()), + )] + .into(), ) .into()], Some(Catch::new( "e", vec![DeclarationList::Var( - vec![Declaration::new("x", Some(Const::from(1).into()))].into(), + vec![Declaration::new_with_identifier( + "x", + Some(Const::from(1).into()), + )] + .into(), ) .into()], )), @@ -81,7 +93,11 @@ fn check_inline_with_empty_try_var_decl_in_finally() { vec![], None, Some(Finally::from(vec![DeclarationList::Var( - vec![Declaration::new("x", Some(Const::from(1).into()))].into(), + vec![Declaration::new_with_identifier( + "x", + Some(Const::from(1).into()), + )] + .into(), ) .into()])), ) @@ -98,7 +114,11 @@ fn check_inline_empty_try_paramless_catch() { Some(Catch::new::<_, Identifier, _>( None, vec![DeclarationList::Var( - vec![Declaration::new("x", Some(Const::from(1).into()))].into(), + vec![Declaration::new_with_identifier( + "x", + Some(Const::from(1).into()), + )] + .into(), ) .into()], )), diff --git a/boa/src/syntax/parser/statement/variable/mod.rs b/boa/src/syntax/parser/statement/variable/mod.rs index db4adabad5..975be82187 100644 --- a/boa/src/syntax/parser/statement/variable/mod.rs +++ b/boa/src/syntax/parser/statement/variable/mod.rs @@ -7,6 +7,7 @@ use crate::{ Keyword, Punctuator, }, lexer::TokenKind, + parser::statement::{ArrayBindingPattern, ObjectBindingPattern}, parser::{ cursor::{Cursor, SemicolonResult}, expression::Initializer, @@ -167,20 +168,69 @@ where type Output = Declaration; fn parse(self, cursor: &mut Cursor) -> Result { - // TODO: BindingPattern - - let name = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; - - let init = if let Some(t) = cursor.peek(0)? { - if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { - Some(Initializer::new(true, self.allow_yield, self.allow_await).parse(cursor)?) - } else { - None + let peek_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; + + match peek_token.kind() { + TokenKind::Punctuator(Punctuator::OpenBlock) => { + let bindings = + ObjectBindingPattern::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + + let init = if let Some(t) = cursor.peek(0)? { + if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { + Some( + Initializer::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?, + ) + } else { + None + } + } else { + None + }; + + Ok(Declaration::new_with_object_pattern(bindings, init)) + } + TokenKind::Punctuator(Punctuator::OpenBracket) => { + let bindings = + ArrayBindingPattern::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + + let init = if let Some(t) = cursor.peek(0)? { + if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { + Some( + Initializer::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?, + ) + } else { + None + } + } else { + None + }; + + Ok(Declaration::new_with_array_pattern(bindings, init)) } - } else { - None - }; - Ok(Declaration::new(name, init)) + _ => { + let ident = + BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + + let init = if let Some(t) = cursor.peek(0)? { + if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { + Some( + Initializer::new(true, self.allow_yield, self.allow_await) + .parse(cursor)?, + ) + } else { + None + } + } else { + None + }; + + Ok(Declaration::new_with_identifier(ident, init)) + } + } } } diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index db4ae8c832..b571e06318 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -74,7 +74,7 @@ fn hoisting() { ) .into(), DeclarationList::Var( - vec![Declaration::new( + vec![Declaration::new_with_identifier( "a", Node::from(Call::new(Identifier::from("hello"), vec![])), )] @@ -94,7 +94,7 @@ fn hoisting() { vec![ Assign::new(Identifier::from("a"), Const::from(10)).into(), UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(), - DeclarationList::Var(vec![Declaration::new("a", None)].into()).into(), + DeclarationList::Var(vec![Declaration::new_with_identifier("a", None)].into()).into(), ], ); } @@ -144,7 +144,7 @@ fn comment_semi_colon_insertion() { s, vec![ DeclarationList::Let( - vec![Declaration::new::<&str, Option>( + vec![Declaration::new_with_identifier::<&str, Option>( "a", Some(Const::Int(10).into()), )] @@ -152,7 +152,7 @@ fn comment_semi_colon_insertion() { ) .into(), DeclarationList::Let( - vec![Declaration::new::<&str, Option>( + vec![Declaration::new_with_identifier::<&str, Option>( "b", Some(Const::Int(20).into()), )] @@ -176,7 +176,7 @@ fn multiline_comment_semi_colon_insertion() { s, vec![ DeclarationList::Let( - vec![Declaration::new::<&str, Option>( + vec![Declaration::new_with_identifier::<&str, Option>( "a", Some(Const::Int(10).into()), )] @@ -184,7 +184,7 @@ fn multiline_comment_semi_colon_insertion() { ) .into(), DeclarationList::Let( - vec![Declaration::new::<&str, Option>( + vec![Declaration::new_with_identifier::<&str, Option>( "b", Some(Const::Int(20).into()), )] @@ -205,7 +205,7 @@ fn multiline_comment_no_lineterminator() { s, vec![ DeclarationList::Let( - vec![Declaration::new::<&str, Option>( + vec![Declaration::new_with_identifier::<&str, Option>( "a", Some(Const::Int(10).into()), )] @@ -213,7 +213,7 @@ fn multiline_comment_no_lineterminator() { ) .into(), DeclarationList::Let( - vec![Declaration::new::<&str, Option>( + vec![Declaration::new_with_identifier::<&str, Option>( "b", Some(Const::Int(20).into()), )] @@ -237,7 +237,7 @@ fn assignment_line_terminator() { s, vec![ DeclarationList::Let( - vec![Declaration::new::<&str, Option>( + vec![Declaration::new_with_identifier::<&str, Option>( "a", Some(Const::Int(3).into()), )] @@ -265,7 +265,7 @@ fn assignment_multiline_terminator() { s, vec![ DeclarationList::Let( - vec![Declaration::new::<&str, Option>( + vec![Declaration::new_with_identifier::<&str, Option>( "a", Some(Const::Int(3).into()), )] @@ -328,8 +328,14 @@ fn empty_statement() { ", vec![ Node::Empty, - DeclarationList::Var(vec![Declaration::new("a", Node::from(Const::from(10)))].into()) + DeclarationList::Var( + vec![Declaration::new_with_identifier( + "a", + Node::from(Const::from(10)), + )] .into(), + ) + .into(), Node::If(If::new::<_, _, Node, _>( Identifier::from("a"), Node::Empty,