Browse Source

Implement destructuring assignments (#1406)

pull/1332/head
raskad 3 years ago committed by GitHub
parent
commit
85aa61906a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 89
      boa/src/bytecompiler.rs
  2. 71
      boa/src/object/gcobject.rs
  3. 2
      boa/src/property/mod.rs
  4. 910
      boa/src/syntax/ast/node/declaration/mod.rs
  5. 41
      boa/src/syntax/ast/node/declaration/tests.rs
  6. 94
      boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs
  7. 95
      boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs
  8. 32
      boa/src/syntax/ast/node/statement_list/mod.rs
  9. 6
      boa/src/syntax/parser/expression/primary/async_function_expression/tests.rs
  10. 6
      boa/src/syntax/parser/expression/primary/function_expression/tests.rs
  11. 14
      boa/src/syntax/parser/expression/primary/object_initializer/tests.rs
  12. 16
      boa/src/syntax/parser/function/tests.rs
  13. 14
      boa/src/syntax/parser/statement/block/tests.rs
  14. 99
      boa/src/syntax/parser/statement/declaration/lexical.rs
  15. 116
      boa/src/syntax/parser/statement/declaration/tests.rs
  16. 16
      boa/src/syntax/parser/statement/iteration/tests.rs
  17. 593
      boa/src/syntax/parser/statement/mod.rs
  18. 8
      boa/src/syntax/parser/statement/switch/tests.rs
  19. 30
      boa/src/syntax/parser/statement/try_stm/tests.rs
  20. 76
      boa/src/syntax/parser/statement/variable/mod.rs
  21. 28
      boa/src/syntax/parser/tests.rs

89
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) => {

71
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<K>(
&mut self,
source: &JsValue,
excluded_keys: Vec<K>,
context: &mut Context,
) -> Result<()>
where
K: Into<PropertyKey>,
{
// 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<PropertyKey> = 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<GcCell<Object>> for GcObject {

2
boa/src/property/mod.rs

@ -472,7 +472,7 @@ impl From<PropertyDescriptorBuilder> 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),

910
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<Declaration> 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<Node>,
pub enum Declaration {
Identifier {
ident: Identifier,
init: Option<Node>,
},
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<N, I>(name: N, init: I) -> Self
/// Creates a new variable declaration with a BindingIdentifier.
#[inline]
pub(in crate::syntax) fn new_with_identifier<N, I>(ident: N, init: I) -> Self
where
N: Into<Identifier>,
I: Into<Option<Node>>,
{
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<I>(
bindings: Vec<BindingPatternTypeObject>,
init: I,
) -> Self
where
I: Into<Option<Node>>,
{
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<I>(
bindings: Vec<BindingPatternTypeArray>,
init: I,
) -> Self
where
I: Into<Option<Node>>,
{
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<JsValue>,
context: &mut Context,
) -> Result<Vec<(Box<str>, 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<BindingPatternTypeObject>,
init: Option<Node>,
}
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<BindingPatternTypeObject>,
init: Option<Node>,
) -> 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<JsValue>,
context: &mut Context,
) -> Result<Vec<(Box<str>, 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<BindingPatternTypeArray>,
init: Option<Node>,
}
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<BindingPatternTypeArray>,
init: Option<Node>,
) -> 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<JsValue>,
context: &mut Context,
) -> Result<Vec<(Box<str>, 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<str>,
property_name: Box<str>,
default_init: Option<Node>,
},
/// 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<str>,
excluded_keys: Vec<Box<str>>,
},
/// 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<str>,
pattern: DeclarationPattern,
default_init: Option<Node>,
},
}
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<str>,
default_init: Option<Node>,
},
/// 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<str> },
/// 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(())
}
}

41
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,
}];
"#,
);
}

94
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(

95
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(

32
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());
}
}
}
}
}
}

6
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::<Option<Box<str>>, _, 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::<Option<Box<str>>, _, StatementList>(
None,
[],
vec![DeclarationList::Const(
vec![Declaration::new(
vec![Declaration::new_with_identifier(
"b",
Some(
AsyncFunctionExpr::new::<Option<Box<str>>, _, StatementList>(

6
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::<Option<Box<str>>, _, 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::<Option<Box<str>>, _, StatementList>(
None,
[],
vec![DeclarationList::Const(
vec![Declaration::new(
vec![Declaration::new_with_identifier(
"b",
Some(
FunctionExpr::new::<Option<Box<str>>, _, StatementList>(

14
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()),
)]

16
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(

14
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(),
],
);
}

99
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<R> TokenParser<R> for LexicalBinding
where
R: Read,
{
type Output = (Box<str>, Option<Node>);
type Output = Declaration;
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
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))
}
}
}
}

116
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(),
)

16
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"),

593
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_in: allow_in.into(),
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl<R> TokenParser<R> for ObjectBindingPattern
where
R: Read,
{
type Output = Vec<BindingPatternTypeObject>;
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_in: allow_in.into(),
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl<R> TokenParser<R> for ArrayBindingPattern
where
R: Read,
{
type Output = Vec<BindingPatternTypeArray>;
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
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)
}
}

8
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![

30
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()],
)),

76
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<R>) -> Result<Self::Output, ParseError> {
// 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))
}
}
}
}

28
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<Node>>(
vec![Declaration::new_with_identifier::<&str, Option<Node>>(
"a",
Some(Const::Int(10).into()),
)]
@ -152,7 +152,7 @@ fn comment_semi_colon_insertion() {
)
.into(),
DeclarationList::Let(
vec![Declaration::new::<&str, Option<Node>>(
vec![Declaration::new_with_identifier::<&str, Option<Node>>(
"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<Node>>(
vec![Declaration::new_with_identifier::<&str, Option<Node>>(
"a",
Some(Const::Int(10).into()),
)]
@ -184,7 +184,7 @@ fn multiline_comment_semi_colon_insertion() {
)
.into(),
DeclarationList::Let(
vec![Declaration::new::<&str, Option<Node>>(
vec![Declaration::new_with_identifier::<&str, Option<Node>>(
"b",
Some(Const::Int(20).into()),
)]
@ -205,7 +205,7 @@ fn multiline_comment_no_lineterminator() {
s,
vec![
DeclarationList::Let(
vec![Declaration::new::<&str, Option<Node>>(
vec![Declaration::new_with_identifier::<&str, Option<Node>>(
"a",
Some(Const::Int(10).into()),
)]
@ -213,7 +213,7 @@ fn multiline_comment_no_lineterminator() {
)
.into(),
DeclarationList::Let(
vec![Declaration::new::<&str, Option<Node>>(
vec![Declaration::new_with_identifier::<&str, Option<Node>>(
"b",
Some(Const::Int(20).into()),
)]
@ -237,7 +237,7 @@ fn assignment_line_terminator() {
s,
vec![
DeclarationList::Let(
vec![Declaration::new::<&str, Option<Node>>(
vec![Declaration::new_with_identifier::<&str, Option<Node>>(
"a",
Some(Const::Int(3).into()),
)]
@ -265,7 +265,7 @@ fn assignment_multiline_terminator() {
s,
vec![
DeclarationList::Let(
vec![Declaration::new::<&str, Option<Node>>(
vec![Declaration::new_with_identifier::<&str, Option<Node>>(
"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,

Loading…
Cancel
Save