Browse Source

Implement Generator parsing (#1575)

pull/1640/head
raskad 3 years ago committed by GitHub
parent
commit
45c8fa0baa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs
  2. 96
      boa/src/syntax/ast/node/declaration/generator_decl/mod.rs
  3. 109
      boa/src/syntax/ast/node/declaration/generator_expr/mod.rs
  4. 2
      boa/src/syntax/ast/node/declaration/mod.rs
  5. 33
      boa/src/syntax/ast/node/mod.rs
  6. 15
      boa/src/syntax/ast/node/object/mod.rs
  7. 70
      boa/src/syntax/ast/node/yield/mod.rs
  8. 8
      boa/src/syntax/parser/error.rs
  9. 44
      boa/src/syntax/parser/expression/assignment/arrow_function.rs
  10. 12
      boa/src/syntax/parser/expression/assignment/mod.rs
  11. 93
      boa/src/syntax/parser/expression/assignment/yield.rs
  12. 41
      boa/src/syntax/parser/expression/primary/async_function_expression/mod.rs
  13. 41
      boa/src/syntax/parser/expression/primary/function_expression/mod.rs
  14. 132
      boa/src/syntax/parser/expression/primary/generator_expression/mod.rs
  15. 57
      boa/src/syntax/parser/expression/primary/generator_expression/tests.rs
  16. 43
      boa/src/syntax/parser/expression/primary/mod.rs
  17. 440
      boa/src/syntax/parser/expression/primary/object_initializer/mod.rs
  18. 63
      boa/src/syntax/parser/function/mod.rs
  19. 23
      boa/src/syntax/parser/function/tests.rs
  20. 85
      boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs
  21. 75
      boa/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs
  22. 85
      boa/src/syntax/parser/statement/declaration/hoistable/generator_decl/mod.rs
  23. 9
      boa/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs
  24. 119
      boa/src/syntax/parser/statement/declaration/hoistable/mod.rs
  25. 2
      boa/src/syntax/parser/statement/declaration/mod.rs
  26. 36
      boa/src/syntax/parser/statement/expression/mod.rs
  27. 69
      boa/src/syntax/parser/statement/if_stm/mod.rs
  28. 17
      boa/src/syntax/parser/statement/iteration/do_while_statement.rs
  29. 33
      boa/src/syntax/parser/statement/iteration/for_statement.rs
  30. 12
      boa/src/syntax/parser/statement/iteration/while_statement.rs
  31. 45
      boa/src/syntax/parser/statement/labelled_stm/mod.rs
  32. 34
      boa/src/syntax/parser/statement/mod.rs

13
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))] #[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] #[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct AsyncFunctionDecl { pub struct AsyncFunctionDecl {
name: Option<Box<str>>, name: Box<str>,
parameters: Box<[FormalParameter]>, parameters: Box<[FormalParameter]>,
body: StatementList, body: StatementList,
} }
@ -31,7 +31,7 @@ impl AsyncFunctionDecl {
/// Creates a new async function declaration. /// Creates a new async function declaration.
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self
where where
N: Into<Option<Box<str>>>, N: Into<Box<str>>,
P: Into<Box<[FormalParameter]>>, P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>, B: Into<StatementList>,
{ {
@ -43,8 +43,8 @@ impl AsyncFunctionDecl {
} }
/// Gets the name of the async function declaration. /// Gets the name of the async function declaration.
pub fn name(&self) -> Option<&str> { pub fn name(&self) -> &str {
self.name.as_deref() &self.name
} }
/// Gets the list of parameters of the async function declaration. /// Gets the list of parameters of the async function declaration.
@ -63,10 +63,7 @@ impl AsyncFunctionDecl {
f: &mut fmt::Formatter<'_>, f: &mut fmt::Formatter<'_>,
indentation: usize, indentation: usize,
) -> fmt::Result { ) -> fmt::Result {
match &self.name { write!(f, "async function {}(", self.name())?;
Some(name) => write!(f, "async function {}(", name)?,
None => write!(f, "async function (")?,
}
join_nodes(f, &self.parameters)?; join_nodes(f, &self.parameters)?;
if self.body().is_empty() { if self.body().is_empty() {
f.write_str(") {}") f.write_str(") {}")

96
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<str>,
parameters: Box<[FormalParameter]>,
body: StatementList,
}
impl GeneratorDecl {
/// Creates a new generator declaration.
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self
where
N: Into<Box<str>>,
P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>,
{
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<JsValue> {
let _timer = BoaProfiler::global().start_event("GeneratorDecl", "exec");
// TODO: Implement GeneratorFunction
// https://tc39.es/ecma262/#sec-generatorfunction-objects
Ok(JsValue::undefined())
}
}
impl From<GeneratorDecl> 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)
}
}

109
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<Box<str>>,
parameters: Box<[FormalParameter]>,
body: StatementList,
}
impl GeneratorExpr {
/// Creates a new generator expression
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self
where
N: Into<Option<Box<str>>>,
P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>,
{
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<JsValue> {
// 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<GeneratorExpr> for Node {
fn from(expr: GeneratorExpr) -> Self {
Self::GeneratorExpr(expr)
}
}

2
boa/src/syntax/ast/node/declaration/mod.rs

@ -17,6 +17,8 @@ pub mod async_function_decl;
pub mod async_function_expr; pub mod async_function_expr;
pub mod function_decl; pub mod function_decl;
pub mod function_expr; pub mod function_expr;
pub mod generator_decl;
pub mod generator_expr;
pub use self::{ pub use self::{
arrow_function_decl::ArrowFunctionDecl, async_function_decl::AsyncFunctionDecl, arrow_function_decl::ArrowFunctionDecl, async_function_decl::AsyncFunctionDecl,

33
boa/src/syntax/ast/node/mod.rs

@ -20,6 +20,7 @@ pub mod switch;
pub mod template; pub mod template;
pub mod throw; pub mod throw;
pub mod try_node; pub mod try_node;
pub mod r#yield;
pub use self::{ pub use self::{
array::ArrayDecl, array::ArrayDecl,
@ -29,8 +30,9 @@ pub use self::{
call::Call, call::Call,
conditional::{ConditionalOp, If}, conditional::{ConditionalOp, If},
declaration::{ declaration::{
ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, generator_decl::GeneratorDecl, generator_expr::GeneratorExpr, ArrowFunctionDecl,
FunctionDecl, FunctionExpr, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, FunctionDecl,
FunctionExpr,
}, },
field::{GetConstField, GetField}, field::{GetConstField, GetField},
identifier::Identifier, identifier::Identifier,
@ -38,6 +40,7 @@ pub use self::{
new::New, new::New,
object::Object, object::Object,
operator::{Assign, BinOp, UnaryOp}, operator::{Assign, BinOp, UnaryOp},
r#yield::Yield,
return_smt::Return, return_smt::Return,
spread::Spread, spread::Spread,
statement_list::{RcStatementList, StatementList}, statement_list::{RcStatementList, StatementList},
@ -209,6 +212,15 @@ pub enum Node {
/// [spec]: https://tc39.es/ecma262/#prod-EmptyStatement /// [spec]: https://tc39.es/ecma262/#prod-EmptyStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/Empty /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/Empty
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 { impl Display for Node {
@ -302,6 +314,9 @@ impl Node {
Self::AsyncFunctionExpr(ref expr) => expr.display(f, indentation), Self::AsyncFunctionExpr(ref expr) => expr.display(f, indentation),
Self::AwaitExpr(ref expr) => Display::fmt(expr, f), Self::AwaitExpr(ref expr) => Display::fmt(expr, f),
Self::Empty => write!(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::Break(ref break_node) => break_node.run(context),
Node::Continue(ref continue_node) => continue_node.run(context), Node::Continue(ref continue_node) => continue_node.run(context),
Node::Empty => Ok(JsValue::undefined()), 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 /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Method_definition_syntax /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Method_definition_syntax
Ordinary, 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 { unsafe impl Trace for MethodDefinitionKind {

15
boa/src/syntax/ast/node/object/mod.rs

@ -72,7 +72,7 @@ impl Object {
match &kind { match &kind {
MethodDefinitionKind::Get => write!(f, "get ")?, MethodDefinitionKind::Get => write!(f, "get ")?,
MethodDefinitionKind::Set => write!(f, "set ")?, MethodDefinitionKind::Set => write!(f, "set ")?,
MethodDefinitionKind::Ordinary => (), MethodDefinitionKind::Ordinary | MethodDefinitionKind::Generator => (),
} }
write!(f, "{}(", key)?; write!(f, "{}(", key)?;
join_nodes(f, node.parameters())?; join_nodes(f, node.parameters())?;
@ -166,6 +166,19 @@ impl Executable for Object {
context, 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 // [spec]: https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation

70
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<Box<Node>>,
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<E, OE>(expr: OE, delegate: bool) -> Self
where
E: Into<Node>,
OE: Into<Option<E>>,
{
Self {
expr: expr.into().map(E::into).map(Box::new),
delegate,
}
}
}
impl Executable for Yield {
fn run(&self, _context: &mut Context) -> JsResult<JsValue> {
// TODO: Implement Generator execution
Ok(JsValue::undefined())
}
}
impl From<Yield> 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),
}
}
}

8
boa/src/syntax/parser/error.rs

@ -92,6 +92,14 @@ impl ParseError {
Self::General { message, position } 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. /// Creates a parsing error from a lexing error.
pub(super) fn lex(e: LexError) -> Self { pub(super) fn lex(e: LexError) -> Self {
Self::Lex { err: e } Self::Lex { err: e }

44
boa/src/syntax/parser/expression/assignment/arrow_function.rs

@ -17,7 +17,7 @@ use crate::{
lexer::{Error as LexError, Position, TokenKind}, lexer::{Error as LexError, Position, TokenKind},
parser::{ parser::{
error::{ErrorContext, ParseError, ParseResult}, error::{ErrorContext, ParseError, ParseResult},
function::{FormalParameters, FunctionBody}, function::{FormalParameterList, FormalParameters, FunctionBody},
statement::BindingIdentifier, statement::BindingIdentifier,
AllowAwait, AllowIn, AllowYield, Cursor, TokenParser, AllowAwait, AllowIn, AllowYield, Cursor, TokenParser,
}, },
@ -72,18 +72,31 @@ where
let _timer = BoaProfiler::global().start_event("ArrowFunction", "Parsing"); let _timer = BoaProfiler::global().start_event("ArrowFunction", "Parsing");
let next_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; 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 // 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)?; let params = FormalParameters::new(self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect(Punctuator::CloseParen, "arrow function")?; cursor.expect(Punctuator::CloseParen, "arrow function")?;
params (params, params_start_position)
} else { } else {
let params_start_position = next_token.span().start();
let param = BindingIdentifier::new(self.allow_yield, self.allow_await) let param = BindingIdentifier::new(self.allow_yield, self.allow_await)
.parse(cursor) .parse(cursor)
.context("arrow function")?; .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")?; cursor.peek_expect_no_lineterminator(0, "arrow function")?;
@ -91,12 +104,29 @@ where
cursor.expect(TokenKind::Punctuator(Punctuator::Arrow), "arrow function")?; cursor.expect(TokenKind::Punctuator(Punctuator::Arrow), "arrow function")?;
let body = ConciseBody::new(self.allow_in).parse(cursor)?; 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 // It is a Syntax Error if any element of the BoundNames of ArrowParameters
// also occurs in the LexicallyDeclaredNames of ConciseBody. // also occurs in the LexicallyDeclaredNames of ConciseBody.
// https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors // https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors
{ {
let lexically_declared_names = body.lexically_declared_names(); 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()) { if lexically_declared_names.contains(param.name()) {
return Err(ParseError::lex(LexError::Syntax( return Err(ParseError::lex(LexError::Syntax(
format!("Redeclaration of formal parameter `{}`", param.name()).into(), format!("Redeclaration of formal parameter `{}`", param.name()).into(),
@ -109,7 +139,7 @@ where
} }
} }
Ok(ArrowFunctionDecl::new(params, body)) Ok(ArrowFunctionDecl::new(params.parameters, body))
} }
} }

12
boa/src/syntax/parser/expression/assignment/mod.rs

@ -10,7 +10,9 @@
mod arrow_function; mod arrow_function;
mod conditional; mod conditional;
mod exponentiation; mod exponentiation;
mod r#yield;
use self::r#yield::YieldExpression;
use self::{arrow_function::ArrowFunction, conditional::ConditionalExpression}; use self::{arrow_function::ArrowFunction, conditional::ConditionalExpression};
use crate::syntax::lexer::{Error as LexError, InputElement, TokenKind}; use crate::syntax::lexer::{Error as LexError, InputElement, TokenKind};
use crate::{ use crate::{
@ -82,9 +84,12 @@ where
let _timer = BoaProfiler::global().start_event("AssignmentExpression", "Parsing"); let _timer = BoaProfiler::global().start_event("AssignmentExpression", "Parsing");
cursor.set_goal(InputElement::Div); cursor.set_goal(InputElement::Div);
// Arrow function
match cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() { 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::Identifier(_)
| TokenKind::Keyword(Keyword::Yield) | TokenKind::Keyword(Keyword::Yield)
| TokenKind::Keyword(Keyword::Await) => { | TokenKind::Keyword(Keyword::Await) => {
@ -100,8 +105,7 @@ where
} }
} }
} }
// ArrowFunction[?In, ?Yield, ?Await] -> ArrowParameters[?Yield, ?Await] -> CoverParenthesizedExpressionAndArrowParameterList[?Yield, ?Await]
// (a,b)=>{} or (a,b) or (Expression)
TokenKind::Punctuator(Punctuator::OpenParen) => { TokenKind::Punctuator(Punctuator::OpenParen) => {
if let Some(next_token) = cursor.peek(1)? { if let Some(next_token) = cursor.peek(1)? {
match *next_token.kind() { match *next_token.kind() {

93
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<I, A>(allow_in: I, allow_await: A) -> Self
where
I: Into<AllowIn>,
A: Into<AllowAwait>,
{
Self {
allow_in: allow_in.into(),
allow_await: allow_await.into(),
}
}
}
impl<R> TokenParser<R> for YieldExpression
where
R: Read,
{
type Output = Node;
fn parse(self, cursor: &mut Cursor<R>) -> 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::<Node, Option<Node>>(
expr, delegate,
)))
}
}

41
boa/src/syntax/parser/expression/primary/async_function_expression/mod.rs

@ -63,7 +63,24 @@ where
return Err(ParseError::AbruptEnd); 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)?; let params = FormalParameters::new(false, true).parse(cursor)?;
@ -74,12 +91,30 @@ where
cursor.expect(Punctuator::CloseBlock, "async function expression")?; 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 // It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of FunctionBody. // also occurs in the LexicallyDeclaredNames of FunctionBody.
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
{ {
let lexically_declared_names = body.lexically_declared_names(); 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()) { if lexically_declared_names.contains(param.name()) {
return Err(ParseError::lex(LexError::Syntax( return Err(ParseError::lex(LexError::Syntax(
format!("Redeclaration of formal parameter `{}`", param.name()).into(), 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))
} }
} }

41
boa/src/syntax/parser/expression/primary/function_expression/mod.rs

@ -58,7 +58,24 @@ where
None 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)?; let params = FormalParameters::new(false, false).parse(cursor)?;
@ -69,12 +86,30 @@ where
cursor.expect(Punctuator::CloseBlock, "function expression")?; 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 // It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of FunctionBody. // also occurs in the LexicallyDeclaredNames of FunctionBody.
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
{ {
let lexically_declared_names = body.lexically_declared_names(); 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()) { if lexically_declared_names.contains(param.name()) {
return Err(ParseError::lex(LexError::Syntax( return Err(ParseError::lex(LexError::Syntax(
format!("Redeclaration of formal parameter `{}`", param.name()).into(), 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))
} }
} }

132
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<R> TokenParser<R> for GeneratorExpression
where
R: Read,
{
type Output = GeneratorExpr;
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
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))
}
}

57
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::<Option<Box<str>>, _, 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::<Option<Box<str>>, _, StatementList>(
None,
[],
vec![Yield::new(Const::from(1), true).into()].into(),
)
.into(),
),
)]
.into(),
)
.into()],
);
}

43
boa/src/syntax/parser/expression/primary/mod.rs

@ -10,6 +10,7 @@
mod array_initializer; mod array_initializer;
mod async_function_expression; mod async_function_expression;
mod function_expression; mod function_expression;
mod generator_expression;
mod object_initializer; mod object_initializer;
mod template; mod template;
#[cfg(test)] #[cfg(test)]
@ -17,7 +18,8 @@ mod tests;
use self::{ use self::{
array_initializer::ArrayLiteral, async_function_expression::AsyncFunctionExpression, 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 super::Expression;
use crate::{ use crate::{
@ -80,8 +82,13 @@ where
match tok.kind() { match tok.kind() {
TokenKind::Keyword(Keyword::This) => Ok(Node::This), TokenKind::Keyword(Keyword::This) => Ok(Node::This),
TokenKind::Keyword(Keyword::Function) => { TokenKind::Keyword(Keyword::Function) => {
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) FunctionExpression.parse(cursor).map(Node::from)
} }
}
TokenKind::Keyword(Keyword::Async) => AsyncFunctionExpression::new(self.allow_yield) TokenKind::Keyword(Keyword::Async) => AsyncFunctionExpression::new(self.allow_yield)
.parse(cursor) .parse(cursor)
.map(Node::from), .map(Node::from),
@ -106,7 +113,39 @@ where
} }
TokenKind::BooleanLiteral(boolean) => Ok(Const::from(*boolean).into()), TokenKind::BooleanLiteral(boolean) => Ok(Const::from(*boolean).into()),
TokenKind::NullLiteral => Ok(Const::Null.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::StringLiteral(s) => Ok(Const::from(s.as_ref()).into()),
TokenKind::TemplateNoSubstitution(template_string) => { TokenKind::TemplateNoSubstitution(template_string) => {
Ok(Const::from(template_string.to_owned_cooked().map_err(ParseError::lex)?).into()) Ok(Const::from(template_string.to_owned_cooked().map_err(ParseError::lex)?).into())

440
boa/src/syntax/parser/expression/primary/object_initializer/mod.rs

@ -9,14 +9,13 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use crate::syntax::ast::node::{Identifier, PropertyName};
use crate::syntax::lexer::TokenKind;
use crate::{ use crate::{
syntax::{ syntax::{
ast::{ ast::{
node::{self, FunctionExpr, MethodDefinitionKind, Node, Object}, node::{self, FunctionExpr, Identifier, MethodDefinitionKind, Node, Object},
Punctuator, Keyword, Punctuator,
}, },
lexer::{Error as LexError, Position, TokenKind},
parser::{ parser::{
expression::AssignmentExpression, expression::AssignmentExpression,
function::{FormalParameters, FunctionBody}, function::{FormalParameters, FunctionBody},
@ -129,226 +128,331 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> { fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
let _timer = BoaProfiler::global().start_event("PropertyDefinition", "Parsing"); let _timer = BoaProfiler::global().start_event("PropertyDefinition", "Parsing");
if cursor.next_if(Punctuator::Spread)?.is_some() { // IdentifierReference[?Yield, ?Await]
let node = AssignmentExpression::new(true, self.allow_yield, self.allow_await) if let Some(next_token) = cursor.peek(1)? {
.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() { match next_token.kind() {
TokenKind::Punctuator(Punctuator::Colon) => { TokenKind::Punctuator(Punctuator::CloseBlock)
let val = AssignmentExpression::new(false, self.allow_yield, self.allow_await) | TokenKind::Punctuator(Punctuator::Comma) => {
.parse(cursor)?; let token = cursor.next()?.ok_or(ParseError::AbruptEnd)?;
return Ok(node::PropertyDefinition::property(node, val)); 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(),
));
} }
TokenKind::Punctuator(Punctuator::OpenParen) => { Identifier::from("yield")
return MethodDefinition::new(self.allow_yield, self.allow_await, node)
.parse(cursor);
} }
_ => { _ => {
return Err(ParseError::unexpected( return Err(ParseError::unexpected(
next_token, token.clone(),
"expected AssignmentExpression or MethodDefinition", "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));
} }
// Peek for '}' or ',' to indicate shorthand property name // MethodDefinition[?Yield, ?Await] -> GeneratorMethod[?Yield, ?Await]
if let Some(next_token) = cursor.peek(1)? { if cursor.next_if(Punctuator::Mul)?.is_some() {
match next_token.kind() { let property_name =
TokenKind::Punctuator(Punctuator::CloseBlock) PropertyName::new(self.allow_yield, self.allow_await).parse(cursor)?;
| TokenKind::Punctuator(Punctuator::Comma) => {
let token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?; let params_start_position = cursor
if let TokenKind::Identifier(ident) = token.kind() { .expect(Punctuator::OpenParen, "generator method definition")?
// ident is both the name and value in a shorthand property .span()
let name = ident.to_string(); .start();
let value = Identifier::from(ident.to_owned()); let params = FormalParameters::new(false, false).parse(cursor)?;
cursor.next()?.expect("token vanished"); // Consume the token. cursor.expect(Punctuator::CloseParen, "generator method definition")?;
return Ok(node::PropertyDefinition::property(name, value));
} else { // Early Error: UniqueFormalParameters : FormalParameters
// Anything besides an identifier is a syntax error if params.has_duplicates {
return Err(ParseError::unexpected(token.clone(), "object literal")); return Err(ParseError::lex(LexError::Syntax(
"Duplicate parameter name not allowed in this context".into(),
params_start_position,
)));
}
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() { 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)?; .parse(cursor)?;
return Ok(node::PropertyDefinition::property(prop_name, val)); return Ok(node::PropertyDefinition::property(property_name, value));
} }
// TODO GeneratorMethod let ordinary_method = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind()
// https://tc39.es/ecma262/#prod-GeneratorMethod == &TokenKind::Punctuator(Punctuator::OpenParen);
if prop_name.as_str() == "async" { match property_name {
// TODO - AsyncMethod. // MethodDefinition[?Yield, ?Await] -> get ClassElementName[?Yield, ?Await] ( ) { FunctionBody[~Yield, ~Await] }
// https://tc39.es/ecma262/#prod-AsyncMethod 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 cursor.expect(
// https://tc39.es/ecma262/#prod-AsyncGeneratorMethod TokenKind::Punctuator(Punctuator::OpenParen),
"get method definition",
)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseParen),
"get method definition",
)?;
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)?;
if cursor let params_start_position = cursor
.next_if(TokenKind::Punctuator(Punctuator::OpenParen))? .expect(
.is_some() TokenKind::Punctuator(Punctuator::OpenParen),
|| ["get", "set"].contains(&prop_name.as_str()) "set method definition",
{ )?
return MethodDefinition::new(self.allow_yield, self.allow_await, prop_name) .span()
.parse(cursor); .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,
));
}
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,
)));
} }
let pos = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.span().start(); Ok(node::PropertyDefinition::method_definition(
Err(ParseError::general("expected property definition", pos)) MethodDefinitionKind::Ordinary,
property_name,
FunctionExpr::new(None, params.parameters, body),
))
}
}
} }
} }
/// Parses a method definition. /// Parses a property name.
/// ///
/// More information: /// More information:
/// - [ECMAScript specification][spec] /// - [ECMAScript specification][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition /// [spec]: https://tc39.es/ecma262/#prod-PropertyName
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct MethodDefinition { struct PropertyName {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
identifier: PropertyName,
} }
impl MethodDefinition { impl PropertyName {
/// Creates a new `MethodDefinition` parser. /// Creates a new `PropertyName` parser.
fn new<Y, A, I>(allow_yield: Y, allow_await: A, identifier: I) -> Self fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
A: Into<AllowAwait>, A: Into<AllowAwait>,
I: Into<PropertyName>,
{ {
Self { Self {
allow_yield: allow_yield.into(), allow_yield: allow_yield.into(),
allow_await: allow_await.into(), allow_await: allow_await.into(),
identifier: identifier.into(),
} }
} }
} }
impl<R> TokenParser<R> for MethodDefinition impl<R> TokenParser<R> for PropertyName
where where
R: Read, R: Read,
{ {
type Output = node::PropertyDefinition; type Output = node::PropertyName;
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> { fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
let _timer = BoaProfiler::global().start_event("MethodDefinition", "Parsing"); let _timer = BoaProfiler::global().start_event("PropertyName", "Parsing");
let (method_kind, prop_name, params) = match self.identifier { // ComputedPropertyName[?Yield, ?Await] -> [ AssignmentExpression[+In, ?Yield, ?Await] ]
PropertyName::Literal(ident) if cursor.next_if(Punctuator::OpenBracket)?.is_some() {
if ["get", "set"].contains(&ident.as_ref()) let node = AssignmentExpression::new(false, self.allow_yield, self.allow_await)
&& 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)?; .parse(cursor)?;
cursor.expect(Punctuator::CloseBracket, "expected token ']'")?; cursor.expect(Punctuator::CloseBracket, "expected token ']'")?;
cursor.expect( return Ok(node.into());
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( // LiteralPropertyName
TokenKind::Punctuator(Punctuator::OpenBlock), Ok(cursor
"property method definition", .next()?
)?; .ok_or(ParseError::AbruptEnd)?
let body = FunctionBody::new(false, false).parse(cursor)?; .to_string()
cursor.expect( .into())
TokenKind::Punctuator(Punctuator::CloseBlock),
"property method definition",
)?;
Ok(node::PropertyDefinition::method_definition(
method_kind,
prop_name,
FunctionExpr::new(None, params, body),
))
} }
} }

63
boa/src/syntax/parser/function/mod.rs

@ -13,7 +13,7 @@ mod tests;
use crate::{ use crate::{
syntax::{ syntax::{
ast::{node, Punctuator}, ast::{node, Punctuator},
lexer::{InputElement, TokenKind}, lexer::{Error as LexError, InputElement, TokenKind},
parser::{ parser::{
expression::Initializer, expression::Initializer,
statement::{BindingIdentifier, StatementList}, statement::{BindingIdentifier, StatementList},
@ -22,7 +22,15 @@ use crate::{
}, },
BoaProfiler, 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. /// Formal parameters parsing.
/// ///
@ -56,25 +64,31 @@ impl<R> TokenParser<R> for FormalParameters
where where
R: Read, R: Read,
{ {
type Output = Box<[node::FormalParameter]>; type Output = FormalParameterList;
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> { fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
let _timer = BoaProfiler::global().start_event("FormalParameters", "Parsing"); let _timer = BoaProfiler::global().start_event("FormalParameters", "Parsing");
cursor.set_goal(InputElement::RegExp); cursor.set_goal(InputElement::RegExp);
let mut params = Vec::new(); let mut params = Vec::new();
let mut param_names = HashSet::new(); let mut is_simple = true;
let mut has_duplicates = false;
if cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind()
== &TokenKind::Punctuator(Punctuator::CloseParen) let next_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?;
{ if next_token.kind() == &TokenKind::Punctuator(Punctuator::CloseParen) {
return Ok(params.into_boxed_slice()); 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 { loop {
let mut rest_param = false; let mut rest_param = false;
let position = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.span().start();
let next_param = match cursor.peek(0)? { let next_param = match cursor.peek(0)? {
Some(tok) if tok.kind() == &TokenKind::Punctuator(Punctuator::Spread) => { Some(tok) if tok.kind() == &TokenKind::Punctuator(Punctuator::Spread) => {
rest_param = true; rest_param = true;
@ -82,11 +96,15 @@ where
} }
_ => FormalParameter::new(self.allow_yield, self.allow_await).parse(cursor)?, _ => 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); params.push(next_param);
if cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() if cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind()
@ -105,7 +123,20 @@ where
cursor.expect(Punctuator::Comma, "parameter list")?; 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 /// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Parameter
/// [spec]: https://tc39.es/ecma262/#prod-FormalParameter /// [spec]: https://tc39.es/ecma262/#prod-FormalParameter
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct FormalParameter { pub(in crate::syntax::parser) struct FormalParameter {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
} }
impl FormalParameter { impl FormalParameter {
/// Creates a new `FormalParameter` parser. /// Creates a new `FormalParameter` parser.
fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self pub(in crate::syntax::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
A: Into<AllowAwait>, A: Into<AllowAwait>,

23
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] #[test]
fn check_duplicates() { fn check_duplicates_strict_off() {
let js = "function foo(a, a) {}"; 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(); let res = Parser::new(js.as_bytes(), false).parse_all();
dbg!(&res); dbg!(&res);

85
boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs

@ -2,12 +2,9 @@
mod tests; mod tests;
use crate::syntax::{ use crate::syntax::{
ast::{node::AsyncFunctionDecl, Keyword, Punctuator}, ast::{node::AsyncFunctionDecl, Keyword},
lexer::TokenKind,
parser::{ parser::{
function::FormalParameters, statement::declaration::hoistable::{parse_callable_declaration, CallableDeclaration},
function::FunctionBody,
statement::{BindingIdentifier, LexError, Position},
AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, 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<R> TokenParser<R> for AsyncFunctionDeclaration impl<R> TokenParser<R> for AsyncFunctionDeclaration
where where
R: Read, R: Read,
@ -54,56 +78,9 @@ where
cursor.expect(Keyword::Async, "async function declaration")?; cursor.expect(Keyword::Async, "async function declaration")?;
cursor.peek_expect_no_lineterminator(0, "async function declaration")?; cursor.peek_expect_no_lineterminator(0, "async function declaration")?;
cursor.expect(Keyword::Function, "async function declaration")?; cursor.expect(Keyword::Function, "async function declaration")?;
let tok = cursor.peek(0)?;
let name = if let Some(token) = tok { let result = parse_callable_declaration(&self, cursor)?;
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),
},
)));
}
}
}
Ok(AsyncFunctionDecl::new(name, params, body)) Ok(AsyncFunctionDecl::new(result.0, result.1, result.2))
} }
} }

75
boa/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs

@ -2,11 +2,9 @@
mod tests; mod tests;
use crate::syntax::{ use crate::syntax::{
ast::{node::FunctionDecl, Keyword, Punctuator}, ast::{node::FunctionDecl, Keyword},
parser::{ parser::{
function::FormalParameters, statement::declaration::hoistable::{parse_callable_declaration, CallableDeclaration},
function::FunctionBody,
statement::{BindingIdentifier, LexError, Position},
AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser,
}, },
}; };
@ -22,7 +20,7 @@ use std::io::Read;
/// [spec]: https://tc39.es/ecma262/#prod-FunctionDeclaration /// [spec]: https://tc39.es/ecma262/#prod-FunctionDeclaration
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(super) struct FunctionDeclaration { pub(in crate::syntax::parser) struct FunctionDeclaration {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
is_default: AllowDefault, is_default: AllowDefault,
@ -30,7 +28,11 @@ pub(super) struct FunctionDeclaration {
impl FunctionDeclaration { impl FunctionDeclaration {
/// Creates a new `FunctionDeclaration` parser. /// Creates a new `FunctionDeclaration` parser.
pub(super) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self pub(in crate::syntax::parser) fn new<Y, A, D>(
allow_yield: Y,
allow_await: A,
is_default: D,
) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
A: Into<AllowAwait>, A: Into<AllowAwait>,
@ -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<R> TokenParser<R> for FunctionDeclaration impl<R> TokenParser<R> for FunctionDeclaration
where where
R: Read, R: Read,
@ -53,38 +82,8 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> { fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
cursor.expect(Keyword::Function, "function declaration")?; cursor.expect(Keyword::Function, "function declaration")?;
// TODO: If self.is_default, then this can be empty. let result = parse_callable_declaration(&self, cursor)?;
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),
},
)));
}
}
}
Ok(FunctionDecl::new(name, params, body)) Ok(FunctionDecl::new(result.0, result.1, result.2))
} }
} }

85
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<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
D: Into<AllowDefault>,
{
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<R> TokenParser<R> for GeneratorDeclaration
where
R: Read,
{
type Output = GeneratorDecl;
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
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))
}
}

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

119
boa/src/syntax/parser/statement/declaration/hoistable/mod.rs

@ -10,15 +10,20 @@ mod tests;
mod async_function_decl; mod async_function_decl;
mod function_decl; mod function_decl;
mod generator_decl;
use async_function_decl::AsyncFunctionDeclaration; use async_function_decl::AsyncFunctionDeclaration;
use function_decl::FunctionDeclaration; pub(in crate::syntax::parser) use function_decl::FunctionDeclaration;
use generator_decl::GeneratorDeclaration;
use crate::{ use crate::{
syntax::{ syntax::{
ast::{Keyword, Node}, ast::node::{FormalParameter, StatementList},
lexer::TokenKind, ast::{Keyword, Node, Punctuator},
lexer::{Position, TokenKind},
parser::{ parser::{
function::{FormalParameters, FunctionBody},
statement::{BindingIdentifier, LexError},
AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, ParseResult, TokenParser, AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, ParseResult, TokenParser,
}, },
}, },
@ -67,10 +72,17 @@ where
match tok.kind() { match tok.kind() {
TokenKind::Keyword(Keyword::Function) => { TokenKind::Keyword(Keyword::Function) => {
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) FunctionDeclaration::new(self.allow_yield, self.allow_await, self.is_default)
.parse(cursor) .parse(cursor)
.map(Node::from) .map(Node::from)
} }
}
TokenKind::Keyword(Keyword::Async) => { TokenKind::Keyword(Keyword::Async) => {
AsyncFunctionDeclaration::new(self.allow_yield, self.allow_await, false) AsyncFunctionDeclaration::new(self.allow_yield, self.allow_await, false)
.parse(cursor) .parse(cursor)
@ -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<R: Read, C: CallableDeclaration>(
c: &C,
cursor: &mut Cursor<R>,
) -> Result<(Box<str>, 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))
}

2
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 //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements#Declarations
//! [spec]:https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement //! [spec]:https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
mod hoistable; pub(in crate::syntax::parser) mod hoistable;
mod lexical; mod lexical;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

36
boa/src/syntax/parser/statement/expression/mod.rs

@ -1,8 +1,9 @@
use super::super::{expression::Expression, ParseResult}; use super::super::{expression::Expression, ParseResult};
use crate::{ use crate::{
syntax::{ syntax::{
ast::node::Node, ast::{node::Node, Keyword, Punctuator},
parser::{AllowAwait, AllowYield, Cursor, TokenParser}, lexer::TokenKind,
parser::{AllowAwait, AllowYield, Cursor, ParseError, TokenParser},
}, },
BoaProfiler, BoaProfiler,
}; };
@ -42,7 +43,36 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> ParseResult { fn parse(self, cursor: &mut Cursor<R>) -> ParseResult {
let _timer = BoaProfiler::global().start_event("ExpressionStatement", "Parsing"); 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)?; let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect_semicolon("expression statement")?; cursor.expect_semicolon("expression statement")?;

69
boa/src/syntax/parser/statement/if_stm/mod.rs

@ -1,20 +1,18 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use super::Statement;
use crate::syntax::lexer::TokenKind;
use crate::{ use crate::{
syntax::{ syntax::{
ast::{node::If, Keyword, Node, Punctuator}, ast::{node::If, Keyword, Node, Punctuator},
lexer::TokenKind,
parser::{ parser::{
expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, expression::Expression,
TokenParser, statement::{declaration::hoistable::FunctionDeclaration, Statement},
AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser,
}, },
}, },
BoaProfiler, BoaProfiler,
}; };
use std::io::Read; use std::io::Read;
/// If statement parsing. /// If statement parsing.
@ -58,30 +56,67 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> { fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
let _timer = BoaProfiler::global().start_event("IfStatement", "Parsing"); let _timer = BoaProfiler::global().start_event("IfStatement", "Parsing");
cursor.expect(Keyword::If, "if statement")?; cursor.expect(Keyword::If, "if statement")?;
cursor.expect(Punctuator::OpenParen, "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)?;
let position = cursor
.expect(Punctuator::CloseParen, "if statement")?
.span()
.end();
cursor.expect(Punctuator::CloseParen, "if statement")?; 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)?;
let then_stm = // Early Error: It is a Syntax Error if IsLabelledFunction(the first Statement) is true.
Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; 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)? { node
if else_tok.kind() == &TokenKind::Keyword(Keyword::Else) { };
cursor.next()?.expect("else token vanished");
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( Some(
Statement::new(self.allow_yield, self.allow_await, self.allow_return) FunctionDeclaration::new(self.allow_yield, self.allow_await, false)
.parse(cursor)?, .parse(cursor)?
.into(),
) )
} else { } 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 { } else {
None None
}; };
Ok(If::new::<_, _, Node, _>(cond, then_stm, else_stm)) Ok(If::new::<_, _, Node, _>(condition, then_node, else_node))
} }
} }

17
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 //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while
//! [spec]: https://tc39.es/ecma262/#sec-do-while-statement //! [spec]: https://tc39.es/ecma262/#sec-do-while-statement
use crate::syntax::lexer::TokenKind;
use crate::{ use crate::{
syntax::{ syntax::{
ast::{node::DoWhileLoop, Keyword, Punctuator}, ast::{node::DoWhileLoop, Keyword, Node, Punctuator},
lexer::TokenKind,
parser::{ parser::{
expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield,
Cursor, ParseError, TokenParser, Cursor, ParseError, TokenParser,
@ -63,11 +63,20 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> { fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
let _timer = BoaProfiler::global().start_event("DoWhileStatement", "Parsing"); 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 = let body =
Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; 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)?; let next_token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?;
if next_token.kind() != &TokenKind::Keyword(Keyword::While) { if next_token.kind() != &TokenKind::Keyword(Keyword::While) {
@ -86,7 +95,7 @@ where
cursor.expect(Punctuator::CloseParen, "do while statement")?; 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: // automatically "enter" or assume a semicolon, since we have just read the `)` token:
// https://tc39.es/ecma262/#sec-automatic-semicolon-insertion // https://tc39.es/ecma262/#sec-automatic-semicolon-insertion
if let Some(tok) = cursor.peek(0)? { if let Some(tok) = cursor.peek(0)? {

33
boa/src/syntax/parser/statement/iteration/for_statement.rs

@ -93,18 +93,40 @@ where
let _ = cursor.next(); let _ = cursor.next();
let expr = let expr =
Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; 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) let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return)
.parse(cursor)?; .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()); return Ok(ForInLoop::new(init.unwrap(), expr, body).into());
} }
Some(tok) if tok.kind() == &TokenKind::Keyword(Keyword::Of) && init.is_some() => { Some(tok) if tok.kind() == &TokenKind::Keyword(Keyword::Of) && init.is_some() => {
let _ = cursor.next(); let _ = cursor.next();
let iterable = let iterable =
Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; 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) let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return)
.parse(cursor)?; .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()); return Ok(ForOfLoop::new(init.unwrap(), iterable, body).into());
} }
_ => {} _ => {}
@ -131,9 +153,16 @@ where
Some(step) Some(step)
}; };
let position = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.span().start();
let body = let body =
Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; 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. // TODO: do not encapsulate the `for` in a block just to have an inner scope.
Ok(ForLoop::new(init, cond, step, body).into()) Ok(ForLoop::new(init, cond, step, body).into())
} }

12
boa/src/syntax/parser/statement/iteration/while_statement.rs

@ -1,6 +1,6 @@
use crate::{ use crate::{
syntax::{ syntax::{
ast::{node::WhileLoop, Keyword, Punctuator}, ast::{node::WhileLoop, Keyword, Node, Punctuator},
parser::{ parser::{
expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield,
Cursor, ParseError, TokenParser, Cursor, ParseError, TokenParser,
@ -60,11 +60,19 @@ where
let cond = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; 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 = let body =
Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; 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)) Ok(WhileLoop::new(cond, body))
} }
} }

45
boa/src/syntax/parser/statement/labelled_stm/mod.rs

@ -1,12 +1,17 @@
use std::io::Read; use std::io::Read;
use super::{LabelIdentifier, Statement};
use crate::{ use crate::{
syntax::ast::Node,
syntax::{ syntax::{
ast::Punctuator, ast::{Keyword, Node, Punctuator},
lexer::TokenKind,
parser::{ parser::{
cursor::Cursor, error::ParseError, AllowAwait, AllowReturn, AllowYield, TokenParser, cursor::Cursor,
error::ParseError,
statement::{
declaration::hoistable::FunctionDeclaration, AllowAwait, AllowReturn,
LabelIdentifier, Statement,
},
AllowYield, TokenParser,
}, },
}, },
BoaProfiler, BoaProfiler,
@ -49,18 +54,38 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> { fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
let _timer = BoaProfiler::global().start_event("Label", "Parsing"); let _timer = BoaProfiler::global().start_event("Label", "Parsing");
let name = LabelIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; let name = LabelIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect(Punctuator::Colon, "Labelled Statement")?; 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); let strict = cursor.strict_mode();
Ok(stmt) 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<str>) { fn set_label_for_node(node: &mut Node, name: Box<str>) {
match stmt { match node {
Node::ForLoop(ref mut for_loop) => for_loop.set_label(name), 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::ForOfLoop(ref mut for_of_loop) => for_of_loop.set_label(name),
Node::ForInLoop(ref mut for_in_loop) => for_in_loop.set_label(name), Node::ForInLoop(ref mut for_in_loop) => for_in_loop.set_label(name),

34
boa/src/syntax/parser/statement/mod.rs

@ -520,24 +520,38 @@ where
match next_token.kind() { match next_token.kind() {
TokenKind::Identifier(ref s) => Ok(s.clone()), 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() { if cursor.strict_mode() {
Err(ParseError::lex(LexError::Syntax( Err(ParseError::general(
"yield keyword in binding identifier not allowed in strict mode".into(), "yield keyword in binding identifier not allowed in strict mode",
next_token.span().start(), next_token.span().start(),
))) ))
} else { } 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() { if cursor.strict_mode() {
Err(ParseError::lex(LexError::Syntax( Err(ParseError::general(
"await keyword in binding identifier not allowed in strict mode".into(), "await keyword in binding identifier not allowed in strict mode",
next_token.span().start(), next_token.span().start(),
))) ))
} else { } else {
Ok(k.as_str().into()) Ok("await".into())
} }
} }
_ => Err(ParseError::expected( _ => Err(ParseError::expected(

Loading…
Cancel
Save