From 143434f643bad49b6ec49a0bac7bda05e9d06044 Mon Sep 17 00:00:00 2001 From: Iban Eguia Date: Sun, 10 May 2020 11:53:20 +0200 Subject: [PATCH] Added `BindingIdentifier` parsing. (#389) --- boa/src/exec/tests.rs | 28 ++--- .../expression/assignment/arrow_function.rs | 43 +++---- .../parser/expression/assignment/mod.rs | 4 +- .../expression/primary/function_expression.rs | 19 +-- .../syntax/parser/expression/primary/mod.rs | 2 +- boa/src/syntax/parser/function/mod.rs | 65 +++++----- .../syntax/parser/statement/break_stm/mod.rs | 1 + .../parser/statement/continue_stm/mod.rs | 1 + .../parser/statement/declaration/hoistable.rs | 17 +-- .../parser/statement/declaration/lexical.rs | 101 +++++++++------ .../parser/statement/declaration/tests.rs | 119 ++++++++++++++++-- boa/src/syntax/parser/statement/mod.rs | 47 +++++++ .../syntax/parser/statement/try_stm/mod.rs | 20 ++- boa/src/syntax/parser/statement/variable.rs | 33 ++--- 14 files changed, 314 insertions(+), 186 deletions(-) diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 4adb74d93d3..55dbbc006a0 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -10,9 +10,7 @@ fn empty_let_decl_undefined() { a == undefined; "#; - let pass = String::from("true"); - - assert_eq!(exec(scenario), pass); + assert_eq!(&exec(scenario), "true"); } #[test] @@ -23,9 +21,7 @@ fn semicolon_expression_stop() { a "#; - let pass = String::from("1"); - - assert_eq!(exec(scenario), pass); + assert_eq!(&exec(scenario), "1"); } #[test] @@ -35,9 +31,7 @@ fn empty_var_decl_undefined() { b == undefined; "#; - let pass = String::from("true"); - - assert_eq!(exec(scenario), pass); + assert_eq!(&exec(scenario), "true"); } #[test] @@ -189,10 +183,10 @@ fn early_return() { #[test] fn short_circuit_evaluation() { // OR operation - assert_eq!(exec("true || true"), String::from("true")); - assert_eq!(exec("true || false"), String::from("true")); - assert_eq!(exec("false || true"), String::from("true")); - assert_eq!(exec("false || false"), String::from("false")); + assert_eq!(&exec("true || true"), "true"); + assert_eq!(&exec("true || false"), "true"); + assert_eq!(&exec("false || true"), "true"); + assert_eq!(&exec("false || false"), "false"); // the second operand must NOT be evaluated if the first one resolve to `true`. let short_circuit_eval = r#" @@ -219,10 +213,10 @@ fn short_circuit_evaluation() { assert_eq!(&exec(short_circuit_eval), "2"); // AND operation - assert_eq!(exec("true && true"), String::from("true")); - assert_eq!(exec("true && false"), String::from("false")); - assert_eq!(exec("false && true"), String::from("false")); - assert_eq!(exec("false && false"), String::from("false")); + assert_eq!(&exec("true && true"), "true"); + assert_eq!(&exec("true && false"), "false"); + assert_eq!(&exec("false && true"), "false"); + assert_eq!(&exec("false && false"), "false"); // the second operand must be evaluated if the first one resolve to `true`. let short_circuit_eval = r#" diff --git a/boa/src/syntax/parser/expression/assignment/arrow_function.rs b/boa/src/syntax/parser/expression/assignment/arrow_function.rs index 24df3d528b5..b8166fe97da 100644 --- a/boa/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa/src/syntax/parser/expression/assignment/arrow_function.rs @@ -16,6 +16,7 @@ use crate::syntax::{ }, parser::{ function::{FormalParameters, FunctionBody}, + statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, }, }; @@ -59,30 +60,30 @@ impl TokenParser for ArrowFunction { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { - let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?; - let params = match &next_token.kind { - TokenKind::Punctuator(Punctuator::OpenParen) => { - let params = - FormalParameters::new(self.allow_yield, self.allow_await).parse(cursor)?; - cursor.expect(Punctuator::CloseParen, "arrow function")?; - params - } - TokenKind::Identifier(param_name) => vec![FormalParameter { + let next_token = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; + let params = if let TokenKind::Punctuator(Punctuator::OpenParen) = &next_token.kind { + // CoverParenthesizedExpressionAndArrowParameterList + cursor.expect(Punctuator::OpenParen, "arrow function")?; + let params = FormalParameters::new(self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "arrow function")?; + params.into_boxed_slice() + } else { + let param = BindingIdentifier::new(self.allow_yield, self.allow_await) + .parse(cursor) + .map_err(|e| match e { + ParseError::Expected(mut exp, tok, _) => { + exp.push(Punctuator::OpenParen.into()); + ParseError::Expected(exp, tok, "arrow function") + } + e => e, + })?; + Box::new([FormalParameter { init: None, - name: param_name.clone(), + name: param, is_rest_param: false, - }], - _ => { - return Err(ParseError::Expected( - vec![ - TokenKind::Punctuator(Punctuator::OpenParen), - TokenKind::identifier("identifier"), - ], - next_token.clone(), - "arrow function", - )) - } + }]) }; + cursor.peek_expect_no_lineterminator(0, "arrow function")?; cursor.expect(Punctuator::Arrow, "arrow function")?; diff --git a/boa/src/syntax/parser/expression/assignment/mod.rs b/boa/src/syntax/parser/expression/assignment/mod.rs index 63a040a4d6e..bcc3079f772 100644 --- a/boa/src/syntax/parser/expression/assignment/mod.rs +++ b/boa/src/syntax/parser/expression/assignment/mod.rs @@ -13,7 +13,7 @@ mod exponentiation; use self::{arrow_function::ArrowFunction, conditional::ConditionalExpression}; use crate::syntax::{ - ast::{node::Node, punc::Punctuator, token::TokenKind}, + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, parser::{AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }; pub(super) use exponentiation::ExponentiationExpression; @@ -72,6 +72,8 @@ impl TokenParser for AssignmentExpression { match next_token.kind { // a=>{} TokenKind::Identifier(_) + | TokenKind::Keyword(Keyword::Yield) + | TokenKind::Keyword(Keyword::Await) if cursor .peek_expect_no_lineterminator(1, "arrow function") .is_ok() => diff --git a/boa/src/syntax/parser/expression/primary/function_expression.rs b/boa/src/syntax/parser/expression/primary/function_expression.rs index 67ba84797cb..0f52e10a61a 100644 --- a/boa/src/syntax/parser/expression/primary/function_expression.rs +++ b/boa/src/syntax/parser/expression/primary/function_expression.rs @@ -8,10 +8,11 @@ //! [spec]: https://tc39.es/ecma262/#prod-FunctionExpression use crate::syntax::{ - ast::{node::Node, punc::Punctuator, token::TokenKind}, + ast::{node::Node, punc::Punctuator}, parser::{ function::{FormalParameters, FunctionBody}, - Cursor, ParseError, ParseResult, TokenParser, + statement::BindingIdentifier, + Cursor, ParseResult, TokenParser, }, }; @@ -30,17 +31,7 @@ impl TokenParser for FunctionExpression { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { - let name = if let TokenKind::Identifier(name) = - &cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind - { - Some(name) - } else { - None - }; - if name.is_some() { - // We move the cursor forward. - let _ = cursor.next().expect("nex token disappeared"); - } + let name = BindingIdentifier::new(false, false).try_parse(cursor); cursor.expect(Punctuator::OpenParen, "function expression")?; @@ -55,6 +46,6 @@ impl TokenParser for FunctionExpression { cursor.expect(Punctuator::CloseBlock, "function expression")?; - Ok(Node::function_expr::<_, &String, _, _>(name, params, body)) + Ok(Node::function_expr::<_, String, _, _>(name, params, body)) } } diff --git a/boa/src/syntax/parser/expression/primary/mod.rs b/boa/src/syntax/parser/expression/primary/mod.rs index 2cb684b3cf3..0022ddacc6c 100644 --- a/boa/src/syntax/parser/expression/primary/mod.rs +++ b/boa/src/syntax/parser/expression/primary/mod.rs @@ -81,7 +81,7 @@ impl TokenParser for PrimaryExpression { // TODO: ADD TokenKind::UndefinedLiteral TokenKind::Identifier(ref i) if i == "undefined" => Ok(Node::Const(Const::Undefined)), TokenKind::NullLiteral => Ok(Node::Const(Const::Null)), - TokenKind::Identifier(ident) => Ok(Node::local(ident)), + TokenKind::Identifier(ident) => Ok(Node::local(ident)), // TODO: IdentifierReference TokenKind::StringLiteral(s) => Ok(Node::const_node(s)), TokenKind::NumericLiteral(NumericLiteral::Integer(num)) => Ok(Node::const_node(*num)), TokenKind::NumericLiteral(NumericLiteral::Rational(num)) => Ok(Node::const_node(*num)), diff --git a/boa/src/syntax/parser/function/mod.rs b/boa/src/syntax/parser/function/mod.rs index 0f3c4a991a9..dc8ef71f3f2 100644 --- a/boa/src/syntax/parser/function/mod.rs +++ b/boa/src/syntax/parser/function/mod.rs @@ -16,7 +16,11 @@ use crate::syntax::{ punc::Punctuator, token::TokenKind, }, - parser::{statement::StatementList, AllowAwait, AllowYield, Cursor, ParseError, TokenParser}, + parser::{ + expression::Initializer, + statement::{BindingIdentifier, StatementList}, + AllowAwait, AllowYield, Cursor, ParseError, TokenParser, + }, }; /// Formal parameters parsing. @@ -100,14 +104,24 @@ impl TokenParser for FormalParameters { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters /// [spec]: https://tc39.es/ecma262/#prod-FunctionRestParameter +type FunctionRestParameter = BindingRestElement; + +/// Rest parameter parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters +/// [spec]: https://tc39.es/ecma262/#prod-BindingRestElement #[derive(Debug, Clone, Copy)] -struct FunctionRestParameter { +struct BindingRestElement { allow_yield: AllowYield, allow_await: AllowAwait, } -impl FunctionRestParameter { - /// Creates a new `FunctionRestParameter` parser. +impl BindingRestElement { + /// Creates a new `BindingRestElement` parser. fn new(allow_yield: Y, allow_await: A) -> Self where Y: Into, @@ -120,24 +134,17 @@ impl FunctionRestParameter { } } -impl TokenParser for FunctionRestParameter { +impl TokenParser for BindingRestElement { type Output = node::FormalParameter; fn parse(self, cursor: &mut Cursor<'_>) -> Result { - let token = cursor.next().ok_or(ParseError::AbruptEnd)?; - Ok(Self::Output::new( - if let TokenKind::Identifier(name) = &token.kind { - name - } else { - return Err(ParseError::Expected( - vec![TokenKind::identifier("identifier")], - token.clone(), - "rest parameter", - )); - }, - None, - true, - )) + // FIXME: we are reading the spread operator before the rest element. + // cursor.expect(Punctuator::Spread, "rest parameter")?; + + let param = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + // TODO: BindingPattern + + Ok(Self::Output::new(param, None, true)) } } @@ -173,19 +180,13 @@ impl TokenParser for FormalParameter { type Output = node::FormalParameter; fn parse(self, cursor: &mut Cursor<'_>) -> Result { - let token = cursor.next().ok_or(ParseError::AbruptEnd)?; - let name = if let TokenKind::Identifier(name) = &token.kind { - name - } else { - return Err(ParseError::Expected( - vec![TokenKind::identifier("identifier")], - token.clone(), - "formal parameter", - )); - }; - - // TODO: Implement initializer. - Ok(Self::Output::new(name, None, false)) + // TODO: BindingPattern + + let param = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + + let init = Initializer::new(true, self.allow_yield, self.allow_await).try_parse(cursor); + + Ok(Self::Output::new(param, init.map(Box::new), false)) } } diff --git a/boa/src/syntax/parser/statement/break_stm/mod.rs b/boa/src/syntax/parser/statement/break_stm/mod.rs index 0edace6bb27..de3119a3cc0 100644 --- a/boa/src/syntax/parser/statement/break_stm/mod.rs +++ b/boa/src/syntax/parser/statement/break_stm/mod.rs @@ -61,6 +61,7 @@ impl TokenParser for BreakStatement { } let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; + // TODO: LabelIdentifier let node = if let TokenKind::Identifier(name) = &tok.kind { Node::break_node(name) } else { diff --git a/boa/src/syntax/parser/statement/continue_stm/mod.rs b/boa/src/syntax/parser/statement/continue_stm/mod.rs index ca794da07c5..d43b40db349 100644 --- a/boa/src/syntax/parser/statement/continue_stm/mod.rs +++ b/boa/src/syntax/parser/statement/continue_stm/mod.rs @@ -61,6 +61,7 @@ impl TokenParser for ContinueStatement { } let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; + // TODO: LabelIdentifier let node = if let TokenKind::Identifier(name) = &tok.kind { Node::continue_node(name) } else { diff --git a/boa/src/syntax/parser/statement/declaration/hoistable.rs b/boa/src/syntax/parser/statement/declaration/hoistable.rs index d0894f3316a..691066844b9 100644 --- a/boa/src/syntax/parser/statement/declaration/hoistable.rs +++ b/boa/src/syntax/parser/statement/declaration/hoistable.rs @@ -6,10 +6,10 @@ //! [spec]: https://tc39.es/ecma262/#prod-HoistableDeclaration use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{keyword::Keyword, node::Node, punc::Punctuator}, parser::{ - function::FormalParameters, function::FunctionBody, AllowAwait, AllowDefault, AllowYield, - Cursor, ParseError, ParseResult, TokenParser, + function::FormalParameters, function::FunctionBody, statement::BindingIdentifier, + AllowAwait, AllowDefault, AllowYield, Cursor, ParseResult, TokenParser, }, }; @@ -89,16 +89,7 @@ impl TokenParser for FunctionDeclaration { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { cursor.expect(Keyword::Function, "function declaration")?; - let token = cursor.next().ok_or(ParseError::AbruptEnd)?; - let name = if let TokenKind::Identifier(name) = &token.kind { - name.clone() - } else { - return Err(ParseError::Expected( - vec![TokenKind::identifier("function name")], - token.clone(), - "function declaration", - )); - }; + let name = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; cursor.expect(Punctuator::OpenParen, "function declaration")?; diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs index 7749bf05149..be0a0eb8525 100644 --- a/boa/src/syntax/parser/statement/declaration/lexical.rs +++ b/boa/src/syntax/parser/statement/declaration/lexical.rs @@ -10,8 +10,8 @@ use crate::syntax::{ ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, parser::{ - expression::Initializer, AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, - TokenParser, + expression::Initializer, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, + Cursor, ParseError, ParseResult, TokenParser, }, }; @@ -69,7 +69,10 @@ impl TokenParser for LexicalDeclaration { /// It will return an error if a `const` declaration is being parsed and there is no /// initializer. /// -/// More information: . +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-BindingList #[derive(Debug, Clone, Copy)] struct BindingList { allow_in: AllowIn, @@ -105,44 +108,22 @@ impl TokenParser for BindingList { let mut const_decls = Vec::new(); loop { - let token = cursor.next().ok_or(ParseError::AbruptEnd)?; - let name = if let TokenKind::Identifier(ref name) = token.kind { - name.clone() - } else { - return Err(ParseError::Expected( - vec![TokenKind::identifier("identifier")], - token.clone(), - if self.is_const { - "const declaration" - } else { - "let declaration" - }, - )); - }; - - match cursor.peek(0) { - Some(token) if token.kind == TokenKind::Punctuator(Punctuator::Assign) => { - let init = Some( - Initializer::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor)?, - ); - if self.is_const { - const_decls.push((name, init.unwrap())); - } else { - let_decls.push((name, init)); - }; - } - _ => { - if self.is_const { - return Err(ParseError::Expected( - vec![TokenKind::Punctuator(Punctuator::Assign)], - cursor.next().ok_or(ParseError::AbruptEnd)?.clone(), - "const declaration", - )); - } else { - let_decls.push((name, None)); - } + let lexical_binding = + LexicalBinding::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + + if self.is_const { + if let (ident, Some(init)) = lexical_binding { + const_decls.push((ident, init)); + } else { + return Err(ParseError::Expected( + vec![TokenKind::Punctuator(Punctuator::Assign)], + cursor.next().ok_or(ParseError::AbruptEnd)?.clone(), + "const declaration", + )); } + } else { + let_decls.push(lexical_binding); } match cursor.peek_semicolon(false) { @@ -170,3 +151,43 @@ impl TokenParser for BindingList { } } } + +/// Lexical binding parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-LexicalBinding +struct LexicalBinding { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl LexicalBinding { + /// Creates a new `BindingList` parser. + 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 LexicalBinding { + type Output = (String, Option); + + fn parse(self, cursor: &mut Cursor<'_>) -> Result<(String, Option), ParseError> { + let ident = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + let initializer = + Initializer::new(self.allow_in, self.allow_yield, self.allow_await).try_parse(cursor); + + Ok((ident, initializer)) + } +} diff --git a/boa/src/syntax/parser/statement/declaration/tests.rs b/boa/src/syntax/parser/statement/declaration/tests.rs index 2e62e2cef3f..21ec1749f48 100644 --- a/boa/src/syntax/parser/statement/declaration/tests.rs +++ b/boa/src/syntax/parser/statement/declaration/tests.rs @@ -5,7 +5,7 @@ use crate::syntax::{ /// Checks `var` declaration parsing. #[test] -fn check_var_declaration() { +fn var_declaration() { check_parser( "var a = 5;", vec![Node::var_decl(vec![( @@ -15,9 +15,29 @@ fn check_var_declaration() { ); } +/// Checks `var` declaration parsing with reserved words. +#[test] +fn var_declaration_keywords() { + check_parser( + "var yield = 5;", + vec![Node::var_decl(vec![( + String::from("yield"), + Some(Node::const_node(5)), + )])], + ); + + check_parser( + "var await = 5;", + vec![Node::var_decl(vec![( + String::from("await"), + Some(Node::const_node(5)), + )])], + ); +} + /// Checks `var` declaration parsing with no spaces. #[test] -fn check_var_declaration_no_spaces() { +fn var_declaration_no_spaces() { check_parser( "var a=5;", vec![Node::var_decl(vec![( @@ -29,7 +49,7 @@ fn check_var_declaration_no_spaces() { /// Checks empty `var` declaration parsing. #[test] -fn check_empty_var_declaration() { +fn empty_var_declaration() { check_parser( "var a;", vec![Node::var_decl(vec![(String::from("a"), None)])], @@ -38,7 +58,7 @@ fn check_empty_var_declaration() { /// Checks multiple `var` declarations. #[test] -fn check_multiple_var_declaration() { +fn multiple_var_declaration() { check_parser( "var a = 5, b, c = 6;", vec![Node::var_decl(vec![ @@ -51,7 +71,7 @@ fn check_multiple_var_declaration() { /// Checks `let` declaration parsing. #[test] -fn check_let_declaration() { +fn let_declaration() { check_parser( "let a = 5;", vec![Node::let_decl(vec![( @@ -61,9 +81,29 @@ fn check_let_declaration() { ); } +/// Checks `let` declaration parsing with reserved words. +#[test] +fn let_declaration_keywords() { + check_parser( + "let yield = 5;", + vec![Node::let_decl(vec![( + String::from("yield"), + Some(Node::const_node(5)), + )])], + ); + + check_parser( + "let await = 5;", + vec![Node::let_decl(vec![( + String::from("await"), + Some(Node::const_node(5)), + )])], + ); +} + /// Checks `let` declaration parsing with no spaces. #[test] -fn check_let_declaration_no_spaces() { +fn let_declaration_no_spaces() { check_parser( "let a=5;", vec![Node::let_decl(vec![( @@ -75,7 +115,7 @@ fn check_let_declaration_no_spaces() { /// Checks empty `let` declaration parsing. #[test] -fn check_empty_let_declaration() { +fn empty_let_declaration() { check_parser( "let a;", vec![Node::let_decl(vec![(String::from("a"), None)])], @@ -84,7 +124,7 @@ fn check_empty_let_declaration() { /// Checks multiple `let` declarations. #[test] -fn check_multiple_let_declaration() { +fn multiple_let_declaration() { check_parser( "let a = 5, b, c = 6;", vec![Node::let_decl(vec![ @@ -97,7 +137,7 @@ fn check_multiple_let_declaration() { /// Checks `const` declaration parsing. #[test] -fn check_const_declaration() { +fn const_declaration() { check_parser( "const a = 5;", vec![Node::const_decl(vec![( @@ -107,9 +147,29 @@ fn check_const_declaration() { ); } +/// Checks `const` declaration parsing with reserved words. +#[test] +fn const_declaration_keywords() { + check_parser( + "const yield = 5;", + vec![Node::const_decl(vec![( + String::from("yield"), + Node::const_node(5), + )])], + ); + + check_parser( + "const await = 5;", + vec![Node::const_decl(vec![( + String::from("await"), + Node::const_node(5), + )])], + ); +} + /// Checks `const` declaration parsing with no spaces. #[test] -fn check_const_declaration_no_spaces() { +fn const_declaration_no_spaces() { check_parser( "const a=5;", vec![Node::const_decl(vec![( @@ -121,13 +181,13 @@ fn check_const_declaration_no_spaces() { /// Checks empty `const` declaration parsing. #[test] -fn check_empty_const_declaration() { +fn empty_const_declaration() { check_invalid("const a;"); } /// Checks multiple `const` declarations. #[test] -fn check_multiple_const_declaration() { +fn multiple_const_declaration() { check_parser( "const a = 5, c = 6;", vec![Node::const_decl(vec![ @@ -136,3 +196,38 @@ fn check_multiple_const_declaration() { ])], ); } + +/// Function declaration parsing. +#[test] +fn function_declaration() { + check_parser( + "function hello() {}", + vec![Node::function_decl( + "hello", + vec![], + Node::statement_list(vec![]), + )], + ); +} + +/// Function declaration parsing with keywords. +#[test] +fn function_declaration_keywords() { + check_parser( + "function yield() {}", + vec![Node::function_decl( + "yield", + vec![], + Node::statement_list(vec![]), + )], + ); + + check_parser( + "function await() {}", + vec![Node::function_decl( + "await", + vec![], + Node::statement_list(vec![]), + )], + ); +} diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 4f95f57716f..368ad3cb9f7 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -316,3 +316,50 @@ impl TokenParser for ExpressionStatement { Ok(expr) } } + +/// Binding identifier parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-BindingIdentifier +#[derive(Debug, Clone, Copy)] +pub(super) struct BindingIdentifier { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl BindingIdentifier { + /// Creates a new `BindingIdentifier` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for BindingIdentifier { + type Output = String; + + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + // TODO: strict mode. + + let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?; + + match next_token.kind { + TokenKind::Identifier(ref s) => Ok(s.clone()), + TokenKind::Keyword(k @ Keyword::Yield) if !self.allow_yield.0 => Ok(k.to_string()), + TokenKind::Keyword(k @ Keyword::Await) if !self.allow_await.0 => Ok(k.to_string()), + _ => Err(ParseError::Expected( + vec![TokenKind::identifier("identifier")], + next_token.clone(), + "binding identifier", + )), + } + } +} diff --git a/boa/src/syntax/parser/statement/try_stm/mod.rs b/boa/src/syntax/parser/statement/try_stm/mod.rs index 0760698f1fb..5cdcd9c3e06 100644 --- a/boa/src/syntax/parser/statement/try_stm/mod.rs +++ b/boa/src/syntax/parser/statement/try_stm/mod.rs @@ -4,7 +4,10 @@ mod tests; use super::block::Block; use crate::syntax::{ ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, - parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, + parser::{ + statement::BindingIdentifier, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, + ParseResult, TokenParser, + }, }; /// Try...catch statement parsing @@ -67,17 +70,10 @@ impl TokenParser for TryStatement { let (catch, param) = if next_token.kind == TokenKind::Keyword(Keyword::Catch) { // Catch binding cursor.expect(Punctuator::OpenParen, "catch in try statement")?; - // TODO: should accept BindingPattern - let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; - let catch_param = if let TokenKind::Identifier(s) = &tok.kind { - Node::local(s) - } else { - return Err(ParseError::Expected( - vec![TokenKind::identifier("identifier")], - tok.clone(), - "catch in try statement", - )); - }; + // TODO: CatchParameter - BindingPattern + let catch_param = BindingIdentifier::new(self.allow_yield, self.allow_await) + .parse(cursor) + .map(Node::local)?; cursor.expect(Punctuator::CloseParen, "catch in try statement")?; // Catch block diff --git a/boa/src/syntax/parser/statement/variable.rs b/boa/src/syntax/parser/statement/variable.rs index baf75cf6497..193d81a4913 100644 --- a/boa/src/syntax/parser/statement/variable.rs +++ b/boa/src/syntax/parser/statement/variable.rs @@ -2,8 +2,8 @@ use crate::syntax::{ ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, parser::{ - expression::Initializer, AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, - TokenParser, + expression::Initializer, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, + Cursor, ParseError, ParseResult, TokenParser, }, }; @@ -154,26 +154,13 @@ impl TokenParser for VariableDeclaration { type Output = (String, Option); fn parse(self, cursor: &mut Cursor<'_>) -> Result { - let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; - let name = if let TokenKind::Identifier(name) = &tok.kind { - name.clone() - } else { - return Err(ParseError::Expected( - vec![TokenKind::identifier("identifier")], - tok.clone(), - "variable declaration", - )); - }; - - match cursor.peek(0) { - Some(tk) if tk.kind == TokenKind::Punctuator(Punctuator::Assign) => Ok(( - name, - Some( - Initializer::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor)?, - ), - )), - _ => Ok((name, None)), - } + // TODO: BindingPattern + + let name = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + + let ident = + Initializer::new(self.allow_in, self.allow_yield, self.allow_await).try_parse(cursor); + + Ok((name, ident)) } }