Skip to content

Commit

Permalink
Refactor IdentifierReference parsing (#2055)
Browse files Browse the repository at this point in the history
This Pull Request changes the following:

- Crate dedicated `IdentifierReference` parser and refactor `PrimaryExpression` and `PropertyDefinition` parsers to use it.
- Move `BindingIdentifier` and `LabelIdentifier` parsers from statement parser module to expression parser module to conform with the spec.
- Add and early error case while converting an `ObjectLiteral` to an `ObjectAssignmentPattern`
  • Loading branch information
raskad authored and Razican committed Jun 8, 2022
1 parent 5489d85 commit 7abd41b
Show file tree
Hide file tree
Showing 21 changed files with 391 additions and 285 deletions.
32 changes: 22 additions & 10 deletions boa_engine/src/syntax/ast/node/operator/assign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::syntax::ast::node::{
ArrayDecl, DeclarationPattern, GetConstField, GetField, Identifier, Node, Object,
};
use boa_gc::{Finalize, Trace};
use boa_interner::{Interner, ToInternedString};
use boa_interner::{Interner, Sym, ToInternedString};

#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -90,18 +90,18 @@ pub enum AssignTarget {
impl AssignTarget {
/// Converts the left-hand-side node of an assignment expression into it's an [`AssignTarget`].
/// Returns `None` if the given node is an invalid left-hand-side for a assignment expression.
pub(crate) fn from_node(node: &Node) -> Option<Self> {
pub(crate) fn from_node(node: &Node, strict: bool) -> Option<Self> {
match node {
Node::Identifier(target) => Some(Self::Identifier(*target)),
Node::GetPrivateField(target) => Some(Self::GetPrivateField(target.clone())),
Node::GetConstField(target) => Some(Self::GetConstField(target.clone())),
Node::GetField(target) => Some(Self::GetField(target.clone())),
Node::Object(object) => {
let pattern = object_decl_to_declaration_pattern(object)?;
let pattern = object_decl_to_declaration_pattern(object, strict)?;
Some(Self::DeclarationPattern(pattern))
}
Node::ArrayDecl(array) => {
let pattern = array_decl_to_declaration_pattern(array)?;
let pattern = array_decl_to_declaration_pattern(array, strict)?;
Some(Self::DeclarationPattern(pattern))
}
_ => None,
Expand Down Expand Up @@ -140,11 +140,17 @@ impl From<GetField> for AssignTarget {
}

/// Converts an object literal into an object declaration pattern.
pub(crate) fn object_decl_to_declaration_pattern(object: &Object) -> Option<DeclarationPattern> {
pub(crate) fn object_decl_to_declaration_pattern(
object: &Object,
strict: bool,
) -> Option<DeclarationPattern> {
let mut bindings = Vec::new();
let mut excluded_keys = Vec::new();
for (i, property) in object.properties().iter().enumerate() {
match property {
PropertyDefinition::IdentifierReference(ident) if strict && *ident == Sym::EVAL => {
return None
}
PropertyDefinition::IdentifierReference(ident) => {
excluded_keys.push(*ident);
bindings.push(BindingPatternTypeObject::SingleName {
Expand All @@ -155,6 +161,9 @@ pub(crate) fn object_decl_to_declaration_pattern(object: &Object) -> Option<Decl
}
PropertyDefinition::Property(name, node) => match (name, node) {
(PropertyName::Literal(name), Node::Identifier(ident)) if *name == ident.sym() => {
if strict && *name == Sym::EVAL {
return None;
}
excluded_keys.push(*name);
bindings.push(BindingPatternTypeObject::SingleName {
ident: *name,
Expand Down Expand Up @@ -196,7 +205,10 @@ pub(crate) fn object_decl_to_declaration_pattern(object: &Object) -> Option<Decl
}

/// Converts an array declaration into an array declaration pattern.
pub(crate) fn array_decl_to_declaration_pattern(array: &ArrayDecl) -> Option<DeclarationPattern> {
pub(crate) fn array_decl_to_declaration_pattern(
array: &ArrayDecl,
strict: bool,
) -> Option<DeclarationPattern> {
if array.has_trailing_comma_spread() {
return None;
}
Expand Down Expand Up @@ -227,11 +239,11 @@ pub(crate) fn array_decl_to_declaration_pattern(array: &ArrayDecl) -> Option<Dec
});
}
Node::ArrayDecl(array) => {
let pattern = array_decl_to_declaration_pattern(array)?;
let pattern = array_decl_to_declaration_pattern(array, strict)?;
bindings.push(BindingPatternTypeArray::BindingPatternRest { pattern });
}
Node::Object(object) => {
let pattern = object_decl_to_declaration_pattern(object)?;
let pattern = object_decl_to_declaration_pattern(object, strict)?;
bindings.push(BindingPatternTypeArray::BindingPatternRest { pattern });
}
_ => return None,
Expand Down Expand Up @@ -268,11 +280,11 @@ pub(crate) fn array_decl_to_declaration_pattern(array: &ArrayDecl) -> Option<Dec
AssignTarget::GetPrivateField(_) => return None,
},
Node::ArrayDecl(array) => {
let pattern = array_decl_to_declaration_pattern(array)?;
let pattern = array_decl_to_declaration_pattern(array, strict)?;
bindings.push(BindingPatternTypeArray::BindingPattern { pattern });
}
Node::Object(object) => {
let pattern = object_decl_to_declaration_pattern(object)?;
let pattern = object_decl_to_declaration_pattern(object, strict)?;
bindings.push(BindingPatternTypeArray::BindingPattern { pattern });
}
Node::GetField(get_field) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use crate::syntax::{
lexer::{Error as LexError, TokenKind},
parser::{
error::{ErrorContext, ParseError, ParseResult},
expression::BindingIdentifier,
function::{FormalParameters, FunctionBody},
statement::BindingIdentifier,
AllowAwait, AllowIn, AllowYield, Cursor, TokenParser,
},
};
Expand Down
2 changes: 1 addition & 1 deletion boa_engine/src/syntax/parser/expression/assignment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ where
}

cursor.next(interner)?.expect("= token vanished");
if let Some(target) = AssignTarget::from_node(&lhs) {
if let Some(target) = AssignTarget::from_node(&lhs, cursor.strict_mode()) {
if let AssignTarget::Identifier(ident) = target {
self.name = Some(ident.sym());
}
Expand Down
238 changes: 238 additions & 0 deletions boa_engine/src/syntax/parser/expression/identifiers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
//! Identifiers parsing.
//!
//! More information:
//! - [ECMAScript specification][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-identifiers
//!

use crate::syntax::{
ast::{node::Identifier, Keyword},
lexer::{Error as LexError, TokenKind},
parser::{cursor::Cursor, AllowAwait, AllowYield, ParseError, TokenParser},
};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use std::io::Read;

const RESERVED_IDENTIFIERS_STRICT: [Sym; 9] = [
Sym::IMPLEMENTS,
Sym::INTERFACE,
Sym::LET,
Sym::PACKAGE,
Sym::PRIVATE,
Sym::PROTECTED,
Sym::PUBLIC,
Sym::STATIC,
Sym::YIELD,
];

/// Identifier reference parsing.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-IdentifierReference
#[derive(Debug, Clone, Copy)]
pub(in crate::syntax::parser) struct IdentifierReference {
allow_yield: AllowYield,
allow_await: AllowAwait,
}

impl IdentifierReference {
/// Creates a new `IdentifierReference` parser.
pub(in crate::syntax::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}

impl<R> TokenParser<R> for IdentifierReference
where
R: Read,
{
type Output = Identifier;

fn parse(
self,
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<Self::Output, ParseError> {
let _timer = Profiler::global().start_event("IdentifierReference", "Parsing");

let token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?;

match token.kind() {
TokenKind::Identifier(ident)
if cursor.strict_mode() && RESERVED_IDENTIFIERS_STRICT.contains(ident) =>
{
Err(ParseError::general(
"using future reserved keyword not allowed in strict mode IdentifierReference",
token.span().start(),
))
}
TokenKind::Identifier(ident) => Ok(Identifier::new(*ident)),
TokenKind::Keyword((Keyword::Let, _)) if cursor.strict_mode() => {
Err(ParseError::general(
"using future reserved keyword not allowed in strict mode IdentifierReference",
token.span().start(),
))
}
TokenKind::Keyword((Keyword::Let, _)) => Ok(Identifier::new(Sym::LET)),
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",
token.span().start(),
))
}
TokenKind::Keyword((Keyword::Yield, _)) if !self.allow_yield.0 => {
if cursor.strict_mode() {
return Err(ParseError::general(
"Unexpected strict mode reserved word",
token.span().start(),
));
}
Ok(Identifier::new(Sym::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".
Err(ParseError::general(
"Unexpected identifier",
token.span().start(),
))
}
TokenKind::Keyword((Keyword::Await, _)) if !self.allow_await.0 => {
Ok(Identifier::new(Sym::AWAIT))
}
_ => Err(ParseError::unexpected(
token.to_string(interner),
token.span(),
"IdentifierReference",
)),
}
}
}

/// Binding identifier parsing.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-BindingIdentifier
#[derive(Debug, Clone, Copy)]
pub(in crate::syntax::parser) struct BindingIdentifier {
allow_yield: AllowYield,
allow_await: AllowAwait,
}

impl BindingIdentifier {
/// Creates a new `BindingIdentifier` parser.
pub(in crate::syntax::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}

impl<R> TokenParser<R> for BindingIdentifier
where
R: Read,
{
type Output = Sym;

/// Strict mode parsing as per <https://tc39.es/ecma262/#sec-identifiers-static-semantics-early-errors>.
fn parse(
self,
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<Self::Output, ParseError> {
let _timer = Profiler::global().start_event("BindingIdentifier", "Parsing");

let next_token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?;

match next_token.kind() {
TokenKind::Identifier(Sym::ARGUMENTS) if cursor.strict_mode() => {
Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'arguments' in strict mode".into(),
next_token.span().start(),
)))
}
TokenKind::Identifier(Sym::EVAL) if cursor.strict_mode() => {
Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'eval' in strict mode".into(),
next_token.span().start(),
)))
}
TokenKind::Identifier(ident) => {
if cursor.strict_mode() && RESERVED_IDENTIFIERS_STRICT.contains(ident) {
return Err(ParseError::general(
"using future reserved keyword not allowed in strict mode",
next_token.span().start(),
));
}
Ok(*ident)
}
TokenKind::Keyword((Keyword::Let, _)) if cursor.strict_mode() => {
Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'let' in strict mode".into(),
next_token.span().start(),
)))
}
TokenKind::Keyword((Keyword::Let, _)) => Ok(Sym::LET),
TokenKind::Keyword((Keyword::Yield, _)) if self.allow_yield.0 => {
// Early Error: It is a Syntax Error if this production has a [Yield] parameter and StringValue of Identifier is "yield".
Err(ParseError::general(
"Unexpected identifier",
next_token.span().start(),
))
}
TokenKind::Keyword((Keyword::Yield, _)) if !self.allow_yield.0 => {
if cursor.strict_mode() {
Err(ParseError::general(
"yield keyword in binding identifier not allowed in strict mode",
next_token.span().start(),
))
} else {
Ok(Sym::YIELD)
}
}
TokenKind::Keyword((Keyword::Await, _)) if cursor.arrow() => Ok(Sym::AWAIT),
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 => Ok(Sym::AWAIT),
_ => Err(ParseError::expected(
["identifier".to_owned()],
next_token.to_string(interner),
next_token.span(),
"binding identifier",
)),
}
}
}

/// Label identifier parsing.
///
/// This seems to be the same as a `BindingIdentifier`.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-LabelIdentifier
pub(in crate::syntax::parser) type LabelIdentifier = BindingIdentifier;

0 comments on commit 7abd41b

Please sign in to comment.