diff --git a/boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs index ae5d271ee0..4d528d0bfa 100644 --- a/boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct AsyncFunctionDecl { - name: Option>, + name: Box, parameters: Box<[FormalParameter]>, body: StatementList, } @@ -31,7 +31,7 @@ impl AsyncFunctionDecl { /// Creates a new async function declaration. pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self where - N: Into>>, + N: Into>, P: Into>, B: Into, { @@ -43,8 +43,8 @@ impl AsyncFunctionDecl { } /// Gets the name of the async function declaration. - pub fn name(&self) -> Option<&str> { - self.name.as_deref() + pub fn name(&self) -> &str { + &self.name } /// Gets the list of parameters of the async function declaration. @@ -63,10 +63,7 @@ impl AsyncFunctionDecl { f: &mut fmt::Formatter<'_>, indentation: usize, ) -> fmt::Result { - match &self.name { - Some(name) => write!(f, "async function {}(", name)?, - None => write!(f, "async function (")?, - } + write!(f, "async function {}(", self.name())?; join_nodes(f, &self.parameters)?; if self.body().is_empty() { f.write_str(") {}") diff --git a/boa/src/syntax/ast/node/declaration/generator_decl/mod.rs b/boa/src/syntax/ast/node/declaration/generator_decl/mod.rs new file mode 100644 index 0000000000..60116ec350 --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/generator_decl/mod.rs @@ -0,0 +1,96 @@ +use crate::{ + exec::Executable, + gc::{Finalize, Trace}, + syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, + BoaProfiler, Context, JsResult, JsValue, +}; +use std::fmt; + +#[cfg(feature = "deser")] +use serde::{Deserialize, Serialize}; + +/// The `function*` declaration (`function` keyword followed by an asterisk) defines a generator function, +/// which returns a `Generator` object. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-GeneratorDeclaration +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct GeneratorDecl { + name: Box, + parameters: Box<[FormalParameter]>, + body: StatementList, +} + +impl GeneratorDecl { + /// Creates a new generator declaration. + pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self + where + N: Into>, + P: Into>, + B: Into, + { + Self { + name: name.into(), + parameters: parameters.into(), + body: body.into(), + } + } + + /// Gets the name of the generator declaration. + pub fn name(&self) -> &str { + &self.name + } + + /// Gets the list of parameters of the generator declaration. + pub fn parameters(&self) -> &[FormalParameter] { + &self.parameters + } + + /// Gets the body of the generator declaration. + pub fn body(&self) -> &[Node] { + self.body.items() + } + + /// Implements the display formatting with indentation. + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + write!(f, "function* {}(", self.name)?; + join_nodes(f, &self.parameters)?; + if self.body().is_empty() { + f.write_str(") {}") + } else { + f.write_str(") {\n")?; + self.body.display(f, indentation + 1)?; + write!(f, "{}}}", " ".repeat(indentation)) + } + } +} + +impl Executable for GeneratorDecl { + fn run(&self, _context: &mut Context) -> JsResult { + let _timer = BoaProfiler::global().start_event("GeneratorDecl", "exec"); + // TODO: Implement GeneratorFunction + // https://tc39.es/ecma262/#sec-generatorfunction-objects + Ok(JsValue::undefined()) + } +} + +impl From for Node { + fn from(decl: GeneratorDecl) -> Self { + Self::GeneratorDecl(decl) + } +} + +impl fmt::Display for GeneratorDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} diff --git a/boa/src/syntax/ast/node/declaration/generator_expr/mod.rs b/boa/src/syntax/ast/node/declaration/generator_expr/mod.rs new file mode 100644 index 0000000000..70d7aa98ab --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/generator_expr/mod.rs @@ -0,0 +1,109 @@ +use crate::{ + exec::Executable, + gc::{Finalize, Trace}, + syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, + Context, JsResult, JsValue, +}; +use std::fmt; + +#[cfg(feature = "deser")] +use serde::{Deserialize, Serialize}; + +/// The `function*` keyword can be used to define a generator function inside an expression. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-GeneratorExpression +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function* +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct GeneratorExpr { + name: Option>, + parameters: Box<[FormalParameter]>, + body: StatementList, +} + +impl GeneratorExpr { + /// Creates a new generator expression + pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self + where + N: Into>>, + P: Into>, + B: Into, + { + Self { + name: name.into(), + parameters: parameters.into(), + body: body.into(), + } + } + + /// Gets the name of the generator declaration. + pub fn name(&self) -> Option<&str> { + self.name.as_ref().map(Box::as_ref) + } + + /// Gets the list of parameters of the generator declaration. + pub fn parameters(&self) -> &[FormalParameter] { + &self.parameters + } + + /// Gets the body of the generator declaration. + pub fn body(&self) -> &StatementList { + &self.body + } + + /// Implements the display formatting with indentation. + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + f.write_str("function*")?; + if let Some(ref name) = self.name { + write!(f, " {}", name)?; + } + f.write_str("(")?; + join_nodes(f, &self.parameters)?; + f.write_str(") ")?; + self.display_block(f, indentation) + } + + /// Displays the generator's body. This includes the curly braces at the start and end. + /// This will not indent the first brace, but will indent the last brace. + pub(in crate::syntax::ast::node) fn display_block( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + if self.body().items().is_empty() { + f.write_str("{}") + } else { + f.write_str("{\n")?; + self.body.display(f, indentation + 1)?; + write!(f, "{}}}", " ".repeat(indentation)) + } + } +} + +impl Executable for GeneratorExpr { + fn run(&self, _context: &mut Context) -> JsResult { + // TODO: Implement GeneratorFunction + // https://tc39.es/ecma262/#sec-generatorfunction-objects + Ok(JsValue::undefined()) + } +} + +impl fmt::Display for GeneratorExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(expr: GeneratorExpr) -> Self { + Self::GeneratorExpr(expr) + } +} diff --git a/boa/src/syntax/ast/node/declaration/mod.rs b/boa/src/syntax/ast/node/declaration/mod.rs index ccb329fabd..d6050a0d15 100644 --- a/boa/src/syntax/ast/node/declaration/mod.rs +++ b/boa/src/syntax/ast/node/declaration/mod.rs @@ -17,6 +17,8 @@ pub mod async_function_decl; pub mod async_function_expr; pub mod function_decl; pub mod function_expr; +pub mod generator_decl; +pub mod generator_expr; pub use self::{ arrow_function_decl::ArrowFunctionDecl, async_function_decl::AsyncFunctionDecl, diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 9da219867f..422f935f48 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -20,6 +20,7 @@ pub mod switch; pub mod template; pub mod throw; pub mod try_node; +pub mod r#yield; pub use self::{ array::ArrayDecl, @@ -29,8 +30,9 @@ pub use self::{ call::Call, conditional::{ConditionalOp, If}, declaration::{ - ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, - FunctionDecl, FunctionExpr, + generator_decl::GeneratorDecl, generator_expr::GeneratorExpr, ArrowFunctionDecl, + AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, FunctionDecl, + FunctionExpr, }, field::{GetConstField, GetField}, identifier::Identifier, @@ -38,6 +40,7 @@ pub use self::{ new::New, object::Object, operator::{Assign, BinOp, UnaryOp}, + r#yield::Yield, return_smt::Return, spread::Spread, statement_list::{RcStatementList, StatementList}, @@ -209,6 +212,15 @@ pub enum Node { /// [spec]: https://tc39.es/ecma262/#prod-EmptyStatement /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/Empty Empty, + + /// A `yield` node. [More information](./yield/struct.Yield.html). + Yield(Yield), + + /// A generator function declaration node. [More information](./declaration/struct.GeneratorDecl.html). + GeneratorDecl(GeneratorDecl), + + /// A generator function expression node. [More information](./declaration/struct.GeneratorExpr.html). + GeneratorExpr(GeneratorExpr), } impl Display for Node { @@ -302,6 +314,9 @@ impl Node { Self::AsyncFunctionExpr(ref expr) => expr.display(f, indentation), Self::AwaitExpr(ref expr) => Display::fmt(expr, f), Self::Empty => write!(f, ";"), + Self::Yield(ref y) => Display::fmt(y, f), + Self::GeneratorDecl(ref decl) => Display::fmt(decl, f), + Self::GeneratorExpr(ref expr) => expr.display(f, indentation), } } } @@ -363,6 +378,9 @@ impl Executable for Node { Node::Break(ref break_node) => break_node.run(context), Node::Continue(ref continue_node) => continue_node.run(context), Node::Empty => Ok(JsValue::undefined()), + Node::Yield(ref y) => y.run(context), + Node::GeneratorDecl(ref decl) => decl.run(context), + Node::GeneratorExpr(ref expr) => expr.run(context), } } } @@ -597,7 +615,16 @@ pub enum MethodDefinitionKind { /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Method_definition_syntax Ordinary, - // TODO: support other method definition kinds, like `Generator`. + + /// Starting with ECMAScript 2015, you are able to define own methods in a shorter syntax, similar to the getters and setters. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#generator_methods + Generator, } unsafe impl Trace for MethodDefinitionKind { diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index 83aabce87b..b54a93acfd 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -72,7 +72,7 @@ impl Object { match &kind { MethodDefinitionKind::Get => write!(f, "get ")?, MethodDefinitionKind::Set => write!(f, "set ")?, - MethodDefinitionKind::Ordinary => (), + MethodDefinitionKind::Ordinary | MethodDefinitionKind::Generator => (), } write!(f, "{}(", key)?; join_nodes(f, node.parameters())?; @@ -166,6 +166,19 @@ impl Executable for Object { context, )?; } + &MethodDefinitionKind::Generator => { + // TODO: Implement generator method definition execution. + obj.__define_own_property__( + name, + PropertyDescriptor::builder() + .value(JsValue::undefined()) + .writable(true) + .enumerable(true) + .configurable(true) + .build(), + context, + )?; + } } } // [spec]: https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation diff --git a/boa/src/syntax/ast/node/yield/mod.rs b/boa/src/syntax/ast/node/yield/mod.rs new file mode 100644 index 0000000000..ec485a6b66 --- /dev/null +++ b/boa/src/syntax/ast/node/yield/mod.rs @@ -0,0 +1,70 @@ +use crate::{ + exec::Executable, + gc::{Finalize, Trace}, + syntax::ast::node::Node, + Context, JsResult, JsValue, +}; +use std::fmt; + +#[cfg(feature = "deser")] +use serde::{Deserialize, Serialize}; + +/// The `yield` keyword is used to pause and resume a generator function +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-YieldExpression +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct Yield { + expr: Option>, + delegate: bool, +} + +impl Yield { + pub fn expr(&self) -> Option<&Node> { + self.expr.as_ref().map(Box::as_ref) + } + + pub fn delegate(&self) -> bool { + self.delegate + } + + /// Creates a `Yield` AST node. + pub fn new(expr: OE, delegate: bool) -> Self + where + E: Into, + OE: Into>, + { + Self { + expr: expr.into().map(E::into).map(Box::new), + delegate, + } + } +} + +impl Executable for Yield { + fn run(&self, _context: &mut Context) -> JsResult { + // TODO: Implement Generator execution + Ok(JsValue::undefined()) + } +} + +impl From for Node { + fn from(r#yield: Yield) -> Node { + Node::Yield(r#yield) + } +} + +impl fmt::Display for Yield { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let y = if self.delegate { "yield*" } else { "yield" }; + match self.expr() { + Some(ex) => write!(f, "{} {}", y, ex), + None => write!(f, "{}", y), + } + } +} diff --git a/boa/src/syntax/parser/error.rs b/boa/src/syntax/parser/error.rs index efb2c0a464..c7f40d831f 100644 --- a/boa/src/syntax/parser/error.rs +++ b/boa/src/syntax/parser/error.rs @@ -92,6 +92,14 @@ impl ParseError { Self::General { message, position } } + /// Creates a "general" parsing error with the specific error message for a wrong function declaration in non-strict mode. + pub(super) fn wrong_function_declaration_non_strict(position: Position) -> Self { + Self::General { + message: "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.", + position + } + } + /// Creates a parsing error from a lexing error. pub(super) fn lex(e: LexError) -> Self { Self::Lex { err: e } diff --git a/boa/src/syntax/parser/expression/assignment/arrow_function.rs b/boa/src/syntax/parser/expression/assignment/arrow_function.rs index 5528c1c8b9..e8187c24d0 100644 --- a/boa/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa/src/syntax/parser/expression/assignment/arrow_function.rs @@ -17,7 +17,7 @@ use crate::{ lexer::{Error as LexError, Position, TokenKind}, parser::{ error::{ErrorContext, ParseError, ParseResult}, - function::{FormalParameters, FunctionBody}, + function::{FormalParameterList, FormalParameters, FunctionBody}, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, Cursor, TokenParser, }, @@ -72,18 +72,31 @@ where let _timer = BoaProfiler::global().start_event("ArrowFunction", "Parsing"); let next_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; - let params = if let TokenKind::Punctuator(Punctuator::OpenParen) = &next_token.kind() { + let (params, params_start_position) = if let TokenKind::Punctuator(Punctuator::OpenParen) = + &next_token.kind() + { // CoverParenthesizedExpressionAndArrowParameterList - cursor.expect(Punctuator::OpenParen, "arrow function")?; + let params_start_position = cursor + .expect(Punctuator::OpenParen, "arrow function")? + .span() + .end(); let params = FormalParameters::new(self.allow_yield, self.allow_await).parse(cursor)?; cursor.expect(Punctuator::CloseParen, "arrow function")?; - params + (params, params_start_position) } else { + let params_start_position = next_token.span().start(); let param = BindingIdentifier::new(self.allow_yield, self.allow_await) .parse(cursor) .context("arrow function")?; - Box::new([FormalParameter::new(param, None, false)]) + ( + FormalParameterList { + parameters: Box::new([FormalParameter::new(param, None, false)]), + is_simple: true, + has_duplicates: false, + }, + params_start_position, + ) }; cursor.peek_expect_no_lineterminator(0, "arrow function")?; @@ -91,12 +104,29 @@ where cursor.expect(TokenKind::Punctuator(Punctuator::Arrow), "arrow function")?; let body = ConciseBody::new(self.allow_in).parse(cursor)?; + // Early Error: ArrowFormalParameters are UniqueFormalParameters. + if params.has_duplicates { + return Err(ParseError::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + params_start_position, + ))); + } + + // Early Error: It is a Syntax Error if ConciseBodyContainsUseStrict of ConciseBody is true + // and IsSimpleParameterList of ArrowParameters is false. + if body.strict() && !params.is_simple { + return Err(ParseError::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list".into(), + params_start_position, + ))); + } + // It is a Syntax Error if any element of the BoundNames of ArrowParameters // also occurs in the LexicallyDeclaredNames of ConciseBody. // https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors { let lexically_declared_names = body.lexically_declared_names(); - for param in params.as_ref() { + for param in params.parameters.as_ref() { if lexically_declared_names.contains(param.name()) { return Err(ParseError::lex(LexError::Syntax( format!("Redeclaration of formal parameter `{}`", param.name()).into(), @@ -109,7 +139,7 @@ where } } - Ok(ArrowFunctionDecl::new(params, body)) + Ok(ArrowFunctionDecl::new(params.parameters, body)) } } diff --git a/boa/src/syntax/parser/expression/assignment/mod.rs b/boa/src/syntax/parser/expression/assignment/mod.rs index be30d52eeb..067aa39dcd 100644 --- a/boa/src/syntax/parser/expression/assignment/mod.rs +++ b/boa/src/syntax/parser/expression/assignment/mod.rs @@ -10,7 +10,9 @@ mod arrow_function; mod conditional; mod exponentiation; +mod r#yield; +use self::r#yield::YieldExpression; use self::{arrow_function::ArrowFunction, conditional::ConditionalExpression}; use crate::syntax::lexer::{Error as LexError, InputElement, TokenKind}; use crate::{ @@ -82,9 +84,12 @@ where let _timer = BoaProfiler::global().start_event("AssignmentExpression", "Parsing"); cursor.set_goal(InputElement::Div); - // Arrow function match cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() { - // a=>{} + // [+Yield]YieldExpression[?In, ?Await] + TokenKind::Keyword(Keyword::Yield) if self.allow_yield.0 => { + return YieldExpression::new(self.allow_in, self.allow_await).parse(cursor) + } + // ArrowFunction[?In, ?Yield, ?Await] -> ArrowParameters[?Yield, ?Await] -> BindingIdentifier[?Yield, ?Await] TokenKind::Identifier(_) | TokenKind::Keyword(Keyword::Yield) | TokenKind::Keyword(Keyword::Await) => { @@ -100,8 +105,7 @@ where } } } - - // (a,b)=>{} or (a,b) or (Expression) + // ArrowFunction[?In, ?Yield, ?Await] -> ArrowParameters[?Yield, ?Await] -> CoverParenthesizedExpressionAndArrowParameterList[?Yield, ?Await] TokenKind::Punctuator(Punctuator::OpenParen) => { if let Some(next_token) = cursor.peek(1)? { match *next_token.kind() { diff --git a/boa/src/syntax/parser/expression/assignment/yield.rs b/boa/src/syntax/parser/expression/assignment/yield.rs new file mode 100644 index 0000000000..75c2849fa0 --- /dev/null +++ b/boa/src/syntax/parser/expression/assignment/yield.rs @@ -0,0 +1,93 @@ +//! YieldExpression parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield +//! [spec]: https://tc39.es/ecma262/#prod-YieldExpression + +use crate::{ + syntax::{ + ast::{ + node::{Node, Yield}, + Keyword, Punctuator, + }, + lexer::TokenKind, + parser::{cursor::SemicolonResult, AllowAwait, AllowIn, Cursor, ParseResult, TokenParser}, + }, + BoaProfiler, +}; + +use std::io::Read; + +use super::AssignmentExpression; + +/// YieldExpression parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield +/// [spec]: https://tc39.es/ecma262/#prod-YieldExpression +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct YieldExpression { + allow_in: AllowIn, + allow_await: AllowAwait, +} + +impl YieldExpression { + /// Creates a new `YieldExpression` parser. + pub(in crate::syntax::parser) fn new(allow_in: I, allow_await: A) -> Self + where + I: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for YieldExpression +where + R: Read, +{ + type Output = Node; + + fn parse(self, cursor: &mut Cursor) -> ParseResult { + let _timer = BoaProfiler::global().start_event("YieldExpression", "Parsing"); + + cursor.expect(TokenKind::Keyword(Keyword::Yield), "yield expression")?; + + let mut expr = None; + let mut delegate = false; + + if let SemicolonResult::Found(_) = cursor.peek_semicolon()? { + cursor.expect( + TokenKind::Punctuator(Punctuator::Semicolon), + "token disappeared", + )?; + } else if let Ok(next_token) = cursor.peek_expect_no_lineterminator(0, "yield expression") { + if let TokenKind::Punctuator(Punctuator::Mul) = next_token.kind() { + cursor.expect(TokenKind::Punctuator(Punctuator::Mul), "token disappeared")?; + delegate = true; + expr = Some( + AssignmentExpression::new(self.allow_in, true, self.allow_await) + .parse(cursor)?, + ); + } else { + expr = Some( + AssignmentExpression::new(self.allow_in, true, self.allow_await) + .parse(cursor)?, + ); + } + } + + Ok(Node::Yield(Yield::new::>( + expr, delegate, + ))) + } +} diff --git a/boa/src/syntax/parser/expression/primary/async_function_expression/mod.rs b/boa/src/syntax/parser/expression/primary/async_function_expression/mod.rs index b931863c5e..d9292d6322 100644 --- a/boa/src/syntax/parser/expression/primary/async_function_expression/mod.rs +++ b/boa/src/syntax/parser/expression/primary/async_function_expression/mod.rs @@ -63,7 +63,24 @@ where return Err(ParseError::AbruptEnd); }; - cursor.expect(Punctuator::OpenParen, "async function expression")?; + // Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code, + // it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments". + if let Some(name) = &name { + if cursor.strict_mode() && ["eval", "arguments"].contains(&name.as_ref()) { + return Err(ParseError::lex(LexError::Syntax( + "Unexpected eval or arguments in strict mode".into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + + let params_start_position = cursor + .expect(Punctuator::OpenParen, "async function expression")? + .span() + .end(); let params = FormalParameters::new(false, true).parse(cursor)?; @@ -74,12 +91,30 @@ where cursor.expect(Punctuator::CloseBlock, "async function expression")?; + // Early Error: If the source code matching FormalParameters is strict mode code, + // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. + if (cursor.strict_mode() || body.strict()) && params.has_duplicates { + return Err(ParseError::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + params_start_position, + ))); + } + + // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of AsyncFunctionBody is true + // and IsSimpleParameterList of FormalParameters is false. + if body.strict() && !params.is_simple { + return Err(ParseError::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list".into(), + params_start_position, + ))); + } + // It is a Syntax Error if any element of the BoundNames of FormalParameters // also occurs in the LexicallyDeclaredNames of FunctionBody. // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors { let lexically_declared_names = body.lexically_declared_names(); - for param in params.as_ref() { + for param in params.parameters.as_ref() { if lexically_declared_names.contains(param.name()) { return Err(ParseError::lex(LexError::Syntax( format!("Redeclaration of formal parameter `{}`", param.name()).into(), @@ -92,6 +127,6 @@ where } } - Ok(AsyncFunctionExpr::new(name, params, body)) + Ok(AsyncFunctionExpr::new(name, params.parameters, body)) } } diff --git a/boa/src/syntax/parser/expression/primary/function_expression/mod.rs b/boa/src/syntax/parser/expression/primary/function_expression/mod.rs index 4083e9fa68..2513dafc34 100644 --- a/boa/src/syntax/parser/expression/primary/function_expression/mod.rs +++ b/boa/src/syntax/parser/expression/primary/function_expression/mod.rs @@ -58,7 +58,24 @@ where None }; - cursor.expect(Punctuator::OpenParen, "function expression")?; + // Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code, + // it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments". + if let Some(name) = &name { + if cursor.strict_mode() && ["eval", "arguments"].contains(&name.as_ref()) { + return Err(ParseError::lex(LexError::Syntax( + "Unexpected eval or arguments in strict mode".into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + + let params_start_position = cursor + .expect(Punctuator::OpenParen, "function expression")? + .span() + .end(); let params = FormalParameters::new(false, false).parse(cursor)?; @@ -69,12 +86,30 @@ where cursor.expect(Punctuator::CloseBlock, "function expression")?; + // Early Error: If the source code matching FormalParameters is strict mode code, + // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. + if (cursor.strict_mode() || body.strict()) && params.has_duplicates { + return Err(ParseError::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + params_start_position, + ))); + } + + // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of GeneratorBody is true + // and IsSimpleParameterList of FormalParameters is false. + if body.strict() && !params.is_simple { + return Err(ParseError::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list".into(), + params_start_position, + ))); + } + // It is a Syntax Error if any element of the BoundNames of FormalParameters // also occurs in the LexicallyDeclaredNames of FunctionBody. // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors { let lexically_declared_names = body.lexically_declared_names(); - for param in params.as_ref() { + for param in params.parameters.as_ref() { if lexically_declared_names.contains(param.name()) { return Err(ParseError::lex(LexError::Syntax( format!("Redeclaration of formal parameter `{}`", param.name()).into(), @@ -87,6 +122,6 @@ where } } - Ok(FunctionExpr::new(name, params, body)) + Ok(FunctionExpr::new(name, params.parameters, body)) } } diff --git a/boa/src/syntax/parser/expression/primary/generator_expression/mod.rs b/boa/src/syntax/parser/expression/primary/generator_expression/mod.rs new file mode 100644 index 0000000000..19914300b2 --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/generator_expression/mod.rs @@ -0,0 +1,132 @@ +//! Generator expression parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function* +//! [spec]: https://tc39.es/ecma262/#prod-GeneratorExpression + +#[cfg(test)] +mod tests; + +use crate::{ + syntax::{ + ast::{node::GeneratorExpr, Keyword, Punctuator}, + lexer::{Error as LexError, Position, TokenKind}, + parser::{ + function::{FormalParameters, FunctionBody}, + statement::BindingIdentifier, + Cursor, ParseError, TokenParser, + }, + }, + BoaProfiler, +}; + +use std::io::Read; + +/// Generator expression parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function* +/// [spec]: https://tc39.es/ecma262/#prod-GeneratorExpression +#[derive(Debug, Clone, Copy)] +pub(super) struct GeneratorExpression; + +impl TokenParser for GeneratorExpression +where + R: Read, +{ + type Output = GeneratorExpr; + + fn parse(self, cursor: &mut Cursor) -> Result { + let _timer = BoaProfiler::global().start_event("GeneratorExpression", "Parsing"); + + cursor.expect( + TokenKind::Punctuator(Punctuator::Mul), + "generator expression", + )?; + + let name = if let Some(token) = cursor.peek(0)? { + match token.kind() { + TokenKind::Identifier(_) + | TokenKind::Keyword(Keyword::Yield) + | TokenKind::Keyword(Keyword::Await) => { + Some(BindingIdentifier::new(true, false).parse(cursor)?) + } + _ => None, + } + } else { + None + }; + + // Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code, + // it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments". + if let Some(name) = &name { + if cursor.strict_mode() && ["eval", "arguments"].contains(&name.as_ref()) { + return Err(ParseError::lex(LexError::Syntax( + "Unexpected eval or arguments in strict mode".into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + + let params_start_position = cursor + .expect(Punctuator::OpenParen, "generator expression")? + .span() + .end(); + + let params = FormalParameters::new(true, false).parse(cursor)?; + + cursor.expect(Punctuator::CloseParen, "generator expression")?; + cursor.expect(Punctuator::OpenBlock, "generator expression")?; + + let body = FunctionBody::new(true, false).parse(cursor)?; + + cursor.expect(Punctuator::CloseBlock, "generator expression")?; + + // Early Error: If the source code matching FormalParameters is strict mode code, + // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. + if (cursor.strict_mode() || body.strict()) && params.has_duplicates { + return Err(ParseError::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + params_start_position, + ))); + } + + // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of GeneratorBody is true + // and IsSimpleParameterList of FormalParameters is false. + if body.strict() && !params.is_simple { + return Err(ParseError::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list".into(), + params_start_position, + ))); + } + + // It is a Syntax Error if any element of the BoundNames of FormalParameters + // also occurs in the LexicallyDeclaredNames of FunctionBody. + // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors + { + let lexically_declared_names = body.lexically_declared_names(); + for param in params.parameters.as_ref() { + if lexically_declared_names.contains(param.name()) { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of formal parameter `{}`", param.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + } + + Ok(GeneratorExpr::new(name, params.parameters, body)) + } +} diff --git a/boa/src/syntax/parser/expression/primary/generator_expression/tests.rs b/boa/src/syntax/parser/expression/primary/generator_expression/tests.rs new file mode 100644 index 0000000000..b3a9d3f51c --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/generator_expression/tests.rs @@ -0,0 +1,57 @@ +use crate::syntax::{ + ast::{ + node::{Declaration, DeclarationList, GeneratorExpr, StatementList, Yield}, + Const, + }, + parser::tests::check_parser, +}; + +#[test] +fn check_generator_function_expression() { + check_parser( + "const gen = function*() { + yield 1; + }; + ", + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + "gen", + Some( + GeneratorExpr::new::>, _, StatementList>( + None, + [], + vec![Yield::new(Const::from(1), false).into()].into(), + ) + .into(), + ), + )] + .into(), + ) + .into()], + ); +} + +#[test] +fn check_generator_function_delegate_yield_expression() { + check_parser( + "const gen = function*() { + yield* 1; + }; + ", + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + "gen", + Some( + GeneratorExpr::new::>, _, StatementList>( + None, + [], + vec![Yield::new(Const::from(1), true).into()].into(), + ) + .into(), + ), + )] + .into(), + ) + .into()], + ); +} diff --git a/boa/src/syntax/parser/expression/primary/mod.rs b/boa/src/syntax/parser/expression/primary/mod.rs index 20ec3c8656..f66d1940b2 100644 --- a/boa/src/syntax/parser/expression/primary/mod.rs +++ b/boa/src/syntax/parser/expression/primary/mod.rs @@ -10,6 +10,7 @@ mod array_initializer; mod async_function_expression; mod function_expression; +mod generator_expression; mod object_initializer; mod template; #[cfg(test)] @@ -17,7 +18,8 @@ mod tests; use self::{ array_initializer::ArrayLiteral, async_function_expression::AsyncFunctionExpression, - function_expression::FunctionExpression, object_initializer::ObjectLiteral, + function_expression::FunctionExpression, generator_expression::GeneratorExpression, + object_initializer::ObjectLiteral, }; use super::Expression; use crate::{ @@ -80,7 +82,12 @@ where match tok.kind() { TokenKind::Keyword(Keyword::This) => Ok(Node::This), TokenKind::Keyword(Keyword::Function) => { - FunctionExpression.parse(cursor).map(Node::from) + let next_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; + if next_token.kind() == &TokenKind::Punctuator(Punctuator::Mul) { + GeneratorExpression.parse(cursor).map(Node::from) + } else { + FunctionExpression.parse(cursor).map(Node::from) + } } TokenKind::Keyword(Keyword::Async) => AsyncFunctionExpression::new(self.allow_yield) .parse(cursor) @@ -106,7 +113,39 @@ where } TokenKind::BooleanLiteral(boolean) => Ok(Const::from(*boolean).into()), TokenKind::NullLiteral => Ok(Const::Null.into()), - TokenKind::Identifier(ident) => Ok(Identifier::from(ident.as_ref()).into()), // TODO: IdentifierReference + TokenKind::Identifier(ident) => Ok(Identifier::from(ident.as_ref()).into()), + TokenKind::Keyword(Keyword::Yield) if self.allow_yield.0 => { + // Early Error: It is a Syntax Error if this production has a [Yield] parameter and StringValue of Identifier is "yield". + Err(ParseError::general( + "Unexpected identifier", + tok.span().start(), + )) + } + TokenKind::Keyword(Keyword::Yield) if !self.allow_yield.0 => { + if cursor.strict_mode() { + return Err(ParseError::general( + "Unexpected strict mode reserved word", + tok.span().start(), + )); + } + Ok(Identifier::from("yield").into()) + } + TokenKind::Keyword(Keyword::Await) if self.allow_await.0 => { + // Early Error: It is a Syntax Error if this production has an [Await] parameter and StringValue of Identifier is "await". + Err(ParseError::general( + "Unexpected identifier", + tok.span().start(), + )) + } + TokenKind::Keyword(Keyword::Await) if !self.allow_await.0 => { + if cursor.strict_mode() { + return Err(ParseError::general( + "Unexpected strict mode reserved word", + tok.span().start(), + )); + } + Ok(Identifier::from("await").into()) + } TokenKind::StringLiteral(s) => Ok(Const::from(s.as_ref()).into()), TokenKind::TemplateNoSubstitution(template_string) => { Ok(Const::from(template_string.to_owned_cooked().map_err(ParseError::lex)?).into()) diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs index b8b2dba8db..041d713caf 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -9,14 +9,13 @@ #[cfg(test)] mod tests; -use crate::syntax::ast::node::{Identifier, PropertyName}; -use crate::syntax::lexer::TokenKind; use crate::{ syntax::{ ast::{ - node::{self, FunctionExpr, MethodDefinitionKind, Node, Object}, - Punctuator, + node::{self, FunctionExpr, Identifier, MethodDefinitionKind, Node, Object}, + Keyword, Punctuator, }, + lexer::{Error as LexError, Position, TokenKind}, parser::{ expression::AssignmentExpression, function::{FormalParameters, FunctionBody}, @@ -129,226 +128,331 @@ where fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("PropertyDefinition", "Parsing"); + // IdentifierReference[?Yield, ?Await] + if let Some(next_token) = cursor.peek(1)? { + match next_token.kind() { + TokenKind::Punctuator(Punctuator::CloseBlock) + | TokenKind::Punctuator(Punctuator::Comma) => { + let token = cursor.next()?.ok_or(ParseError::AbruptEnd)?; + let ident = match token.kind() { + TokenKind::Identifier(ident) => Identifier::from(ident.as_ref()), + TokenKind::Keyword(Keyword::Yield) if self.allow_yield.0 => { + // Early Error: It is a Syntax Error if this production has a [Yield] parameter and StringValue of Identifier is "yield". + return Err(ParseError::general( + "Unexpected identifier", + token.span().start(), + )); + } + TokenKind::Keyword(Keyword::Yield) if !self.allow_yield.0 => { + if cursor.strict_mode() { + // Early Error: It is a Syntax Error if the code matched by this production is contained in strict mode code. + return Err(ParseError::general( + "Unexpected strict mode reserved word", + token.span().start(), + )); + } + Identifier::from("yield") + } + TokenKind::Keyword(Keyword::Await) if self.allow_await.0 => { + // Early Error: It is a Syntax Error if this production has an [Await] parameter and StringValue of Identifier is "await". + return Err(ParseError::general( + "Unexpected identifier", + token.span().start(), + )); + } + TokenKind::Keyword(Keyword::Await) if !self.allow_await.0 => { + if cursor.strict_mode() { + // Early Error: It is a Syntax Error if the code matched by this production is contained in strict mode code. + return Err(ParseError::general( + "Unexpected strict mode reserved word", + token.span().start(), + )); + } + Identifier::from("yield") + } + _ => { + return Err(ParseError::unexpected( + token.clone(), + "expected IdentifierReference", + )); + } + }; + return Ok(node::PropertyDefinition::property( + ident.clone().as_ref(), + ident, + )); + } + _ => {} + } + } + + // ... AssignmentExpression[+In, ?Yield, ?Await] if cursor.next_if(Punctuator::Spread)?.is_some() { let node = AssignmentExpression::new(true, self.allow_yield, self.allow_await) .parse(cursor)?; return Ok(node::PropertyDefinition::SpreadObject(node)); } - // ComputedPropertyName - // https://tc39.es/ecma262/#prod-ComputedPropertyName - if cursor.next_if(Punctuator::OpenBracket)?.is_some() { - let node = AssignmentExpression::new(false, self.allow_yield, self.allow_await) - .parse(cursor)?; - cursor.expect(Punctuator::CloseBracket, "expected token ']'")?; - let next_token = cursor.next()?.ok_or(ParseError::AbruptEnd)?; - match next_token.kind() { - TokenKind::Punctuator(Punctuator::Colon) => { - let val = AssignmentExpression::new(false, self.allow_yield, self.allow_await) - .parse(cursor)?; - return Ok(node::PropertyDefinition::property(node, val)); - } - TokenKind::Punctuator(Punctuator::OpenParen) => { - return MethodDefinition::new(self.allow_yield, self.allow_await, node) - .parse(cursor); - } - _ => { - return Err(ParseError::unexpected( - next_token, - "expected AssignmentExpression or MethodDefinition", - )) - } + // MethodDefinition[?Yield, ?Await] -> GeneratorMethod[?Yield, ?Await] + if cursor.next_if(Punctuator::Mul)?.is_some() { + let property_name = + PropertyName::new(self.allow_yield, self.allow_await).parse(cursor)?; + + let params_start_position = cursor + .expect(Punctuator::OpenParen, "generator method definition")? + .span() + .start(); + let params = FormalParameters::new(false, false).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "generator method definition")?; + + // Early Error: UniqueFormalParameters : FormalParameters + if params.has_duplicates { + return Err(ParseError::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + params_start_position, + ))); } - } - // Peek for '}' or ',' to indicate shorthand property name - if let Some(next_token) = cursor.peek(1)? { - match next_token.kind() { - TokenKind::Punctuator(Punctuator::CloseBlock) - | TokenKind::Punctuator(Punctuator::Comma) => { - let token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; - if let TokenKind::Identifier(ident) = token.kind() { - // ident is both the name and value in a shorthand property - let name = ident.to_string(); - let value = Identifier::from(ident.to_owned()); - cursor.next()?.expect("token vanished"); // Consume the token. - return Ok(node::PropertyDefinition::property(name, value)); - } else { - // Anything besides an identifier is a syntax error - return Err(ParseError::unexpected(token.clone(), "object literal")); + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "generator method definition", + )?; + let body = FunctionBody::new(true, false).parse(cursor)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "generator method definition", + )?; + + // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true + // and IsSimpleParameterList of UniqueFormalParameters is false. + if body.strict() && !params.is_simple { + return Err(ParseError::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list" + .into(), + params_start_position, + ))); + } + + // Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also + // occurs in the LexicallyDeclaredNames of GeneratorBody. + { + let lexically_declared_names = body.lexically_declared_names(); + for param in params.parameters.as_ref() { + if lexically_declared_names.contains(param.name()) { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of formal parameter `{}`", param.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); } } - _ => {} } + + return Ok(node::PropertyDefinition::method_definition( + MethodDefinitionKind::Generator, + property_name, + FunctionExpr::new(None, params.parameters, body), + )); } - let prop_name = cursor.next()?.ok_or(ParseError::AbruptEnd)?.to_string(); + let mut property_name = + PropertyName::new(self.allow_yield, self.allow_await).parse(cursor)?; + + // PropertyName[?Yield, ?Await] : AssignmentExpression[+In, ?Yield, ?Await] if cursor.next_if(Punctuator::Colon)?.is_some() { - let val = AssignmentExpression::new(true, self.allow_yield, self.allow_await) + let value = AssignmentExpression::new(true, self.allow_yield, self.allow_await) .parse(cursor)?; - return Ok(node::PropertyDefinition::property(prop_name, val)); + return Ok(node::PropertyDefinition::property(property_name, value)); } - // TODO GeneratorMethod - // https://tc39.es/ecma262/#prod-GeneratorMethod + let ordinary_method = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() + == &TokenKind::Punctuator(Punctuator::OpenParen); - if prop_name.as_str() == "async" { - // TODO - AsyncMethod. - // https://tc39.es/ecma262/#prod-AsyncMethod + match property_name { + // MethodDefinition[?Yield, ?Await] -> get ClassElementName[?Yield, ?Await] ( ) { FunctionBody[~Yield, ~Await] } + node::PropertyName::Literal(str) if str.as_ref() == "get" && !ordinary_method => { + property_name = + PropertyName::new(self.allow_yield, self.allow_await).parse(cursor)?; - // TODO - AsyncGeneratorMethod - // https://tc39.es/ecma262/#prod-AsyncGeneratorMethod - } + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenParen), + "get method definition", + )?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseParen), + "get method definition", + )?; - if cursor - .next_if(TokenKind::Punctuator(Punctuator::OpenParen))? - .is_some() - || ["get", "set"].contains(&prop_name.as_str()) - { - return MethodDefinition::new(self.allow_yield, self.allow_await, prop_name) - .parse(cursor); - } + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "get method definition", + )?; + let body = FunctionBody::new(false, false).parse(cursor)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "get method definition", + )?; + + Ok(node::PropertyDefinition::method_definition( + MethodDefinitionKind::Get, + property_name, + FunctionExpr::new(None, [], body), + )) + } + // MethodDefinition[?Yield, ?Await] -> set ClassElementName[?Yield, ?Await] ( PropertySetParameterList ) { FunctionBody[~Yield, ~Await] } + node::PropertyName::Literal(str) if str.as_ref() == "set" && !ordinary_method => { + property_name = + PropertyName::new(self.allow_yield, self.allow_await).parse(cursor)?; + + let params_start_position = cursor + .expect( + TokenKind::Punctuator(Punctuator::OpenParen), + "set method definition", + )? + .span() + .end(); + let params = FormalParameters::new(false, false).parse(cursor)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseParen), + "set method definition", + )?; + if params.parameters.len() != 1 { + return Err(ParseError::general( + "set method definition must have one parameter", + params_start_position, + )); + } - let pos = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.span().start(); - Err(ParseError::general("expected property definition", pos)) + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "set method definition", + )?; + let body = FunctionBody::new(false, false).parse(cursor)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "set method definition", + )?; + + // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true + // and IsSimpleParameterList of PropertySetParameterList is false. + if body.strict() && !params.is_simple { + return Err(ParseError::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list" + .into(), + params_start_position, + ))); + } + + Ok(node::PropertyDefinition::method_definition( + MethodDefinitionKind::Set, + property_name, + FunctionExpr::new(None, params.parameters, body), + )) + } + // MethodDefinition[?Yield, ?Await] -> ClassElementName[?Yield, ?Await] ( UniqueFormalParameters[~Yield, ~Await] ) { FunctionBody[~Yield, ~Await] } + _ => { + let params_start_position = cursor + .expect( + TokenKind::Punctuator(Punctuator::OpenParen), + "method definition", + )? + .span() + .end(); + let params = FormalParameters::new(false, false).parse(cursor)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseParen), + "method definition", + )?; + + // Early Error: UniqueFormalParameters : FormalParameters + if params.has_duplicates { + return Err(ParseError::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + params_start_position, + ))); + } + + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "method definition", + )?; + let body = FunctionBody::new(false, false).parse(cursor)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "method definition", + )?; + + // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true + // and IsSimpleParameterList of UniqueFormalParameters is false. + if body.strict() && !params.is_simple { + return Err(ParseError::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list" + .into(), + params_start_position, + ))); + } + + Ok(node::PropertyDefinition::method_definition( + MethodDefinitionKind::Ordinary, + property_name, + FunctionExpr::new(None, params.parameters, body), + )) + } + } } } -/// Parses a method definition. +/// Parses a property name. /// /// More information: /// - [ECMAScript specification][spec] /// -/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition +/// [spec]: https://tc39.es/ecma262/#prod-PropertyName #[derive(Debug, Clone)] -struct MethodDefinition { +struct PropertyName { allow_yield: AllowYield, allow_await: AllowAwait, - identifier: PropertyName, } -impl MethodDefinition { - /// Creates a new `MethodDefinition` parser. - fn new(allow_yield: Y, allow_await: A, identifier: I) -> Self +impl PropertyName { + /// Creates a new `PropertyName` parser. + fn new(allow_yield: Y, allow_await: A) -> Self where Y: Into, A: Into, - I: Into, { Self { allow_yield: allow_yield.into(), allow_await: allow_await.into(), - identifier: identifier.into(), } } } -impl TokenParser for MethodDefinition +impl TokenParser for PropertyName where R: Read, { - type Output = node::PropertyDefinition; + type Output = node::PropertyName; fn parse(self, cursor: &mut Cursor) -> Result { - let _timer = BoaProfiler::global().start_event("MethodDefinition", "Parsing"); - - let (method_kind, prop_name, params) = match self.identifier { - PropertyName::Literal(ident) - if ["get", "set"].contains(&ident.as_ref()) - && matches!( - cursor.peek(0)?.map(|t| t.kind()), - Some(&TokenKind::Identifier(_)) - | Some(&TokenKind::Keyword(_)) - | Some(&TokenKind::BooleanLiteral(_)) - | Some(&TokenKind::NullLiteral) - | Some(&TokenKind::NumericLiteral(_)) - ) => - { - let prop_name = cursor.next()?.ok_or(ParseError::AbruptEnd)?.to_string(); - cursor.expect( - TokenKind::Punctuator(Punctuator::OpenParen), - "property method definition", - )?; - let first_param = cursor.peek(0)?.expect("current token disappeared").clone(); - let params = FormalParameters::new(false, false).parse(cursor)?; - cursor.expect(Punctuator::CloseParen, "method definition")?; - if ident.as_ref() == "get" { - if !params.is_empty() { - return Err(ParseError::unexpected( - first_param, - "getter functions must have no arguments", - )); - } - (MethodDefinitionKind::Get, prop_name.into(), params) - } else { - if params.len() != 1 { - return Err(ParseError::unexpected( - first_param, - "setter functions must have one argument", - )); - } - (MethodDefinitionKind::Set, prop_name.into(), params) - } - } - PropertyName::Literal(ident) - if ["get", "set"].contains(&ident.as_ref()) - && matches!( - cursor.peek(0)?.map(|t| t.kind()), - Some(&TokenKind::Punctuator(Punctuator::OpenBracket)) - ) => - { - cursor.expect(Punctuator::OpenBracket, "token vanished")?; - let prop_name = - AssignmentExpression::new(false, self.allow_yield, self.allow_await) - .parse(cursor)?; - cursor.expect(Punctuator::CloseBracket, "expected token ']'")?; - cursor.expect( - TokenKind::Punctuator(Punctuator::OpenParen), - "property method definition", - )?; - let first_param = cursor.peek(0)?.expect("current token disappeared").clone(); - let params = FormalParameters::new(false, false).parse(cursor)?; - cursor.expect(Punctuator::CloseParen, "method definition")?; - if ident.as_ref() == "get" { - if !params.is_empty() { - return Err(ParseError::unexpected( - first_param, - "getter functions must have no arguments", - )); - } - (MethodDefinitionKind::Get, prop_name.into(), params) - } else { - if params.len() != 1 { - return Err(ParseError::unexpected( - first_param, - "setter functions must have one argument", - )); - } - (MethodDefinitionKind::Set, prop_name.into(), params) - } - } - prop_name => { - let params = FormalParameters::new(false, false).parse(cursor)?; - cursor.expect(Punctuator::CloseParen, "method definition")?; - (MethodDefinitionKind::Ordinary, prop_name, params) - } - }; - - cursor.expect( - TokenKind::Punctuator(Punctuator::OpenBlock), - "property method definition", - )?; - let body = FunctionBody::new(false, false).parse(cursor)?; - cursor.expect( - TokenKind::Punctuator(Punctuator::CloseBlock), - "property method definition", - )?; - - Ok(node::PropertyDefinition::method_definition( - method_kind, - prop_name, - FunctionExpr::new(None, params, body), - )) + let _timer = BoaProfiler::global().start_event("PropertyName", "Parsing"); + + // ComputedPropertyName[?Yield, ?Await] -> [ AssignmentExpression[+In, ?Yield, ?Await] ] + if cursor.next_if(Punctuator::OpenBracket)?.is_some() { + let node = AssignmentExpression::new(false, self.allow_yield, self.allow_await) + .parse(cursor)?; + cursor.expect(Punctuator::CloseBracket, "expected token ']'")?; + return Ok(node.into()); + } + + // LiteralPropertyName + Ok(cursor + .next()? + .ok_or(ParseError::AbruptEnd)? + .to_string() + .into()) } } diff --git a/boa/src/syntax/parser/function/mod.rs b/boa/src/syntax/parser/function/mod.rs index 7367f6dbd9..40887c1cab 100644 --- a/boa/src/syntax/parser/function/mod.rs +++ b/boa/src/syntax/parser/function/mod.rs @@ -13,7 +13,7 @@ mod tests; use crate::{ syntax::{ ast::{node, Punctuator}, - lexer::{InputElement, TokenKind}, + lexer::{Error as LexError, InputElement, TokenKind}, parser::{ expression::Initializer, statement::{BindingIdentifier, StatementList}, @@ -22,7 +22,15 @@ use crate::{ }, BoaProfiler, }; -use std::{collections::HashSet, io::Read}; +use rustc_hash::FxHashSet; +use std::io::Read; + +/// Intermediate type for a list of FormalParameters with some meta information. +pub(in crate::syntax::parser) struct FormalParameterList { + pub(in crate::syntax::parser) parameters: Box<[node::FormalParameter]>, + pub(in crate::syntax::parser) is_simple: bool, + pub(in crate::syntax::parser) has_duplicates: bool, +} /// Formal parameters parsing. /// @@ -56,25 +64,31 @@ impl TokenParser for FormalParameters where R: Read, { - type Output = Box<[node::FormalParameter]>; + type Output = FormalParameterList; fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("FormalParameters", "Parsing"); cursor.set_goal(InputElement::RegExp); let mut params = Vec::new(); - let mut param_names = HashSet::new(); - - if cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() - == &TokenKind::Punctuator(Punctuator::CloseParen) - { - return Ok(params.into_boxed_slice()); + let mut is_simple = true; + let mut has_duplicates = false; + + let next_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; + if next_token.kind() == &TokenKind::Punctuator(Punctuator::CloseParen) { + return Ok(FormalParameterList { + parameters: params.into_boxed_slice(), + is_simple, + has_duplicates, + }); } + let start_position = next_token.span().start(); + + let mut parameter_names = FxHashSet::default(); loop { let mut rest_param = false; - let position = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.span().start(); let next_param = match cursor.peek(0)? { Some(tok) if tok.kind() == &TokenKind::Punctuator(Punctuator::Spread) => { rest_param = true; @@ -82,11 +96,15 @@ where } _ => FormalParameter::new(self.allow_yield, self.allow_await).parse(cursor)?, }; - if param_names.contains(next_param.name()) { - return Err(ParseError::general("duplicate parameter name", position)); + + if next_param.is_rest_param() || next_param.init().is_some() { + is_simple = false; + } + if parameter_names.contains(next_param.name()) { + has_duplicates = true; } + parameter_names.insert(Box::from(next_param.name())); - param_names.insert(Box::from(next_param.name())); params.push(next_param); if cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() @@ -105,7 +123,20 @@ where cursor.expect(Punctuator::Comma, "parameter list")?; } - Ok(params.into_boxed_slice()) + // Early Error: It is a Syntax Error if IsSimpleParameterList of FormalParameterList is false + // and BoundNames of FormalParameterList contains any duplicate elements. + if !is_simple && has_duplicates { + return Err(ParseError::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + start_position, + ))); + } + + Ok(FormalParameterList { + parameters: params.into_boxed_slice(), + is_simple, + has_duplicates, + }) } } @@ -173,14 +204,14 @@ where /// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Parameter /// [spec]: https://tc39.es/ecma262/#prod-FormalParameter #[derive(Debug, Clone, Copy)] -struct FormalParameter { +pub(in crate::syntax::parser) struct FormalParameter { allow_yield: AllowYield, allow_await: AllowAwait, } impl FormalParameter { /// Creates a new `FormalParameter` parser. - fn new(allow_yield: Y, allow_await: A) -> Self + pub(in crate::syntax::parser) fn new(allow_yield: Y, allow_await: A) -> Self where Y: Into, A: Into, diff --git a/boa/src/syntax/parser/function/tests.rs b/boa/src/syntax/parser/function/tests.rs index cd66a0d54c..a6b97c7f88 100644 --- a/boa/src/syntax/parser/function/tests.rs +++ b/boa/src/syntax/parser/function/tests.rs @@ -21,10 +21,27 @@ fn check_basic() { ); } -/// Checks for duplicate parameter names. +/// Checks if duplicate parameter names are allowed with strict mode off. #[test] -fn check_duplicates() { - let js = "function foo(a, a) {}"; +fn check_duplicates_strict_off() { + check_parser( + "function foo(a, a) { return a; }", + vec![FunctionDecl::new( + Box::from("foo"), + vec![ + FormalParameter::new("a", None, false), + FormalParameter::new("a", None, false), + ], + vec![Return::new(Identifier::from("a"), None).into()], + ) + .into()], + ); +} + +/// Checks if duplicate parameter names are an error with strict mode on. +#[test] +fn check_duplicates_strict_on() { + let js = "'use strict'; function foo(a, a) {}"; let res = Parser::new(js.as_bytes(), false).parse_all(); dbg!(&res); diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs b/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs index 0c9f3c5e97..9e8e1d198e 100644 --- a/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs +++ b/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs @@ -2,12 +2,9 @@ mod tests; use crate::syntax::{ - ast::{node::AsyncFunctionDecl, Keyword, Punctuator}, - lexer::TokenKind, + ast::{node::AsyncFunctionDecl, Keyword}, parser::{ - function::FormalParameters, - function::FunctionBody, - statement::{BindingIdentifier, LexError, Position}, + statement::declaration::hoistable::{parse_callable_declaration, CallableDeclaration}, AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, }, }; @@ -44,6 +41,33 @@ impl AsyncFunctionDeclaration { } } +impl CallableDeclaration for AsyncFunctionDeclaration { + fn error_context(&self) -> &'static str { + "async function declaration" + } + fn is_default(&self) -> bool { + self.is_default.0 + } + fn name_allow_yield(&self) -> bool { + self.allow_yield.0 + } + fn name_allow_await(&self) -> bool { + self.allow_await.0 + } + fn parameters_allow_yield(&self) -> bool { + false + } + fn parameters_allow_await(&self) -> bool { + true + } + fn body_allow_yield(&self) -> bool { + false + } + fn body_allow_await(&self) -> bool { + true + } +} + impl TokenParser for AsyncFunctionDeclaration where R: Read, @@ -54,56 +78,9 @@ where cursor.expect(Keyword::Async, "async function declaration")?; cursor.peek_expect_no_lineterminator(0, "async function declaration")?; cursor.expect(Keyword::Function, "async function declaration")?; - let tok = cursor.peek(0)?; - - let name = if let Some(token) = tok { - match token.kind() { - TokenKind::Punctuator(Punctuator::OpenParen) => { - if !self.is_default.0 { - return Err(ParseError::unexpected( - token.clone(), - " in async function declaration", - )); - } - None - } - _ => { - Some(BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?) - } - } - } else { - return Err(ParseError::AbruptEnd); - }; - - cursor.expect(Punctuator::OpenParen, "async function declaration")?; - - let params = FormalParameters::new(false, true).parse(cursor)?; - cursor.expect(Punctuator::CloseParen, "async function declaration")?; - cursor.expect(Punctuator::OpenBlock, "async function declaration")?; - - let body = FunctionBody::new(false, true).parse(cursor)?; - - cursor.expect(Punctuator::CloseBlock, "async function declaration")?; - - // It is a Syntax Error if any element of the BoundNames of FormalParameters - // also occurs in the LexicallyDeclaredNames of FunctionBody. - // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors - { - let lexically_declared_names = body.lexically_declared_names(); - for param in params.as_ref() { - if lexically_declared_names.contains(param.name()) { - return Err(ParseError::lex(LexError::Syntax( - format!("Redeclaration of formal parameter `{}`", param.name()).into(), - match cursor.peek(0)? { - Some(token) => token.span().end(), - None => Position::new(1, 1), - }, - ))); - } - } - } + let result = parse_callable_declaration(&self, cursor)?; - Ok(AsyncFunctionDecl::new(name, params, body)) + Ok(AsyncFunctionDecl::new(result.0, result.1, result.2)) } } diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs b/boa/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs index a2f3c765b4..f878d2684d 100644 --- a/boa/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs +++ b/boa/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs @@ -2,11 +2,9 @@ mod tests; use crate::syntax::{ - ast::{node::FunctionDecl, Keyword, Punctuator}, + ast::{node::FunctionDecl, Keyword}, parser::{ - function::FormalParameters, - function::FunctionBody, - statement::{BindingIdentifier, LexError, Position}, + statement::declaration::hoistable::{parse_callable_declaration, CallableDeclaration}, AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, }, }; @@ -22,7 +20,7 @@ use std::io::Read; /// [spec]: https://tc39.es/ecma262/#prod-FunctionDeclaration #[derive(Debug, Clone, Copy)] -pub(super) struct FunctionDeclaration { +pub(in crate::syntax::parser) struct FunctionDeclaration { allow_yield: AllowYield, allow_await: AllowAwait, is_default: AllowDefault, @@ -30,7 +28,11 @@ pub(super) struct FunctionDeclaration { impl FunctionDeclaration { /// Creates a new `FunctionDeclaration` parser. - pub(super) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self + pub(in crate::syntax::parser) fn new( + allow_yield: Y, + allow_await: A, + is_default: D, + ) -> Self where Y: Into, A: Into, @@ -44,6 +46,33 @@ impl FunctionDeclaration { } } +impl CallableDeclaration for FunctionDeclaration { + fn error_context(&self) -> &'static str { + "function declaration" + } + fn is_default(&self) -> bool { + self.is_default.0 + } + fn name_allow_yield(&self) -> bool { + self.allow_yield.0 + } + fn name_allow_await(&self) -> bool { + self.allow_await.0 + } + fn parameters_allow_yield(&self) -> bool { + false + } + fn parameters_allow_await(&self) -> bool { + false + } + fn body_allow_yield(&self) -> bool { + self.allow_yield.0 + } + fn body_allow_await(&self) -> bool { + self.allow_await.0 + } +} + impl TokenParser for FunctionDeclaration where R: Read, @@ -53,38 +82,8 @@ where fn parse(self, cursor: &mut Cursor) -> Result { cursor.expect(Keyword::Function, "function declaration")?; - // TODO: If self.is_default, then this can be empty. - let name = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; - - cursor.expect(Punctuator::OpenParen, "function declaration")?; - - let params = FormalParameters::new(false, false).parse(cursor)?; - - cursor.expect(Punctuator::CloseParen, "function declaration")?; - cursor.expect(Punctuator::OpenBlock, "function declaration")?; - - let body = FunctionBody::new(self.allow_yield, self.allow_await).parse(cursor)?; - - cursor.expect(Punctuator::CloseBlock, "function declaration")?; - - // It is a Syntax Error if any element of the BoundNames of FormalParameters - // also occurs in the LexicallyDeclaredNames of FunctionBody. - // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors - { - let lexically_declared_names = body.lexically_declared_names(); - for param in params.as_ref() { - if lexically_declared_names.contains(param.name()) { - return Err(ParseError::lex(LexError::Syntax( - format!("Redeclaration of formal parameter `{}`", param.name()).into(), - match cursor.peek(0)? { - Some(token) => token.span().end(), - None => Position::new(1, 1), - }, - ))); - } - } - } + let result = parse_callable_declaration(&self, cursor)?; - Ok(FunctionDecl::new(name, params, body)) + Ok(FunctionDecl::new(result.0, result.1, result.2)) } } diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/generator_decl/mod.rs b/boa/src/syntax/parser/statement/declaration/hoistable/generator_decl/mod.rs new file mode 100644 index 0000000000..67eed08a13 --- /dev/null +++ b/boa/src/syntax/parser/statement/declaration/hoistable/generator_decl/mod.rs @@ -0,0 +1,85 @@ +#[cfg(test)] +mod tests; + +use crate::syntax::{ + ast::{node::declaration::generator_decl::GeneratorDecl, Keyword, Punctuator}, + parser::{ + statement::declaration::hoistable::{parse_callable_declaration, CallableDeclaration}, + AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, + }, +}; +use std::io::Read; + +/// Generator declaration parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* +/// [spec]: https://tc39.es/ecma262/#prod-GeneratorDeclaration +#[derive(Debug, Clone, Copy)] +pub(super) struct GeneratorDeclaration { + allow_yield: AllowYield, + allow_await: AllowAwait, + is_default: AllowDefault, +} + +impl GeneratorDeclaration { + /// Creates a new `GeneratorDeclaration` parser. + pub(super) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self + where + Y: Into, + A: Into, + D: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + is_default: is_default.into(), + } + } +} + +impl CallableDeclaration for GeneratorDeclaration { + fn error_context(&self) -> &'static str { + "generator declaration" + } + fn is_default(&self) -> bool { + self.is_default.0 + } + fn name_allow_yield(&self) -> bool { + self.allow_yield.0 + } + fn name_allow_await(&self) -> bool { + self.allow_await.0 + } + fn parameters_allow_yield(&self) -> bool { + true + } + fn parameters_allow_await(&self) -> bool { + false + } + fn body_allow_yield(&self) -> bool { + true + } + fn body_allow_await(&self) -> bool { + false + } +} + +impl TokenParser for GeneratorDeclaration +where + R: Read, +{ + type Output = GeneratorDecl; + + fn parse(self, cursor: &mut Cursor) -> Result { + cursor.expect(Keyword::Function, "generator declaration")?; + cursor.expect(Punctuator::Mul, "generator declaration")?; + + let result = parse_callable_declaration(&self, cursor)?; + + Ok(GeneratorDecl::new(result.0, result.1, result.2)) + } +} diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs b/boa/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs new file mode 100644 index 0000000000..d914972d1f --- /dev/null +++ b/boa/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs @@ -0,0 +1,9 @@ +use crate::syntax::{ast::node::GeneratorDecl, parser::tests::check_parser}; + +#[test] +fn generator_function_declaration() { + check_parser( + "function* gen() {}", + vec![GeneratorDecl::new(Box::from("gen"), vec![], vec![]).into()], + ); +} diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/mod.rs b/boa/src/syntax/parser/statement/declaration/hoistable/mod.rs index bfb104d67d..4f3cb18eac 100644 --- a/boa/src/syntax/parser/statement/declaration/hoistable/mod.rs +++ b/boa/src/syntax/parser/statement/declaration/hoistable/mod.rs @@ -10,15 +10,20 @@ mod tests; mod async_function_decl; mod function_decl; +mod generator_decl; use async_function_decl::AsyncFunctionDeclaration; -use function_decl::FunctionDeclaration; +pub(in crate::syntax::parser) use function_decl::FunctionDeclaration; +use generator_decl::GeneratorDeclaration; use crate::{ syntax::{ - ast::{Keyword, Node}, - lexer::TokenKind, + ast::node::{FormalParameter, StatementList}, + ast::{Keyword, Node, Punctuator}, + lexer::{Position, TokenKind}, parser::{ + function::{FormalParameters, FunctionBody}, + statement::{BindingIdentifier, LexError}, AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, ParseResult, TokenParser, }, }, @@ -67,9 +72,16 @@ where match tok.kind() { TokenKind::Keyword(Keyword::Function) => { - FunctionDeclaration::new(self.allow_yield, self.allow_await, self.is_default) - .parse(cursor) - .map(Node::from) + let next_token = cursor.peek(1)?.ok_or(ParseError::AbruptEnd)?; + if let TokenKind::Punctuator(Punctuator::Mul) = next_token.kind() { + GeneratorDeclaration::new(self.allow_yield, self.allow_await, self.is_default) + .parse(cursor) + .map(Node::from) + } else { + FunctionDeclaration::new(self.allow_yield, self.allow_await, self.is_default) + .parse(cursor) + .map(Node::from) + } } TokenKind::Keyword(Keyword::Async) => { AsyncFunctionDeclaration::new(self.allow_yield, self.allow_await, false) @@ -80,3 +92,104 @@ where } } } + +trait CallableDeclaration { + fn error_context(&self) -> &'static str; + fn is_default(&self) -> bool; + fn name_allow_yield(&self) -> bool; + fn name_allow_await(&self) -> bool; + fn parameters_allow_yield(&self) -> bool; + fn parameters_allow_await(&self) -> bool; + fn body_allow_yield(&self) -> bool; + fn body_allow_await(&self) -> bool; +} + +// This is a helper function to not duplicate code in the individual callable deceleration parsers. +#[inline] +#[allow(clippy::type_complexity)] +fn parse_callable_declaration( + c: &C, + cursor: &mut Cursor, +) -> Result<(Box, Box<[FormalParameter]>, StatementList), ParseError> { + let next_token = cursor.peek(0)?; + let name = if let Some(token) = next_token { + match token.kind() { + TokenKind::Punctuator(Punctuator::OpenParen) => { + if !c.is_default() { + return Err(ParseError::unexpected(token.clone(), c.error_context())); + } + "default".into() + } + _ => { + BindingIdentifier::new(c.name_allow_yield(), c.name_allow_await()).parse(cursor)? + } + } + } else { + return Err(ParseError::AbruptEnd); + }; + + // Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code, + // it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments". + if cursor.strict_mode() && ["eval", "arguments"].contains(&name.as_ref()) { + return Err(ParseError::lex(LexError::Syntax( + "Unexpected eval or arguments in strict mode".into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + + let params_start_position = cursor + .expect(Punctuator::OpenParen, c.error_context())? + .span() + .end(); + + let params = FormalParameters::new(c.parameters_allow_yield(), c.parameters_allow_await()) + .parse(cursor)?; + + cursor.expect(Punctuator::CloseParen, c.error_context())?; + cursor.expect(Punctuator::OpenBlock, c.error_context())?; + + let body = FunctionBody::new(c.body_allow_yield(), c.body_allow_await()).parse(cursor)?; + + cursor.expect(Punctuator::CloseBlock, c.error_context())?; + + // Early Error: If the source code matching FormalParameters is strict mode code, + // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. + if (cursor.strict_mode() || body.strict()) && params.has_duplicates { + return Err(ParseError::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + params_start_position, + ))); + } + + // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true + // and IsSimpleParameterList of FormalParameters is false. + if body.strict() && !params.is_simple { + return Err(ParseError::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list".into(), + params_start_position, + ))); + } + + // It is a Syntax Error if any element of the BoundNames of FormalParameters + // also occurs in the LexicallyDeclaredNames of FunctionBody. + // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors + { + let lexically_declared_names = body.lexically_declared_names(); + for param in params.parameters.as_ref() { + if lexically_declared_names.contains(param.name()) { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of formal parameter `{}`", param.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + } + + Ok((name, params.parameters, body)) +} diff --git a/boa/src/syntax/parser/statement/declaration/mod.rs b/boa/src/syntax/parser/statement/declaration/mod.rs index 518748d2e7..e6abaac0e2 100644 --- a/boa/src/syntax/parser/statement/declaration/mod.rs +++ b/boa/src/syntax/parser/statement/declaration/mod.rs @@ -7,7 +7,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements#Declarations //! [spec]:https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement -mod hoistable; +pub(in crate::syntax::parser) mod hoistable; mod lexical; #[cfg(test)] mod tests; diff --git a/boa/src/syntax/parser/statement/expression/mod.rs b/boa/src/syntax/parser/statement/expression/mod.rs index 7140d33069..eb66384679 100644 --- a/boa/src/syntax/parser/statement/expression/mod.rs +++ b/boa/src/syntax/parser/statement/expression/mod.rs @@ -1,8 +1,9 @@ use super::super::{expression::Expression, ParseResult}; use crate::{ syntax::{ - ast::node::Node, - parser::{AllowAwait, AllowYield, Cursor, TokenParser}, + ast::{node::Node, Keyword, Punctuator}, + lexer::TokenKind, + parser::{AllowAwait, AllowYield, Cursor, ParseError, TokenParser}, }, BoaProfiler, }; @@ -42,7 +43,36 @@ where fn parse(self, cursor: &mut Cursor) -> ParseResult { let _timer = BoaProfiler::global().start_event("ExpressionStatement", "Parsing"); - // TODO: lookahead + + let next_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; + match next_token.kind() { + TokenKind::Keyword(Keyword::Function) | TokenKind::Keyword(Keyword::Class) => { + return Err(ParseError::general( + "expected statement", + next_token.span().start(), + )); + } + TokenKind::Keyword(Keyword::Async) => { + let next_token = cursor.peek(1)?.ok_or(ParseError::AbruptEnd)?; + if next_token.kind() == &TokenKind::Keyword(Keyword::Function) { + return Err(ParseError::general( + "expected statement", + next_token.span().start(), + )); + } + } + TokenKind::Keyword(Keyword::Let) => { + let next_token = cursor.peek(1)?.ok_or(ParseError::AbruptEnd)?; + if next_token.kind() == &TokenKind::Punctuator(Punctuator::OpenBracket) { + return Err(ParseError::general( + "expected statement", + next_token.span().start(), + )); + } + } + _ => {} + } + let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; cursor.expect_semicolon("expression statement")?; diff --git a/boa/src/syntax/parser/statement/if_stm/mod.rs b/boa/src/syntax/parser/statement/if_stm/mod.rs index 4ec7931dc0..326abff45b 100644 --- a/boa/src/syntax/parser/statement/if_stm/mod.rs +++ b/boa/src/syntax/parser/statement/if_stm/mod.rs @@ -1,20 +1,18 @@ #[cfg(test)] mod tests; -use super::Statement; - -use crate::syntax::lexer::TokenKind; use crate::{ syntax::{ ast::{node::If, Keyword, Node, Punctuator}, + lexer::TokenKind, parser::{ - expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, - TokenParser, + expression::Expression, + statement::{declaration::hoistable::FunctionDeclaration, Statement}, + AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, }, }, BoaProfiler, }; - use std::io::Read; /// If statement parsing. @@ -58,30 +56,67 @@ where fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("IfStatement", "Parsing"); + cursor.expect(Keyword::If, "if statement")?; cursor.expect(Punctuator::OpenParen, "if statement")?; - let cond = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + let condition = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; - cursor.expect(Punctuator::CloseParen, "if statement")?; + let position = cursor + .expect(Punctuator::CloseParen, "if statement")? + .span() + .end(); - let then_stm = - Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; + let then_node = if !cursor.strict_mode() + && cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() + == &TokenKind::Keyword(Keyword::Function) + { + // FunctionDeclarations in IfStatement Statement Clauses + // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses + FunctionDeclaration::new(self.allow_yield, self.allow_await, false) + .parse(cursor)? + .into() + } else { + let node = Statement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor)?; + + // Early Error: It is a Syntax Error if IsLabelledFunction(the first Statement) is true. + if let Node::FunctionDecl(_) = node { + return Err(ParseError::wrong_function_declaration_non_strict(position)); + } - let else_stm = if let Some(else_tok) = cursor.peek(0)? { - if else_tok.kind() == &TokenKind::Keyword(Keyword::Else) { - cursor.next()?.expect("else token vanished"); + node + }; + + let else_node = if cursor.next_if(Keyword::Else)?.is_some() { + let position = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.span().start(); + + if !cursor.strict_mode() + && cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() + == &TokenKind::Keyword(Keyword::Function) + { + // FunctionDeclarations in IfStatement Statement Clauses + // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses Some( - Statement::new(self.allow_yield, self.allow_await, self.allow_return) - .parse(cursor)?, + FunctionDeclaration::new(self.allow_yield, self.allow_await, false) + .parse(cursor)? + .into(), ) } else { - None + let node = Statement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor)?; + + // Early Error: It is a Syntax Error if IsLabelledFunction(the second Statement) is true. + if let Node::FunctionDecl(_) = node { + return Err(ParseError::wrong_function_declaration_non_strict(position)); + } + + Some(node) } } else { None }; - Ok(If::new::<_, _, Node, _>(cond, then_stm, else_stm)) + Ok(If::new::<_, _, Node, _>(condition, then_node, else_node)) } } diff --git a/boa/src/syntax/parser/statement/iteration/do_while_statement.rs b/boa/src/syntax/parser/statement/iteration/do_while_statement.rs index 5314d54856..6518841ff5 100644 --- a/boa/src/syntax/parser/statement/iteration/do_while_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/do_while_statement.rs @@ -7,10 +7,10 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while //! [spec]: https://tc39.es/ecma262/#sec-do-while-statement -use crate::syntax::lexer::TokenKind; use crate::{ syntax::{ - ast::{node::DoWhileLoop, Keyword, Punctuator}, + ast::{node::DoWhileLoop, Keyword, Node, Punctuator}, + lexer::TokenKind, parser::{ expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, @@ -63,11 +63,20 @@ where fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("DoWhileStatement", "Parsing"); - cursor.expect(Keyword::Do, "do while statement")?; + + let position = cursor + .expect(Keyword::Do, "do while statement")? + .span() + .end(); let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; + // Early Error: It is a Syntax Error if IsLabelledFunction(Statement) is true. + if let Node::FunctionDecl(_) = body { + return Err(ParseError::wrong_function_declaration_non_strict(position)); + } + let next_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; if next_token.kind() != &TokenKind::Keyword(Keyword::While) { @@ -86,7 +95,7 @@ where cursor.expect(Punctuator::CloseParen, "do while statement")?; - // Here, we only care to read the next token if it's a smicolon. If it's not, we + // Here, we only care to read the next token if it's a semicolon. If it's not, we // automatically "enter" or assume a semicolon, since we have just read the `)` token: // https://tc39.es/ecma262/#sec-automatic-semicolon-insertion if let Some(tok) = cursor.peek(0)? { diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index 71b0ea3c17..57b01621c5 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/for_statement.rs @@ -93,18 +93,40 @@ where let _ = cursor.next(); let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; - cursor.expect(Punctuator::CloseParen, "for in statement")?; + + let position = cursor + .expect(Punctuator::CloseParen, "for in statement")? + .span() + .end(); + let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return) .parse(cursor)?; + + // Early Error: It is a Syntax Error if IsLabelledFunction(the first Statement) is true. + if let Node::FunctionDecl(_) = body { + return Err(ParseError::wrong_function_declaration_non_strict(position)); + } + return Ok(ForInLoop::new(init.unwrap(), expr, body).into()); } Some(tok) if tok.kind() == &TokenKind::Keyword(Keyword::Of) && init.is_some() => { let _ = cursor.next(); let iterable = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; - cursor.expect(Punctuator::CloseParen, "for of statement")?; + + let position = cursor + .expect(Punctuator::CloseParen, "for of statement")? + .span() + .end(); + let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return) .parse(cursor)?; + + // Early Error: It is a Syntax Error if IsLabelledFunction(the first Statement) is true. + if let Node::FunctionDecl(_) = body { + return Err(ParseError::wrong_function_declaration_non_strict(position)); + } + return Ok(ForOfLoop::new(init.unwrap(), iterable, body).into()); } _ => {} @@ -131,9 +153,16 @@ where Some(step) }; + let position = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.span().start(); + let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; + // Early Error: It is a Syntax Error if IsLabelledFunction(the first Statement) is true. + if let Node::FunctionDecl(_) = body { + return Err(ParseError::wrong_function_declaration_non_strict(position)); + } + // TODO: do not encapsulate the `for` in a block just to have an inner scope. Ok(ForLoop::new(init, cond, step, body).into()) } diff --git a/boa/src/syntax/parser/statement/iteration/while_statement.rs b/boa/src/syntax/parser/statement/iteration/while_statement.rs index c8b59a9990..56ff0b4cd0 100644 --- a/boa/src/syntax/parser/statement/iteration/while_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/while_statement.rs @@ -1,6 +1,6 @@ use crate::{ syntax::{ - ast::{node::WhileLoop, Keyword, Punctuator}, + ast::{node::WhileLoop, Keyword, Node, Punctuator}, parser::{ expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, @@ -60,11 +60,19 @@ where let cond = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; - cursor.expect(Punctuator::CloseParen, "while statement")?; + let position = cursor + .expect(Punctuator::CloseParen, "while statement")? + .span() + .end(); let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; + // Early Error: It is a Syntax Error if IsLabelledFunction(Statement) is true. + if let Node::FunctionDecl(_) = body { + return Err(ParseError::wrong_function_declaration_non_strict(position)); + } + Ok(WhileLoop::new(cond, body)) } } diff --git a/boa/src/syntax/parser/statement/labelled_stm/mod.rs b/boa/src/syntax/parser/statement/labelled_stm/mod.rs index 0e970846c0..241d96f308 100644 --- a/boa/src/syntax/parser/statement/labelled_stm/mod.rs +++ b/boa/src/syntax/parser/statement/labelled_stm/mod.rs @@ -1,12 +1,17 @@ use std::io::Read; -use super::{LabelIdentifier, Statement}; use crate::{ - syntax::ast::Node, syntax::{ - ast::Punctuator, + ast::{Keyword, Node, Punctuator}, + lexer::TokenKind, parser::{ - cursor::Cursor, error::ParseError, AllowAwait, AllowReturn, AllowYield, TokenParser, + cursor::Cursor, + error::ParseError, + statement::{ + declaration::hoistable::FunctionDeclaration, AllowAwait, AllowReturn, + LabelIdentifier, Statement, + }, + AllowYield, TokenParser, }, }, BoaProfiler, @@ -49,18 +54,38 @@ where fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("Label", "Parsing"); + let name = LabelIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect(Punctuator::Colon, "Labelled Statement")?; - let mut stmt = - Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; - set_label_for_node(&mut stmt, name); - Ok(stmt) + let strict = cursor.strict_mode(); + let next_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; + let mut node = match next_token.kind() { + // Early Error: It is a Syntax Error if any strict mode source code matches this rule. + // https://tc39.es/ecma262/#sec-labelled-statements-static-semantics-early-errors + // https://tc39.es/ecma262/#sec-labelled-function-declarations + TokenKind::Keyword(Keyword::Function) if strict => { + return Err(ParseError::general( + "In strict mode code, functions can only be declared at top level or inside a block.", + next_token.span().start() + )) + } + TokenKind::Keyword(Keyword::Function) => { + FunctionDeclaration::new(self.allow_yield, self.allow_await, false) + .parse(cursor)? + .into() + } + _ => Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)? + }; + + set_label_for_node(&mut node, name); + Ok(node) } } -fn set_label_for_node(stmt: &mut Node, name: Box) { - match stmt { +fn set_label_for_node(node: &mut Node, name: Box) { + match node { Node::ForLoop(ref mut for_loop) => for_loop.set_label(name), Node::ForOfLoop(ref mut for_of_loop) => for_of_loop.set_label(name), Node::ForInLoop(ref mut for_in_loop) => for_in_loop.set_label(name), diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 6e42f34e86..1e9447c14a 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -520,24 +520,38 @@ where match next_token.kind() { TokenKind::Identifier(ref s) => Ok(s.clone()), - TokenKind::Keyword(k @ Keyword::Yield) if !self.allow_yield.0 => { + TokenKind::Keyword(Keyword::Yield) if self.allow_yield.0 => { + // Early Error: It is a Syntax Error if this production has a [Yield] parameter and StringValue of Identifier is "yield". + Err(ParseError::general( + "Unexpected identifier", + next_token.span().start(), + )) + } + TokenKind::Keyword(Keyword::Yield) if !self.allow_yield.0 => { if cursor.strict_mode() { - Err(ParseError::lex(LexError::Syntax( - "yield keyword in binding identifier not allowed in strict mode".into(), + Err(ParseError::general( + "yield keyword in binding identifier not allowed in strict mode", next_token.span().start(), - ))) + )) } else { - Ok(k.as_str().into()) + Ok("yield".into()) } } - TokenKind::Keyword(k @ Keyword::Await) if !self.allow_await.0 => { + TokenKind::Keyword(Keyword::Await) if self.allow_await.0 => { + // Early Error: It is a Syntax Error if this production has an [Await] parameter and StringValue of Identifier is "await". + Err(ParseError::general( + "Unexpected identifier", + next_token.span().start(), + )) + } + TokenKind::Keyword(Keyword::Await) if !self.allow_await.0 => { if cursor.strict_mode() { - Err(ParseError::lex(LexError::Syntax( - "await keyword in binding identifier not allowed in strict mode".into(), + Err(ParseError::general( + "await keyword in binding identifier not allowed in strict mode", next_token.span().start(), - ))) + )) } else { - Ok(k.as_str().into()) + Ok("await".into()) } } _ => Err(ParseError::expected(