diff --git a/boa/src/bytecompiler.rs b/boa/src/bytecompiler.rs index 1c3bc8b9ca3..0d87d2471e4 100644 --- a/boa/src/bytecompiler.rs +++ b/boa/src/bytecompiler.rs @@ -672,6 +672,38 @@ impl ByteCompiler { } } } + MethodDefinitionKind::Async => { + // TODO: Implement async + self.emit_opcode(Opcode::PushUndefined); + self.emit_opcode(Opcode::Swap); + match name { + PropertyName::Literal(name) => { + let index = self.get_or_insert_name(name); + self.emit(Opcode::SetPropertyByName, &[index]) + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true); + self.emit_opcode(Opcode::Swap); + self.emit_opcode(Opcode::SetPropertyByValue); + } + } + } + MethodDefinitionKind::AsyncGenerator => { + // TODO: Implement async generators + self.emit_opcode(Opcode::PushUndefined); + self.emit_opcode(Opcode::Swap); + match name { + PropertyName::Literal(name) => { + let index = self.get_or_insert_name(name); + self.emit(Opcode::SetPropertyByName, &[index]) + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true); + self.emit_opcode(Opcode::Swap); + self.emit_opcode(Opcode::SetPropertyByValue); + } + } + } } } // TODO: Spread Object diff --git a/boa/src/syntax/ast/node/declaration/async_generator_decl/mod.rs b/boa/src/syntax/ast/node/declaration/async_generator_decl/mod.rs new file mode 100644 index 00000000000..74a51610da3 --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/async_generator_decl/mod.rs @@ -0,0 +1,94 @@ +//! Async Generator Declaration + +use crate::{ + exec::Executable, + syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, + BoaProfiler, Context, JsResult, JsValue, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "deser")] +use serde::{Deserialize, Serialize}; + +/// The 'async function*' defines an async generator function +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorMethod +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct AsyncGeneratorDecl { + name: Box, + parameters: Box<[FormalParameter]>, + body: StatementList, +} + +impl AsyncGeneratorDecl { + /// Creates a new async generator declaration. + pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self + where + N: Into>, + P: Into>, + B: Into, + { + Self { + name: name.into(), + parameters: parameters.into(), + body: body.into(), + } + } + + /// Gets the name of the async function declaration. + pub fn name(&self) -> &str { + &self.name + } + + /// Gets the list of parameters of the async function declaration. + pub fn parameters(&self) -> &[FormalParameter] { + &self.parameters + } + + /// Gets the body of the async function 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, "async 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 AsyncGeneratorDecl { + fn run(&self, _: &mut Context) -> JsResult { + let _timer = BoaProfiler::global().start_event("AsyncGeneratorDecl", "exec"); + //TODO: Implement AsyncGeneratorDecl + Ok(JsValue::undefined()) + } +} + +impl From for Node { + fn from(decl: AsyncGeneratorDecl) -> Self { + Self::AsyncGeneratorDecl(decl) + } +} + +impl fmt::Display for AsyncGeneratorDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} diff --git a/boa/src/syntax/ast/node/declaration/async_generator_expr/mod.rs b/boa/src/syntax/ast/node/declaration/async_generator_expr/mod.rs new file mode 100644 index 00000000000..c4ab57e365e --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/async_generator_expr/mod.rs @@ -0,0 +1,105 @@ +//! Async Generator Expression + +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 `async function*` keyword can be used to define a generator function inside an expression. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorExpression +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct AsyncGeneratorExpr { + name: Option>, + parameters: Box<[FormalParameter]>, + body: StatementList, +} + +impl AsyncGeneratorExpr { + /// Creates a new async generator expression + pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self + where + N: Into>>, + P: Into>, + B: Into, + { + Self { + name: name.into(), + parameters: parameters.into(), + body: body.into(), + } + } + + /// Gets the name of the async generator expression + pub fn name(&self) -> Option<&str> { + self.name.as_ref().map(Box::as_ref) + } + + /// Gets the list of parameters of the async generator expression + pub fn parameters(&self) -> &[FormalParameter] { + &self.parameters + } + + /// Gets the body of the async generator expression + pub fn body(&self) -> &StatementList { + &self.body + } + + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + f.write_str("async 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) + } + + 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 AsyncGeneratorExpr { + fn run(&self, _context: &mut Context) -> JsResult { + //TODO: Implement AsyncGeneratorFunction + Ok(JsValue::undefined()) + } +} + +impl fmt::Display for AsyncGeneratorExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(expr: AsyncGeneratorExpr) -> Self { + Self::AsyncGeneratorExpr(expr) + } +} diff --git a/boa/src/syntax/ast/node/declaration/mod.rs b/boa/src/syntax/ast/node/declaration/mod.rs index 2b926af1aa2..71f57dc2240 100644 --- a/boa/src/syntax/ast/node/declaration/mod.rs +++ b/boa/src/syntax/ast/node/declaration/mod.rs @@ -15,6 +15,8 @@ use serde::{Deserialize, Serialize}; pub mod arrow_function_decl; pub mod async_function_decl; pub mod async_function_expr; +pub mod async_generator_decl; +pub mod async_generator_expr; pub mod function_decl; pub mod function_expr; pub mod generator_decl; @@ -22,7 +24,8 @@ pub mod generator_expr; pub use self::{ arrow_function_decl::ArrowFunctionDecl, async_function_decl::AsyncFunctionDecl, - async_function_expr::AsyncFunctionExpr, function_decl::FunctionDecl, + async_function_expr::AsyncFunctionExpr, async_generator_decl::AsyncGeneratorDecl, + async_generator_expr::AsyncGeneratorExpr, function_decl::FunctionDecl, function_expr::FunctionExpr, }; diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 422f935f481..6c05941393a 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -30,6 +30,7 @@ pub use self::{ call::Call, conditional::{ConditionalOp, If}, declaration::{ + async_generator_decl::AsyncGeneratorDecl, async_generator_expr::AsyncGeneratorExpr, generator_decl::GeneratorDecl, generator_expr::GeneratorExpr, ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, FunctionDecl, FunctionExpr, @@ -82,6 +83,12 @@ pub enum Node { /// An async function expression node. [More information](./declaration/struct.AsyncFunctionExpr.html). AsyncFunctionExpr(AsyncFunctionExpr), + /// An async generator expression node. + AsyncGeneratorExpr(AsyncGeneratorExpr), + + /// An async generator declaration node. + AsyncGeneratorDecl(AsyncGeneratorDecl), + /// An await expression node. [More information](./await_expr/struct.AwaitExpression.html). AwaitExpr(AwaitExpr), @@ -317,6 +324,8 @@ impl Node { Self::Yield(ref y) => Display::fmt(y, f), Self::GeneratorDecl(ref decl) => Display::fmt(decl, f), Self::GeneratorExpr(ref expr) => expr.display(f, indentation), + Self::AsyncGeneratorExpr(ref expr) => expr.display(f, indentation), + Self::AsyncGeneratorDecl(ref decl) => decl.display(f, indentation), } } } @@ -327,6 +336,8 @@ impl Executable for Node { match *self { Node::AsyncFunctionDecl(ref decl) => decl.run(context), Node::AsyncFunctionExpr(ref function_expr) => function_expr.run(context), + Node::AsyncGeneratorExpr(ref expr) => expr.run(context), + Node::AsyncGeneratorDecl(ref decl) => decl.run(context), Node::AwaitExpr(ref expr) => expr.run(context), Node::Call(ref call) => call.run(context), Node::Const(Const::Null) => Ok(JsValue::null()), @@ -625,6 +636,26 @@ pub enum MethodDefinitionKind { /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#generator_methods Generator, + + /// Async generators can be used to define a method + /// + /// More information + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorMethod + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#async_generator_methods + AsyncGenerator, + + /// Async function can be used to define a method + /// + /// More information + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-AsyncMethod + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#async_methods + Async, } unsafe impl Trace for MethodDefinitionKind { diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index 4d06bfb5aae..809cfc89c7a 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -72,7 +72,10 @@ impl Object { match &kind { MethodDefinitionKind::Get => write!(f, "get ")?, MethodDefinitionKind::Set => write!(f, "set ")?, - MethodDefinitionKind::Ordinary | MethodDefinitionKind::Generator => (), + MethodDefinitionKind::Ordinary + | MethodDefinitionKind::Generator + | MethodDefinitionKind::Async + | MethodDefinitionKind::AsyncGenerator => (), } write!(f, "{}(", key)?; join_nodes(f, node.parameters())?; @@ -179,6 +182,31 @@ impl Executable for Object { context, )?; } + &MethodDefinitionKind::AsyncGenerator => { + // TODO: Implement async generator method definition execution. + obj.__define_own_property__( + name, + PropertyDescriptor::builder() + .value(JsValue::undefined()) + .writable(true) + .enumerable(true) + .configurable(true) + .build(), + context, + )?; + } + &MethodDefinitionKind::Async => { + obj.__define_own_property__( + name, + PropertyDescriptor::builder() + .value(JsValue::undefined()) + .writable(true) + .enumerable(true) + .configurable(true) + .build(), + context, + )?; + } } } // [spec]: https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation diff --git a/boa/src/syntax/parser/expression/primary/async_generator_expression/mod.rs b/boa/src/syntax/parser/expression/primary/async_generator_expression/mod.rs new file mode 100644 index 00000000000..66e69a55058 --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/async_generator_expression/mod.rs @@ -0,0 +1,129 @@ +//! Async Generator Expression Parser +//! +//! Implements TokenParser for AsyncGeneratorExpression and outputs +//! an AsyncGeneratorExpr ast node +//! +//! More information: +//! - [ECMAScript specification][spec] +//! +//! [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorExpression +#[cfg(test)] +mod test; + +use crate::{ + syntax::{ + ast::{node::AsyncGeneratorExpr, Keyword, Punctuator}, + lexer::{Error as LexError, Position, TokenKind}, + parser::{ + function::{FormalParameters, FunctionBody}, + statement::BindingIdentifier, + Cursor, ParseError, TokenParser, + }, + }, + BoaProfiler, +}; + +use std::io::Read; + +/// Async Generator Expression Parsing +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorExpression +#[derive(Debug, Clone, Copy)] +pub(super) struct AsyncGeneratorExpression; + +impl TokenParser for AsyncGeneratorExpression +where + R: Read, +{ + //The below needs to be implemented in ast::node + type Output = AsyncGeneratorExpr; + + fn parse(self, cursor: &mut Cursor) -> Result { + let _timer = BoaProfiler::global().start_event("AsyncGeneratorExpression", "Parsing"); + + cursor.peek_expect_no_lineterminator(0, "async generator expression")?; + cursor.expect(Keyword::Function, "async generator expression")?; + cursor.expect( + TokenKind::Punctuator(Punctuator::Mul), + "async generator expression", + )?; + + let name = if let Some(token) = cursor.peek(0)? { + match token.kind() { + TokenKind::Punctuator(Punctuator::OpenParen) => None, + _ => Some(BindingIdentifier::new(true, true).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 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 generator expression")? + .span() + .end(); + + let params = FormalParameters::new(true, true).parse(cursor)?; + + cursor.expect(Punctuator::CloseParen, "async generator expression")?; + cursor.expect(Punctuator::OpenBlock, "async generator expression")?; + + let body = FunctionBody::new(true, true).parse(cursor)?; + + cursor.expect(Punctuator::CloseBlock, "async 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. + { + 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), + }, + ))); + } + } + } + + //implement the below AsyncGeneratorExpr in ast::node + Ok(AsyncGeneratorExpr::new(name, params.parameters, body)) + } +} diff --git a/boa/src/syntax/parser/expression/primary/async_generator_expression/test.rs b/boa/src/syntax/parser/expression/primary/async_generator_expression/test.rs new file mode 100644 index 00000000000..e9ef670a353 --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/async_generator_expression/test.rs @@ -0,0 +1,82 @@ +use crate::syntax::{ + ast::{ + node::{AsyncGeneratorExpr, Declaration, DeclarationList, Return, StatementList}, + Const, + }, + parser::tests::check_parser, +}; + +///checks async generator expression parsing + +#[test] +fn check_async_generator_expr() { + check_parser( + "const add = async function*(){ + return 1; + }; + ", + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + "add", + Some( + AsyncGeneratorExpr::new::>, _, StatementList>( + None, + [], + vec![Return::new::<_, _, Option>>(Const::from(1), None).into()] + .into(), + ) + .into(), + ), + )] + .into(), + ) + .into()], + ); +} + +#[test] +fn check_nested_async_generator_expr() { + check_parser( + "const a = async function*() { + const b = async function*() { + return 1; + }; + }; + ", + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + "a", + Some( + AsyncGeneratorExpr::new::>, _, StatementList>( + None, + [], + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + "b", + Some( + AsyncGeneratorExpr::new::>, _, StatementList>( + None, + [], + vec![Return::new::<_, _, Option>>( + Const::from(1), + None, + ) + .into()] + .into(), + ) + .into(), + ), + )] + .into(), + ) + .into()] + .into(), + ) + .into(), + ), + )] + .into(), + ) + .into()], + ); +} diff --git a/boa/src/syntax/parser/expression/primary/generator_expression/mod.rs b/boa/src/syntax/parser/expression/primary/generator_expression/mod.rs index 19914300b2a..8ba348771dd 100644 --- a/boa/src/syntax/parser/expression/primary/generator_expression/mod.rs +++ b/boa/src/syntax/parser/expression/primary/generator_expression/mod.rs @@ -12,7 +12,7 @@ mod tests; use crate::{ syntax::{ - ast::{node::GeneratorExpr, Keyword, Punctuator}, + ast::{node::GeneratorExpr, Punctuator}, lexer::{Error as LexError, Position, TokenKind}, parser::{ function::{FormalParameters, FunctionBody}, @@ -52,15 +52,11 @@ where 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, + TokenKind::Punctuator(Punctuator::OpenParen) => None, + _ => Some(BindingIdentifier::new(true, false).parse(cursor)?), } } else { - None + return Err(ParseError::AbruptEnd); }; // Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code, diff --git a/boa/src/syntax/parser/expression/primary/mod.rs b/boa/src/syntax/parser/expression/primary/mod.rs index f66d1940b2e..1e78329e3b7 100644 --- a/boa/src/syntax/parser/expression/primary/mod.rs +++ b/boa/src/syntax/parser/expression/primary/mod.rs @@ -9,6 +9,7 @@ mod array_initializer; mod async_function_expression; +mod async_generator_expression; mod function_expression; mod generator_expression; mod object_initializer; @@ -18,8 +19,8 @@ mod tests; use self::{ array_initializer::ArrayLiteral, async_function_expression::AsyncFunctionExpression, - function_expression::FunctionExpression, generator_expression::GeneratorExpression, - object_initializer::ObjectLiteral, + async_generator_expression::AsyncGeneratorExpression, function_expression::FunctionExpression, + generator_expression::GeneratorExpression, object_initializer::ObjectLiteral, }; use super::Expression; use crate::{ @@ -77,6 +78,8 @@ where fn parse(self, cursor: &mut Cursor) -> ParseResult { let _timer = BoaProfiler::global().start_event("PrimaryExpression", "Parsing"); + // TODO: tok currently consumes the token instead of peeking, so the token + // isn't passed and consumed by parsers according to spec (EX: GeneratorExpression) let tok = cursor.next()?.ok_or(ParseError::AbruptEnd)?; match tok.kind() { @@ -89,9 +92,16 @@ where FunctionExpression.parse(cursor).map(Node::from) } } - TokenKind::Keyword(Keyword::Async) => AsyncFunctionExpression::new(self.allow_yield) - .parse(cursor) - .map(Node::from), + TokenKind::Keyword(Keyword::Async) => { + let mul_peek = cursor.peek(1)?.ok_or(ParseError::AbruptEnd)?; + if mul_peek.kind() == &TokenKind::Punctuator(Punctuator::Mul) { + AsyncGeneratorExpression.parse(cursor).map(Node::from) + } else { + AsyncFunctionExpression::new(self.allow_yield) + .parse(cursor) + .map(Node::from) + } + } TokenKind::Punctuator(Punctuator::OpenParen) => { cursor.set_goal(InputElement::RegExp); let expr = diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs index 041d713cafd..9ef767d8f76 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -193,6 +193,140 @@ where return Ok(node::PropertyDefinition::SpreadObject(node)); } + //Async [AsyncMethod, AsyncGeneratorMethod] object methods + if cursor.next_if(Keyword::Async)?.is_some() { + cursor.peek_expect_no_lineterminator(0, "Async object methods")?; + + let mul_check = cursor.next_if(Punctuator::Mul)?; + let property_name = + PropertyName::new(self.allow_yield, self.allow_await).parse(cursor)?; + + if mul_check.is_some() { + // MethodDefinition[?Yield, ?Await] -> AsyncGeneratorMethod[?Yield, ?Await] + + let params_start_position = cursor + .expect(Punctuator::OpenParen, "async generator method definition")? + .span() + .start(); + let params = FormalParameters::new(true, true).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "async generator method definition")?; + + // Early Error: UniqueFormalParameters : FormalParameters + // NOTE: does not appear to formally be in ECMAScript specs for method + 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), + "async generator method definition", + )?; + let body = FunctionBody::new(true, true).parse(cursor)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "async 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::AsyncGenerator, + property_name, + FunctionExpr::new(None, params.parameters, body), + )); + } else { + // MethodDefinition[?Yield, ?Await] -> AsyncMethod[?Yield, ?Await] + + let params_start_position = cursor + .expect(Punctuator::OpenParen, "async method definition")? + .span() + .start(); + let params = FormalParameters::new(false, true).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "async method definition")?; + + // Early Error: UniqueFormalParameters : FormalParameters + // NOTE: does not appear to be in ECMAScript specs + 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), + "async method definition", + )?; + let body = FunctionBody::new(true, true).parse(cursor)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "async 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::Async, + property_name, + FunctionExpr::new(None, params.parameters, body), + )); + } + } + // MethodDefinition[?Yield, ?Await] -> GeneratorMethod[?Yield, ?Await] if cursor.next_if(Punctuator::Mul)?.is_some() { let property_name = @@ -206,6 +340,7 @@ where cursor.expect(Punctuator::CloseParen, "generator method definition")?; // Early Error: UniqueFormalParameters : FormalParameters + // NOTE: does not appear to be in ECMAScript specs for GeneratorMethod if params.has_duplicates { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs index 29ca8c17b59..5348f7c947a 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs @@ -6,7 +6,7 @@ use crate::syntax::{ }, Const, }, - parser::tests::check_parser, + parser::tests::{check_invalid, check_parser}, }; /// Checks object literal parsing. @@ -294,3 +294,73 @@ fn check_object_spread() { .into()], ); } + +#[test] +fn check_async_method() { + let object_properties = vec![PropertyDefinition::method_definition( + MethodDefinitionKind::Async, + "dive", + FunctionExpr::new(None, vec![], vec![]), + )]; + + check_parser( + "const x = { + async dive() {} + }; + ", + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + "x", + Some(Object::from(object_properties).into()), + )] + .into(), + ) + .into()], + ); +} + +#[test] +fn check_async_generator_method() { + let object_properties = vec![PropertyDefinition::method_definition( + MethodDefinitionKind::AsyncGenerator, + "vroom", + FunctionExpr::new(None, vec![], vec![]), + )]; + + check_parser( + "const x = { + async* vroom() {} + }; + ", + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + "x", + Some(Object::from(object_properties).into()), + )] + .into(), + ) + .into()], + ); +} + +#[test] +fn check_async_method_lineterminator() { + check_invalid( + "const x = { + async + dive(){} + }; + ", + ) +} + +#[test] +fn check_async_gen_method_lineterminator() { + check_invalid( + "const x = { + async + * vroom() {} + }; + ", + ) +} diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs b/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs index 9e8e1d198ef..9fa9e1ca115 100644 --- a/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs +++ b/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs @@ -75,9 +75,9 @@ where type Output = AsyncFunctionDecl; fn parse(self, cursor: &mut Cursor) -> Result { - cursor.expect(Keyword::Async, "async function declaration")?; - cursor.peek_expect_no_lineterminator(0, "async function declaration")?; - cursor.expect(Keyword::Function, "async function declaration")?; + cursor.expect(Keyword::Async, "async hoistable declaration")?; + cursor.peek_expect_no_lineterminator(0, "async hoistable declaration")?; + cursor.expect(Keyword::Function, "async hoistable declaration")?; let result = parse_callable_declaration(&self, cursor)?; diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/mod.rs b/boa/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/mod.rs new file mode 100644 index 00000000000..683aea1e2c4 --- /dev/null +++ b/boa/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/mod.rs @@ -0,0 +1,105 @@ +//! Async Generator Declaration parsing +//! +//! Implements TokenParser for AsyncGeneratorDeclaration and outputs an +//! AsyncGeneratorDecl ast node +//! + +#[cfg(test)] +mod tests; +use crate::syntax::{ + ast::{node::AsyncGeneratorDecl, Keyword, Punctuator}, + parser::{ + statement::declaration::hoistable::{parse_callable_declaration, CallableDeclaration}, + AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, + }, +}; +use std::io::Read; + +/// Async Generator Declaration Parser +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorDeclaration +#[derive(Debug, Clone, Copy)] +pub(super) struct AsyncGeneratorDeclaration { + allow_yield: AllowYield, + allow_await: AllowAwait, + is_default: AllowDefault, +} + +impl AsyncGeneratorDeclaration { + /// Creates a new `AsyncGeneratorDeclaration` parser. + pub(super) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self + where + Y: Into, + A: Into, + D: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + is_default: is_default.into(), + } + } +} + +impl CallableDeclaration for AsyncGeneratorDeclaration { + #[inline] + fn error_context(&self) -> &'static str { + "async generator declaration" + } + + #[inline] + fn is_default(&self) -> bool { + self.is_default.0 + } + + #[inline] + fn name_allow_yield(&self) -> bool { + self.allow_yield.0 + } + + #[inline] + fn name_allow_await(&self) -> bool { + self.allow_await.0 + } + + #[inline] + fn parameters_allow_yield(&self) -> bool { + true + } + + #[inline] + fn parameters_allow_await(&self) -> bool { + true + } + + #[inline] + fn body_allow_yield(&self) -> bool { + true + } + + #[inline] + fn body_allow_await(&self) -> bool { + true + } +} + +impl TokenParser for AsyncGeneratorDeclaration +where + R: Read, +{ + type Output = AsyncGeneratorDecl; + + fn parse(self, cursor: &mut Cursor) -> Result { + cursor.expect(Keyword::Async, "async hoistable declaration")?; + cursor.peek_expect_no_lineterminator(0, "async hoistable declaration")?; + cursor.expect(Keyword::Function, "async hoistable declaration")?; + cursor.expect(Punctuator::Mul, "async generator declaration")?; + + let result = parse_callable_declaration(&self, cursor)?; + + Ok(AsyncGeneratorDecl::new(result.0, result.1, result.2)) + } +} diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/tests.rs b/boa/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/tests.rs new file mode 100644 index 00000000000..1a35bbb996b --- /dev/null +++ b/boa/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/tests.rs @@ -0,0 +1,9 @@ +use crate::syntax::{ast::node::AsyncGeneratorDecl, parser::tests::check_parser}; + +#[test] +fn async_generator_function_declaration() { + check_parser( + "async function* gen() {}", + vec![AsyncGeneratorDecl::new(Box::from("gen"), vec![], vec![]).into()], + ); +} diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/mod.rs b/boa/src/syntax/parser/statement/declaration/hoistable/mod.rs index 4f3cb18eacc..37dcad7df05 100644 --- a/boa/src/syntax/parser/statement/declaration/hoistable/mod.rs +++ b/boa/src/syntax/parser/statement/declaration/hoistable/mod.rs @@ -9,10 +9,12 @@ mod tests; mod async_function_decl; +mod async_generator_decl; mod function_decl; mod generator_decl; use async_function_decl::AsyncFunctionDeclaration; +use async_generator_decl::AsyncGeneratorDeclaration; pub(in crate::syntax::parser) use function_decl::FunctionDeclaration; use generator_decl::GeneratorDeclaration; @@ -84,9 +86,20 @@ where } } TokenKind::Keyword(Keyword::Async) => { - AsyncFunctionDeclaration::new(self.allow_yield, self.allow_await, false) + let next_token = cursor.peek(2)?.ok_or(ParseError::AbruptEnd)?; + if let TokenKind::Punctuator(Punctuator::Mul) = next_token.kind() { + AsyncGeneratorDeclaration::new( + self.allow_yield, + self.allow_await, + self.is_default, + ) .parse(cursor) .map(Node::from) + } else { + AsyncFunctionDeclaration::new(self.allow_yield, self.allow_await, false) + .parse(cursor) + .map(Node::from) + } } _ => unreachable!("unknown token found: {:?}", tok), }