diff --git a/core/ast/src/expression/access.rs b/core/ast/src/expression/access.rs index 96aabe7e55d..2ced62ff20b 100644 --- a/core/ast/src/expression/access.rs +++ b/core/ast/src/expression/access.rs @@ -47,6 +47,13 @@ impl From for PropertyAccessField { } } +impl From> for PropertyAccessField { + #[inline] + fn from(expr: Box) -> Self { + Self::Expr(expr) + } +} + impl VisitWith for PropertyAccessField { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where @@ -151,12 +158,12 @@ impl SimplePropertyAccess { } /// Creates a `PropertyAccess` AST Expression. - pub fn new(target: Expression, field: F) -> Self + pub fn new(target: Box, field: F) -> Self where F: Into, { Self { - target: target.into(), + target, field: field.into(), } } @@ -222,9 +229,9 @@ impl PrivatePropertyAccess { /// Creates a `GetPrivateField` AST Expression. #[inline] #[must_use] - pub fn new(value: Expression, field: PrivateName) -> Self { + pub fn new(value: Box, field: PrivateName) -> Self { Self { - target: value.into(), + target: value, field, } } diff --git a/core/ast/src/expression/call.rs b/core/ast/src/expression/call.rs index 7e4ed8f4ae3..ad24fe1ece8 100644 --- a/core/ast/src/expression/call.rs +++ b/core/ast/src/expression/call.rs @@ -38,6 +38,13 @@ impl Call { } } + /// Creates a new `Call` AST Expression from boxed function. + #[inline] + #[must_use] + pub fn new_boxed(function: Box, args: Box<[Expression]>) -> Self { + Self { function, args } + } + /// Gets the target function of this call expression. #[inline] #[must_use] @@ -71,6 +78,12 @@ impl From for Expression { } } +impl From for Box { + fn from(call: Call) -> Self { + Box::new(Expression::Call(call)) + } +} + impl VisitWith for Call { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where @@ -140,6 +153,12 @@ impl From for Expression { } } +impl From for Box { + fn from(call: SuperCall) -> Self { + Box::new(Expression::SuperCall(call)) + } +} + impl VisitWith for SuperCall { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where @@ -181,13 +200,9 @@ pub struct ImportCall { impl ImportCall { /// Creates a new `ImportCall` AST node. - pub fn new(arg: A) -> Self - where - A: Into, - { - Self { - arg: Box::new(arg.into()), - } + #[must_use] + pub fn new(arg: Box) -> Self { + Self { arg } } /// Retrieves the single argument of the import call. @@ -211,6 +226,12 @@ impl From for Expression { } } +impl From for Box { + fn from(call: ImportCall) -> Self { + Box::new(Expression::ImportCall(call)) + } +} + impl VisitWith for ImportCall { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where diff --git a/core/ast/src/expression/identifier.rs b/core/ast/src/expression/identifier.rs index db4e024663d..cc0839cbbdc 100644 --- a/core/ast/src/expression/identifier.rs +++ b/core/ast/src/expression/identifier.rs @@ -105,6 +105,12 @@ impl From for Expression { } } +impl From for Box { + fn from(local: Identifier) -> Self { + Box::new(Expression::Identifier(local)) + } +} + impl VisitWith for Identifier { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where diff --git a/core/ast/src/expression/mod.rs b/core/ast/src/expression/mod.rs index 49778b77771..67816a149da 100644 --- a/core/ast/src/expression/mod.rs +++ b/core/ast/src/expression/mod.rs @@ -92,22 +92,22 @@ pub enum Expression { Spread(Spread), /// See [`FunctionExpression`]. - FunctionExpression(FunctionExpression), + FunctionExpression(Box), /// See [`ArrowFunction`]. - ArrowFunction(ArrowFunction), + ArrowFunction(Box), /// See [`AsyncArrowFunction`]. - AsyncArrowFunction(AsyncArrowFunction), + AsyncArrowFunction(Box), /// See [`GeneratorExpression`]. - GeneratorExpression(GeneratorExpression), + GeneratorExpression(Box), /// See [`AsyncFunctionExpression`]. - AsyncFunctionExpression(AsyncFunctionExpression), + AsyncFunctionExpression(Box), /// See [`AsyncGeneratorExpression`]. - AsyncGeneratorExpression(AsyncGeneratorExpression), + AsyncGeneratorExpression(Box), /// See [`ClassExpression`]. ClassExpression(Box), @@ -277,6 +277,12 @@ impl Expression { } expression } + + /// Create boxed expression. + #[must_use] + pub fn boxed(f: impl FnOnce() -> Self) -> Box { + Box::new(f()) + } } impl From for Statement { diff --git a/core/ast/src/expression/new.rs b/core/ast/src/expression/new.rs index 5db52cf6e24..eb59193159d 100644 --- a/core/ast/src/expression/new.rs +++ b/core/ast/src/expression/new.rs @@ -70,6 +70,12 @@ impl From for Expression { } } +impl From for Box { + fn from(new: New) -> Self { + Box::new(Expression::New(new)) + } +} + impl VisitWith for New { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where diff --git a/core/ast/src/expression/operator/assign/mod.rs b/core/ast/src/expression/operator/assign/mod.rs index aa4e6e67d66..78e172bdee2 100644 --- a/core/ast/src/expression/operator/assign/mod.rs +++ b/core/ast/src/expression/operator/assign/mod.rs @@ -49,6 +49,13 @@ impl Assign { } } + /// Creates an `Assign` AST Expression with boxed values. + #[inline] + #[must_use] + pub fn new_boxed(op: AssignOp, lhs: Box, rhs: Box) -> Self { + Self { op, lhs, rhs } + } + /// Gets the operator of the assignment operation. #[inline] #[must_use] diff --git a/core/ast/src/expression/operator/binary/mod.rs b/core/ast/src/expression/operator/binary/mod.rs index 3dea406f035..cbff783e88d 100644 --- a/core/ast/src/expression/operator/binary/mod.rs +++ b/core/ast/src/expression/operator/binary/mod.rs @@ -50,6 +50,24 @@ impl Binary { } } + /// Creates a `BinOp` AST Expression with boxed values. + #[inline] + #[must_use] + pub fn new_boxed(op: BinaryOp, lhs: Box, rhs: Box) -> Self { + Self { op, lhs, rhs } + } + + /// Creates an `Box` from boxed values. + // Non-inline to reduce stack alloc size: most likely, it will be inlined in release anyway. + #[must_use] + pub fn new_boxed_expr( + op: BinaryOp, + lhs: Box, + rhs: Box, + ) -> Box { + Box::new(Self::new_boxed(op, lhs, rhs).into()) + } + /// Gets the binary operation of the Expression. #[inline] #[must_use] @@ -105,6 +123,13 @@ impl From for Expression { } } +impl From for Box { + #[inline] + fn from(op: Binary) -> Self { + Box::new(Expression::Binary(op)) + } +} + impl VisitWith for Binary { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where @@ -147,6 +172,13 @@ impl BinaryInPrivate { } } + /// Creates a `BinaryInPrivate` AST Expression. + #[inline] + #[must_use] + pub fn new_boxed(lhs: PrivateName, rhs: Box) -> Self { + Self { lhs, rhs } + } + /// Gets the left hand side of the binary operation. #[inline] #[must_use] @@ -180,6 +212,12 @@ impl From for Expression { } } +impl From for Box { + fn from(op: BinaryInPrivate) -> Self { + Box::new(Expression::BinaryInPrivate(op)) + } +} + impl VisitWith for BinaryInPrivate { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where diff --git a/core/ast/src/expression/operator/conditional.rs b/core/ast/src/expression/operator/conditional.rs index 826b0efa714..b9354f2c72d 100644 --- a/core/ast/src/expression/operator/conditional.rs +++ b/core/ast/src/expression/operator/conditional.rs @@ -53,11 +53,15 @@ impl Conditional { /// Creates a `Conditional` AST Expression. #[inline] #[must_use] - pub fn new(condition: Expression, if_true: Expression, if_false: Expression) -> Self { + pub fn new( + condition: Box, + if_true: Box, + if_false: Box, + ) -> Self { Self { - condition: Box::new(condition), - if_true: Box::new(if_true), - if_false: Box::new(if_false), + condition, + if_true, + if_false, } } } @@ -81,6 +85,13 @@ impl From for Expression { } } +impl From for Box { + #[inline] + fn from(cond_op: Conditional) -> Self { + Box::new(Expression::Conditional(cond_op)) + } +} + impl VisitWith for Conditional { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where diff --git a/core/ast/src/expression/operator/unary/mod.rs b/core/ast/src/expression/operator/unary/mod.rs index 3d66014f027..1b3f4359a6d 100644 --- a/core/ast/src/expression/operator/unary/mod.rs +++ b/core/ast/src/expression/operator/unary/mod.rs @@ -47,6 +47,13 @@ impl Unary { } } + /// Creates a new `UnaryOp` AST Expression with boxed expression. + #[inline] + #[must_use] + pub fn new_boxed(op: UnaryOp, target: Box) -> Self { + Self { op, target } + } + /// Gets the unary operation of the Expression. #[inline] #[must_use] @@ -83,6 +90,12 @@ impl From for Expression { } } +impl From for Box { + fn from(op: Unary) -> Self { + Box::new(Expression::Unary(op)) + } +} + impl VisitWith for Unary { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where diff --git a/core/ast/src/expression/operator/update/op.rs b/core/ast/src/expression/operator/update/op.rs index 7877bfa7b14..7d8527fca62 100644 --- a/core/ast/src/expression/operator/update/op.rs +++ b/core/ast/src/expression/operator/update/op.rs @@ -75,6 +75,15 @@ impl UpdateOp { Self::DecrementPost | Self::DecrementPre => "--", } } + + /// Return true if self is Inc op + #[must_use] + pub const fn is_inc(self) -> bool { + match self { + Self::IncrementPost | Self::IncrementPre => true, + Self::DecrementPost | Self::DecrementPre => false, + } + } } impl std::fmt::Display for UpdateOp { diff --git a/core/ast/src/expression/optional.rs b/core/ast/src/expression/optional.rs index d36665521ab..31cd87d80c5 100644 --- a/core/ast/src/expression/optional.rs +++ b/core/ast/src/expression/optional.rs @@ -208,11 +208,8 @@ impl Optional { /// Creates a new `Optional` expression. #[inline] #[must_use] - pub fn new(target: Expression, chain: Box<[OptionalOperation]>) -> Self { - Self { - target: Box::new(target), - chain, - } + pub fn new(target: Box, chain: Box<[OptionalOperation]>) -> Self { + Self { target, chain } } /// Gets the target of this `Optional` expression. @@ -236,6 +233,12 @@ impl From for Expression { } } +impl From for Box { + fn from(opt: Optional) -> Self { + Box::new(Expression::Optional(opt)) + } +} + impl ToInternedString for Optional { fn to_interned_string(&self, interner: &Interner) -> String { let mut buf = self.target.to_interned_string(interner); diff --git a/core/ast/src/expression/tagged_template.rs b/core/ast/src/expression/tagged_template.rs index 4ac3bd013e5..f550ad22a37 100644 --- a/core/ast/src/expression/tagged_template.rs +++ b/core/ast/src/expression/tagged_template.rs @@ -29,14 +29,14 @@ impl TaggedTemplate { #[inline] #[must_use] pub fn new( - tag: Expression, + tag: Box, raws: Box<[Sym]>, cookeds: Box<[Option]>, exprs: Box<[Expression]>, identifier: u64, ) -> Self { Self { - tag: tag.into(), + tag, raws, cookeds, exprs, @@ -105,6 +105,12 @@ impl From for Expression { } } +impl From> for Box { + fn from(boxed_template: Box) -> Self { + Box::new(Expression::TaggedTemplate(*boxed_template)) + } +} + impl VisitWith for TaggedTemplate { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where diff --git a/core/ast/src/expression/yield.rs b/core/ast/src/expression/yield.rs index ec316af9e07..a00d0e91aa8 100644 --- a/core/ast/src/expression/yield.rs +++ b/core/ast/src/expression/yield.rs @@ -44,6 +44,16 @@ impl Yield { delegate, } } + + /// Creates a `Yield` AST Expression from boxed expression. + #[inline] + #[must_use] + pub fn new_boxed(expr: Option>, delegate: bool) -> Self { + Self { + target: expr, + delegate, + } + } } impl From for Expression { diff --git a/core/ast/src/function/arrow_function.rs b/core/ast/src/function/arrow_function.rs index f1e721fe10b..ed59a00824a 100644 --- a/core/ast/src/function/arrow_function.rs +++ b/core/ast/src/function/arrow_function.rs @@ -55,6 +55,16 @@ impl ArrowFunction { linear_span: linear_span.into(), } } + /// Creates a new `ArrowFunctionDecl` AST Expression. + #[must_use] + pub fn new_boxed( + name: Option, + parameters: FormalParameterList, + body: FunctionBody, + linear_span: LinearSpan, + ) -> Box { + Box::new(Self::new(name, parameters, body, linear_span)) + } /// Gets the name of the arrow function. #[inline] @@ -121,8 +131,8 @@ impl ToIndentedString for ArrowFunction { } } -impl From for Expression { - fn from(decl: ArrowFunction) -> Self { +impl From> for Expression { + fn from(decl: Box) -> Self { Self::ArrowFunction(decl) } } diff --git a/core/ast/src/function/async_arrow_function.rs b/core/ast/src/function/async_arrow_function.rs index dfb517c6ca5..1031ee064b5 100644 --- a/core/ast/src/function/async_arrow_function.rs +++ b/core/ast/src/function/async_arrow_function.rs @@ -36,24 +36,23 @@ pub struct AsyncArrowFunction { impl AsyncArrowFunction { /// Creates a new `AsyncArrowFunction` AST Expression. - #[inline] #[must_use] - pub fn new( + pub fn new_boxed( name: Option, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, - ) -> Self { + ) -> Box { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); - Self { + Box::new(Self { name, parameters, body, contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), - } + }) } /// Gets the name of the async arrow function. @@ -121,8 +120,8 @@ impl ToIndentedString for AsyncArrowFunction { } } -impl From for Expression { - fn from(decl: AsyncArrowFunction) -> Self { +impl From> for Expression { + fn from(decl: Box) -> Self { Self::AsyncArrowFunction(decl) } } diff --git a/core/ast/src/function/async_function.rs b/core/ast/src/function/async_function.rs index b90c8a4bb3f..972d794af8b 100644 --- a/core/ast/src/function/async_function.rs +++ b/core/ast/src/function/async_function.rs @@ -166,18 +166,17 @@ pub struct AsyncFunctionExpression { impl AsyncFunctionExpression { /// Creates a new async function expression. - #[inline] #[must_use] - pub fn new( + pub fn new_boxed( name: Option, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, has_binding_identifier: bool, - ) -> Self { + ) -> Box { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); - Self { + Box::new(Self { name, parameters, body, @@ -186,7 +185,7 @@ impl AsyncFunctionExpression { contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), - } + }) } /// Gets the name of the async function expression. @@ -271,9 +270,9 @@ impl ToIndentedString for AsyncFunctionExpression { } } -impl From for Expression { +impl From> for Expression { #[inline] - fn from(expr: AsyncFunctionExpression) -> Self { + fn from(expr: Box) -> Self { Self::AsyncFunctionExpression(expr) } } diff --git a/core/ast/src/function/async_generator.rs b/core/ast/src/function/async_generator.rs index 6e2f9de98b9..83533abc2e7 100644 --- a/core/ast/src/function/async_generator.rs +++ b/core/ast/src/function/async_generator.rs @@ -166,18 +166,17 @@ pub struct AsyncGeneratorExpression { impl AsyncGeneratorExpression { /// Creates a new async generator expression. - #[inline] #[must_use] - pub fn new( + pub fn new_boxed( name: Option, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, has_binding_identifier: bool, - ) -> Self { + ) -> Box { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); - Self { + Box::new(Self { name, parameters, body, @@ -186,7 +185,7 @@ impl AsyncGeneratorExpression { contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), - } + }) } /// Gets the name of the async generator expression. @@ -264,9 +263,9 @@ impl ToIndentedString for AsyncGeneratorExpression { } } -impl From for Expression { +impl From> for Expression { #[inline] - fn from(expr: AsyncGeneratorExpression) -> Self { + fn from(expr: Box) -> Self { Self::AsyncGeneratorExpression(expr) } } diff --git a/core/ast/src/function/generator.rs b/core/ast/src/function/generator.rs index 1bfd0d970ca..293abfa880a 100644 --- a/core/ast/src/function/generator.rs +++ b/core/ast/src/function/generator.rs @@ -163,19 +163,18 @@ pub struct GeneratorExpression { } impl GeneratorExpression { - /// Creates a new generator expression. - #[inline] + /// Creates a new boxed generator expression. #[must_use] - pub fn new( + pub fn new_boxed( name: Option, parameters: FormalParameterList, body: FunctionBody, linear_span: LinearSpan, has_binding_identifier: bool, - ) -> Self { + ) -> Box { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); - Self { + Box::new(Self { name, parameters, body, @@ -184,7 +183,7 @@ impl GeneratorExpression { contains_direct_eval, scopes: FunctionScopes::default(), linear_span: linear_span.into(), - } + }) } /// Gets the name of the generator expression. @@ -262,9 +261,9 @@ impl ToIndentedString for GeneratorExpression { } } -impl From for Expression { +impl From> for Expression { #[inline] - fn from(expr: GeneratorExpression) -> Self { + fn from(expr: Box) -> Self { Self::GeneratorExpression(expr) } } diff --git a/core/ast/src/function/ordinary_function.rs b/core/ast/src/function/ordinary_function.rs index 77eb3e66efc..7779baf5481 100644 --- a/core/ast/src/function/ordinary_function.rs +++ b/core/ast/src/function/ordinary_function.rs @@ -202,6 +202,24 @@ impl FunctionExpression { } } + /// Creates a new boxed function expression. + #[must_use] + pub fn new_boxed( + name: Option, + parameters: FormalParameterList, + body: FunctionBody, + linear_span: Option, + has_binding_identifier: bool, + ) -> Box { + Box::new(Self::new( + name, + parameters, + body, + linear_span, + has_binding_identifier, + )) + } + /// Gets the name of the function expression. #[inline] #[must_use] @@ -285,9 +303,9 @@ impl ToIndentedString for FunctionExpression { } } -impl From for Expression { +impl From> for Expression { #[inline] - fn from(expr: FunctionExpression) -> Self { + fn from(expr: Box) -> Self { Self::FunctionExpression(expr) } } diff --git a/core/ast/src/statement/iteration/for_in_loop.rs b/core/ast/src/statement/iteration/for_in_loop.rs index b234318924c..39f9413a3ae 100644 --- a/core/ast/src/statement/iteration/for_in_loop.rs +++ b/core/ast/src/statement/iteration/for_in_loop.rs @@ -20,7 +20,7 @@ use core::ops::ControlFlow; #[derive(Clone, Debug, PartialEq)] pub struct ForInLoop { pub(crate) initializer: IterableLoopInitializer, - pub(crate) target: Expression, + pub(crate) target: Box, pub(crate) body: Box, pub(crate) target_contains_direct_eval: bool, pub(crate) contains_direct_eval: bool, @@ -36,8 +36,12 @@ impl ForInLoop { /// Creates a new `ForInLoop`. #[inline] #[must_use] - pub fn new(initializer: IterableLoopInitializer, target: Expression, body: Statement) -> Self { - let target_contains_direct_eval = contains(&target, ContainsSymbol::DirectEval); + pub fn new( + initializer: IterableLoopInitializer, + target: Box, + body: Statement, + ) -> Self { + let target_contains_direct_eval = contains(target.as_ref(), ContainsSymbol::DirectEval); let contains_direct_eval = contains(&initializer, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { diff --git a/core/ast/src/statement/iteration/for_of_loop.rs b/core/ast/src/statement/iteration/for_of_loop.rs index 42959593c29..2c20012372c 100644 --- a/core/ast/src/statement/iteration/for_of_loop.rs +++ b/core/ast/src/statement/iteration/for_of_loop.rs @@ -25,7 +25,7 @@ use core::ops::ControlFlow; #[derive(Clone, Debug, PartialEq)] pub struct ForOfLoop { pub(crate) init: IterableLoopInitializer, - pub(crate) iterable: Expression, + pub(crate) iterable: Box, pub(crate) body: Box, r#await: bool, pub(crate) iterable_contains_direct_eval: bool, @@ -44,11 +44,11 @@ impl ForOfLoop { #[must_use] pub fn new( init: IterableLoopInitializer, - iterable: Expression, + iterable: Box, body: Statement, r#await: bool, ) -> Self { - let iterable_contains_direct_eval = contains(&iterable, ContainsSymbol::DirectEval); + let iterable_contains_direct_eval = contains(iterable.as_ref(), ContainsSymbol::DirectEval); let contains_direct_eval = contains(&init, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { diff --git a/core/ast/src/statement/try.rs b/core/ast/src/statement/try.rs index 7112fad39cd..2573bc34e9b 100644 --- a/core/ast/src/statement/try.rs +++ b/core/ast/src/statement/try.rs @@ -28,7 +28,7 @@ use core::ops::ControlFlow; #[derive(Clone, Debug, PartialEq)] pub struct Try { block: Block, - handler: ErrorHandler, + handler: Box, } /// The type of error handler in a [`Try`] statement. @@ -48,8 +48,11 @@ impl Try { /// Creates a new `Try` AST node. #[inline] #[must_use] - pub const fn new(block: Block, handler: ErrorHandler) -> Self { - Self { block, handler } + pub fn new(block: Block, handler: ErrorHandler) -> Self { + Self { + block, + handler: Box::new(handler), + } } /// Gets the `try` block. @@ -63,7 +66,7 @@ impl Try { #[inline] #[must_use] pub const fn catch(&self) -> Option<&Catch> { - match &self.handler { + match &*self.handler { ErrorHandler::Catch(c) | ErrorHandler::Full(c, _) => Some(c), ErrorHandler::Finally(_) => None, } @@ -73,7 +76,7 @@ impl Try { #[inline] #[must_use] pub const fn finally(&self) -> Option<&Finally> { - match &self.handler { + match &*self.handler { ErrorHandler::Finally(f) | ErrorHandler::Full(_, f) => Some(f), ErrorHandler::Catch(_) => None, } @@ -126,7 +129,7 @@ impl VisitWith for Try { V: VisitorMut<'a>, { visitor.visit_block_mut(&mut self.block)?; - match &mut self.handler { + match &mut *self.handler { ErrorHandler::Catch(c) => visitor.visit_catch_mut(c)?, ErrorHandler::Finally(f) => visitor.visit_finally_mut(f)?, ErrorHandler::Full(c, f) => { diff --git a/core/engine/src/bytecompiler/expression/mod.rs b/core/engine/src/bytecompiler/expression/mod.rs index 23bb63c635b..06dc2fbd8e8 100644 --- a/core/engine/src/bytecompiler/expression/mod.rs +++ b/core/engine/src/bytecompiler/expression/mod.rs @@ -129,22 +129,22 @@ impl ByteCompiler<'_> { Expression::This => self.access_get(Access::This, dst), Expression::Spread(spread) => self.compile_expr(spread.target(), dst), Expression::FunctionExpression(function) => { - self.function_with_binding(function.into(), NodeKind::Expression, dst); + self.function_with_binding(function.as_ref().into(), NodeKind::Expression, dst); } Expression::ArrowFunction(function) => { - self.function_with_binding(function.into(), NodeKind::Expression, dst); + self.function_with_binding(function.as_ref().into(), NodeKind::Expression, dst); } Expression::AsyncArrowFunction(function) => { - self.function_with_binding(function.into(), NodeKind::Expression, dst); + self.function_with_binding(function.as_ref().into(), NodeKind::Expression, dst); } Expression::GeneratorExpression(function) => { - self.function_with_binding(function.into(), NodeKind::Expression, dst); + self.function_with_binding(function.as_ref().into(), NodeKind::Expression, dst); } Expression::AsyncFunctionExpression(function) => { - self.function_with_binding(function.into(), NodeKind::Expression, dst); + self.function_with_binding(function.as_ref().into(), NodeKind::Expression, dst); } Expression::AsyncGeneratorExpression(function) => { - self.function_with_binding(function.into(), NodeKind::Expression, dst); + self.function_with_binding(function.as_ref().into(), NodeKind::Expression, dst); } Expression::Call(call) => self.call(Callable::Call(call), dst), Expression::New(new) => self.call(Callable::New(new), dst), diff --git a/core/parser/src/error/mod.rs b/core/parser/src/error/mod.rs index b20092ed002..c320b429548 100644 --- a/core/parser/src/error/mod.rs +++ b/core/parser/src/error/mod.rs @@ -37,9 +37,98 @@ impl From for Error { } } +impl From for ErrorInner { + #[inline] + fn from(e: LexError) -> Self { + Self::lex(e) + } +} + +/// A struct which represents errors encountered during parsing an expression. +/// Contains boxed `ErrorInner` to reduce memory allocation on the stack. +#[derive(Debug)] +pub struct Error { + inner: Box, +} + +impl Error { + /// Changes the context of the error, if any. + fn set_context(self, new_context: &'static str) -> Self { + Self { + inner: Box::new((*self.inner).set_context(new_context)), + } + } + + /// Gets the context of the error, if any. + fn context(&self) -> Option<&'static str> { + self.inner.context() + } + + /// Creates an `Expected` parsing error. + pub(crate) fn expected(expected: E, found: F, span: Span, context: &'static str) -> Self + where + E: Into>, + F: Into>, + { + ErrorInner::expected(expected, found, span, context).into() + } + + /// Creates an `Unexpected` parsing error. + pub(crate) fn unexpected(found: F, span: Span, message: C) -> Self + where + F: Into>, + C: Into>, + { + ErrorInner::unexpected(found, span, message).into() + } + + /// Creates a "general" parsing error. + pub(crate) fn general(message: S, position: P) -> Self + where + S: Into>, + P: Into, + { + ErrorInner::general(message, position).into() + } + + /// Creates a "general" parsing error with the specific error message for a misplaced function declaration. + pub(crate) fn misplaced_function_declaration(position: Position, strict: bool) -> Self { + ErrorInner::misplaced_function_declaration(position, strict).into() + } + + /// Creates a "general" parsing error with the specific error message for a wrong function declaration with label. + pub(crate) fn wrong_labelled_function_declaration(position: Position) -> Self { + ErrorInner::wrong_labelled_function_declaration(position).into() + } + + /// Creates a parsing error from a lexing error. + pub(crate) fn lex(e: LexError) -> Self { + ErrorInner::lex(e).into() + } + + /// Creates a parsing error from a lexing error. + pub(crate) fn abrupt_end() -> Self { + ErrorInner::AbruptEnd.into() + } +} + +impl From for Error { + fn from(value: ErrorInner) -> Self { + Self { + inner: Box::new(value), + } + } +} + +impl From for ErrorInner { + fn from(value: Error) -> Self { + *value.inner + } +} + /// An enum which represents errors encountered during parsing an expression #[derive(Debug)] -pub enum Error { +pub enum ErrorInner { /// When it expected a certain kind of token, but got another as part of something Expected { /// The token(s) that were expected. @@ -86,7 +175,7 @@ pub enum Error { }, } -impl Error { +impl ErrorInner { /// Changes the context of the error, if any. fn set_context(self, new_context: &'static str) -> Self { match self { @@ -178,7 +267,7 @@ impl Error { } } -impl fmt::Display for Error { +impl fmt::Display for ErrorInner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Expected { @@ -233,4 +322,11 @@ impl fmt::Display for Error { } } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl std::error::Error for ErrorInner {} impl std::error::Error for Error {} diff --git a/core/parser/src/error/tests.rs b/core/parser/src/error/tests.rs index 93596068836..2f1b74e07c0 100644 --- a/core/parser/src/error/tests.rs +++ b/core/parser/src/error/tests.rs @@ -16,12 +16,12 @@ fn context() { assert_eq!(result.context(), Some("after")); let error = result.unwrap_err(); - if let Error::Expected { + if let ErrorInner::Expected { expected, found, span, context, - } = error + } = error.into() { assert_eq!(expected.as_ref(), &["testing".to_owned()]); assert_eq!(found, "nottesting".into()); @@ -31,7 +31,7 @@ fn context() { unreachable!(); } - let err = Error::AbruptEnd; + let err = Error::abrupt_end(); assert!(err.context().is_none()); let err = err.set_context("ignored"); assert!(err.context().is_none()); @@ -40,20 +40,20 @@ fn context() { #[test] fn from_lex_error() { let lex_err = LexError::syntax("testing", Position::new(1, 1)); - let parse_err: Error = lex_err.into(); + let parse_err: ErrorInner = lex_err.into(); - assert!(matches!(parse_err, Error::Lex { .. })); + assert!(matches!(parse_err, ErrorInner::Lex { .. })); let lex_err = LexError::syntax("testing", Position::new(1, 1)); - let parse_err = Error::lex(lex_err); + let parse_err = ErrorInner::lex(lex_err); - assert!(matches!(parse_err, Error::Lex { .. })); + assert!(matches!(parse_err, ErrorInner::Lex { .. })); } #[test] fn misplaced_function_declaration() { let err = Error::misplaced_function_declaration(Position::new(1, 1), false); - if let Error::General { message, position } = err { + if let ErrorInner::General { message, position } = err.into() { assert_eq!( message.as_ref(), "functions can only be declared at the top level or inside a block." @@ -64,7 +64,7 @@ fn misplaced_function_declaration() { } let err = Error::misplaced_function_declaration(Position::new(1, 1), true); - if let Error::General { message, position } = err { + if let ErrorInner::General { message, position } = err.into() { assert_eq!( message.as_ref(), "in strict mode code, functions can only be declared at the top level or inside a block." @@ -78,7 +78,7 @@ fn misplaced_function_declaration() { #[test] fn wrong_labelled_function_declaration() { let err = Error::wrong_labelled_function_declaration(Position::new(1, 1)); - if let Error::General { message, position } = err { + if let ErrorInner::General { message, position } = err.into() { assert_eq!( message.as_ref(), "labelled functions can only be declared at the top level or inside a block" @@ -156,7 +156,7 @@ fn display() { "this is a general error message at line 1, col 1" ); - let err = Error::AbruptEnd; + let err = Error::abrupt_end(); assert_eq!(err.to_string(), "abrupt end"); let lex_err = LexError::syntax("testing", Position::new(1, 1)); diff --git a/core/parser/src/parser/expression/assignment/async_arrow_function.rs b/core/parser/src/parser/expression/assignment/async_arrow_function.rs index c004b899cab..09506895026 100644 --- a/core/parser/src/parser/expression/assignment/async_arrow_function.rs +++ b/core/parser/src/parser/expression/assignment/async_arrow_function.rs @@ -67,6 +67,14 @@ where type Output = ast::function::AsyncArrowFunction; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("AsyncArrowFunction", "Parsing"); let async_token = @@ -149,7 +157,7 @@ where let linear_pos_end = body.linear_pos_end(); let span = start_linear_span.union(linear_pos_end); - Ok(ast::function::AsyncArrowFunction::new( + Ok(ast::function::AsyncArrowFunction::new_boxed( None, params, body, span, )) } diff --git a/core/parser/src/parser/expression/assignment/conditional.rs b/core/parser/src/parser/expression/assignment/conditional.rs index 01b1088532b..38f27de9a06 100644 --- a/core/parser/src/parser/expression/assignment/conditional.rs +++ b/core/parser/src/parser/expression/assignment/conditional.rs @@ -61,21 +61,44 @@ where type Output = Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("ConditionalExpression", "Parsing"); let lhs = ShortCircuitExpression::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; + + self.parse_boxed_tail(cursor, interner, lhs) + } +} +impl ConditionalExpression { + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_boxed_tail( + self, + cursor: &mut Cursor, + interner: &mut Interner, + lhs: Box, + ) -> ParseResult> { if let Some(tok) = cursor.peek(0, interner)? { if tok.kind() == &TokenKind::Punctuator(Punctuator::Question) { cursor.advance(interner); let then_clause = AssignmentExpression::new(true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; cursor.expect(Punctuator::Colon, "conditional expression", interner)?; let else_clause = AssignmentExpression::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; return Ok(Conditional::new(lhs, then_clause, else_clause).into()); } } diff --git a/core/parser/src/parser/expression/assignment/exponentiation.rs b/core/parser/src/parser/expression/assignment/exponentiation.rs index 60b86059599..a9a80c52004 100644 --- a/core/parser/src/parser/expression/assignment/exponentiation.rs +++ b/core/parser/src/parser/expression/assignment/exponentiation.rs @@ -57,6 +57,14 @@ where type Output = Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("ExponentiationExpression", "Parsing"); let next = cursor.peek(0, interner).or_abrupt()?; @@ -66,26 +74,41 @@ where Punctuator::Add | Punctuator::Sub | Punctuator::Not | Punctuator::Neg, ) => { return UnaryExpression::new(self.allow_yield, self.allow_await) - .parse(cursor, interner); + .parse_boxed(cursor, interner); } TokenKind::Keyword((Keyword::Await, _)) if self.allow_await.0 => { return UnaryExpression::new(self.allow_yield, self.allow_await) - .parse(cursor, interner); + .parse_boxed(cursor, interner); } _ => {} } - let lhs = - UpdateExpression::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; + let lhs = UpdateExpression::new(self.allow_yield, self.allow_await) + .parse_boxed(cursor, interner)?; + + self.parse_boxed_tail(cursor, interner, lhs) + } +} + +impl ExponentiationExpression { + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_boxed_tail( + self, + cursor: &mut Cursor, + interner: &mut Interner, + lhs: Box, + ) -> ParseResult> { if let Some(tok) = cursor.peek(0, interner)? { if tok.kind() == &TokenKind::Punctuator(Punctuator::Exp) { cursor.advance(interner); - return Ok(Binary::new( + return Ok(Binary::new_boxed_expr( ArithmeticOp::Exp.into(), lhs, - self.parse(cursor, interner)?, - ) - .into()); + self.parse_boxed(cursor, interner)?, + )); } } Ok(lhs) diff --git a/core/parser/src/parser/expression/assignment/mod.rs b/core/parser/src/parser/expression/assignment/mod.rs index 49a300bd77e..9ddc02dc730 100644 --- a/core/parser/src/parser/expression/assignment/mod.rs +++ b/core/parser/src/parser/expression/assignment/mod.rs @@ -30,8 +30,9 @@ use crate::{ }; use boa_ast::{ expression::operator::assign::{Assign, AssignOp, AssignTarget}, + function::FormalParameterList, operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, - Expression, Keyword, Punctuator, + Expression, Keyword, LinearSpan, Position, Punctuator, }; use boa_interner::Interner; use boa_profiler::Profiler; @@ -86,14 +87,61 @@ where type Output = Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("AssignmentExpression", "Parsing"); + + if let Some(ok) = self.parse_boxed_prefix_special_expr(cursor, interner)? { + return Ok(ok); + } + + cursor.set_goal(InputElement::Div); + + let peek_token = cursor.peek(0, interner).or_abrupt()?; + let position = peek_token.span().start(); + let start_linear_span = peek_token.linear_span(); + let lhs = ConditionalExpression::new(self.allow_in, self.allow_yield, self.allow_await) + .parse_boxed(cursor, interner)?; + + // If the left hand side is a parameter list, we must parse an arrow function. + if let Expression::FormalParameterList(parameters) = *lhs { + return self.parse_boxed_formal_parameter_list( + cursor, + interner, + position, + start_linear_span, + parameters, + ); + } + + self.parse_boxed_tail(cursor, interner, lhs) + } +} + +impl AssignmentExpression { + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_boxed_prefix_special_expr( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult>> { cursor.set_goal(InputElement::RegExp); match cursor.peek(0, interner).or_abrupt()?.kind() { // [+Yield]YieldExpression[?In, ?Await] TokenKind::Keyword((Keyword::Yield, _)) if self.allow_yield.0 => { return YieldExpression::new(self.allow_in, self.allow_await) - .parse(cursor, interner) + .parse_boxed(cursor, interner) + .map(Some) } // ArrowFunction[?In, ?Yield, ?Await] -> ArrowParameters[?Yield, ?Await] -> BindingIdentifier[?Yield, ?Await] TokenKind::IdentifierName(_) @@ -114,8 +162,8 @@ where self.allow_yield, self.allow_await, ) - .parse(cursor, interner) - .map(Expression::ArrowFunction); + .parse_boxed(cursor, interner) + .map(|arrow| Some(Box::new(Expression::ArrowFunction(arrow)))); } } } @@ -144,86 +192,106 @@ where TokenKind::Punctuator(Punctuator::Arrow) ))) { - return Ok(AsyncArrowFunction::new(self.allow_in, self.allow_yield) - .parse(cursor, interner)? - .into()); + return AsyncArrowFunction::new(self.allow_in, self.allow_yield) + .parse_boxed(cursor, interner) + .map(|arrow| Some(Box::new(Expression::AsyncArrowFunction(arrow)))); } } _ => {} } + Ok(None) + } - cursor.set_goal(InputElement::Div); - - let peek_token = cursor.peek(0, interner).or_abrupt()?; - let position = peek_token.span().start(); - let start_linear_span = peek_token.linear_span(); - let mut lhs = ConditionalExpression::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; - - // If the left hand side is a parameter list, we must parse an arrow function. - if let Expression::FormalParameterList(parameters) = lhs { - cursor.peek_expect_no_lineterminator(0, "arrow function", interner)?; + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + /// + /// # Return + /// * `Err(_)` if error occurs; + /// * `Ok(Some(Box))` if the next expression is `FormalParameterList`; + /// * `Ok(None)` otherwise; + fn parse_boxed_formal_parameter_list( + self, + cursor: &mut Cursor, + interner: &mut Interner, + position: Position, + start_linear_span: LinearSpan, + parameters: FormalParameterList, + ) -> ParseResult> { + cursor.peek_expect_no_lineterminator(0, "arrow function", interner)?; - cursor.expect( - TokenKind::Punctuator(Punctuator::Arrow), - "arrow function", - interner, - )?; - let arrow = cursor.arrow(); - cursor.set_arrow(true); - let body = ConciseBody::new(self.allow_in).parse(cursor, interner)?; - cursor.set_arrow(arrow); + cursor.expect( + TokenKind::Punctuator(Punctuator::Arrow), + "arrow function", + interner, + )?; + let arrow = cursor.arrow(); + cursor.set_arrow(true); + let body = ConciseBody::new(self.allow_in).parse(cursor, interner)?; + cursor.set_arrow(arrow); - // Early Error: ArrowFormalParameters are UniqueFormalParameters. - if parameters.has_duplicates() { - return Err(Error::lex(LexError::Syntax( - "Duplicate parameter name not allowed in this context".into(), - position, - ))); - } - - // Early Error: It is a Syntax Error if ArrowParameters Contains YieldExpression is true. - if contains(¶meters, ContainsSymbol::YieldExpression) { - return Err(Error::lex(LexError::Syntax( - "Yield expression not allowed in this context".into(), - position, - ))); - } + // Early Error: ArrowFormalParameters are UniqueFormalParameters. + if parameters.has_duplicates() { + return Err(Error::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + position, + ))); + } - // Early Error: It is a Syntax Error if ArrowParameters Contains AwaitExpression is true. - if contains(¶meters, ContainsSymbol::AwaitExpression) { - return Err(Error::lex(LexError::Syntax( - "Await expression not allowed in this context".into(), - position, - ))); - } + // Early Error: It is a Syntax Error if ArrowParameters Contains YieldExpression is true. + if contains(¶meters, ContainsSymbol::YieldExpression) { + return Err(Error::lex(LexError::Syntax( + "Yield expression not allowed in this context".into(), + position, + ))); + } - // Early Error: It is a Syntax Error if ConciseBodyContainsUseStrict of ConciseBody is true - // and IsSimpleParameterList of ArrowParameters is false. - if body.strict() && !parameters.is_simple() { - return Err(Error::lex(LexError::Syntax( - "Illegal 'use strict' directive in function with non-simple parameter list" - .into(), - position, - ))); - } + // Early Error: It is a Syntax Error if ArrowParameters Contains AwaitExpression is true. + if contains(¶meters, ContainsSymbol::AwaitExpression) { + return Err(Error::lex(LexError::Syntax( + "Await expression not allowed in this context".into(), + position, + ))); + } - // It is a Syntax Error if any element of the BoundNames of ArrowParameters - // also occurs in the LexicallyDeclaredNames of ConciseBody. - // https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors - name_in_lexically_declared_names( - &bound_names(¶meters), - &lexically_declared_names(&body), + // Early Error: It is a Syntax Error if ConciseBodyContainsUseStrict of ConciseBody is true + // and IsSimpleParameterList of ArrowParameters is false. + if body.strict() && !parameters.is_simple() { + return Err(Error::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list".into(), position, - interner, - )?; + ))); + } - let linear_pos_end = body.linear_pos_end(); - let span = start_linear_span.union(linear_pos_end); + // It is a Syntax Error if any element of the BoundNames of ArrowParameters + // also occurs in the LexicallyDeclaredNames of ConciseBody. + // https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors + name_in_lexically_declared_names( + &bound_names(¶meters), + &lexically_declared_names(&body), + position, + interner, + )?; - return Ok(boa_ast::function::ArrowFunction::new(None, parameters, body, span).into()); - } + let linear_pos_end = body.linear_pos_end(); + let span = start_linear_span.union(linear_pos_end); + + Ok(Expression::boxed(|| { + boa_ast::function::ArrowFunction::new_boxed(None, parameters, body, span).into() + })) + } + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_boxed_tail( + self, + cursor: &mut Cursor, + interner: &mut Interner, + mut lhs: Box, + ) -> ParseResult> { // Review if we are trying to assign to an invalid left hand side expression. if let Some(tok) = cursor.peek(0, interner)?.cloned() { match tok.kind() { @@ -231,18 +299,20 @@ where cursor.advance(interner); cursor.set_goal(InputElement::RegExp); - let lhs_name = if let Expression::Identifier(ident) = lhs { + let lhs_name = if let Expression::Identifier(ident) = *lhs { Some(ident) } else { None }; if let Some(target) = AssignTarget::from_expression(&lhs, cursor.strict()) { - let mut expr = self.parse(cursor, interner)?; + let mut expr = self.parse_boxed(cursor, interner)?; if let Some(ident) = lhs_name { expr.set_anonymous_function_definition_name(&ident); } - lhs = Assign::new(AssignOp::Assign, target, expr).into(); + lhs = Expression::boxed(|| { + Assign::new_boxed(AssignOp::Assign, Box::new(target), expr).into() + }); } else { return Err(Error::lex(LexError::Syntax( "Invalid left-hand side in assignment".into(), @@ -257,7 +327,7 @@ where { let assignop = p.as_assign_op().expect("assignop disappeared"); - let mut rhs = self.parse(cursor, interner)?; + let mut rhs = self.parse_boxed(cursor, interner)?; if assignop == AssignOp::BoolAnd || assignop == AssignOp::BoolOr || assignop == AssignOp::Coalesce @@ -266,7 +336,9 @@ where rhs.set_anonymous_function_definition_name(&ident); } } - lhs = Assign::new(assignop, target, rhs).into(); + lhs = Expression::boxed(|| { + Assign::new_boxed(assignop, Box::new(target), rhs).into() + }); } else { return Err(Error::lex(LexError::Syntax( "Invalid left-hand side in assignment".into(), diff --git a/core/parser/src/parser/expression/assignment/yield.rs b/core/parser/src/parser/expression/assignment/yield.rs index ff59cd4af24..6284db0a395 100644 --- a/core/parser/src/parser/expression/assignment/yield.rs +++ b/core/parser/src/parser/expression/assignment/yield.rs @@ -72,8 +72,8 @@ where TokenKind::Punctuator(Punctuator::Mul) => { cursor.advance(interner); let expr = AssignmentExpression::new(self.allow_in, true, self.allow_await) - .parse(cursor, interner)?; - Ok(Yield::new(Some(expr), true).into()) + .parse_boxed(cursor, interner)?; + Ok(Yield::new_boxed(Some(expr), true).into()) } TokenKind::IdentifierName(_) | TokenKind::Punctuator( @@ -111,10 +111,10 @@ where | TokenKind::RegularExpressionLiteral(_, _) | TokenKind::TemplateMiddle(_) => { let expr = AssignmentExpression::new(self.allow_in, true, self.allow_await) - .parse(cursor, interner)?; - Ok(Yield::new(Some(expr), false).into()) + .parse_boxed(cursor, interner)?; + Ok(Yield::new_boxed(Some(expr), false).into()) } - _ => Ok(Yield::new(None, false).into()), + _ => Ok(Yield::new_boxed(None, false).into()), } } } diff --git a/core/parser/src/parser/expression/left_hand_side/call.rs b/core/parser/src/parser/expression/left_hand_side/call.rs index aad09aab31d..0989e57ea5b 100644 --- a/core/parser/src/parser/expression/left_hand_side/call.rs +++ b/core/parser/src/parser/expression/left_hand_side/call.rs @@ -39,7 +39,7 @@ use boa_profiler::Profiler; pub(super) struct CallExpression { allow_yield: AllowYield, allow_await: AllowAwait, - first_member_expr: ast::Expression, + first_member_expr: Box, } impl CallExpression { @@ -47,7 +47,7 @@ impl CallExpression { pub(super) fn new( allow_yield: Y, allow_await: A, - first_member_expr: ast::Expression, + first_member_expr: Box, ) -> Self where Y: Into, @@ -75,7 +75,7 @@ where let lhs = if token.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) { let args = Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; - Call::new(self.first_member_expr, args).into() + Call::new_boxed(self.first_member_expr, args).into() } else { let next_token = cursor.next(interner)?.expect("token vanished"); return Err(Error::expected( @@ -95,12 +95,12 @@ where pub(super) struct CallExpressionTail { allow_yield: AllowYield, allow_await: AllowAwait, - call: ast::Expression, + call: Box, } impl CallExpressionTail { /// Creates a new `CallExpressionTail` parser. - pub(super) fn new(allow_yield: Y, allow_await: A, call: ast::Expression) -> Self + pub(super) fn new(allow_yield: Y, allow_await: A, call: Box) -> Self where Y: Into, A: Into, @@ -120,6 +120,14 @@ where type Output = ast::Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let mut lhs = self.call; while let Some(tok) = cursor.peek(0, interner)? { @@ -128,7 +136,7 @@ where TokenKind::Punctuator(Punctuator::OpenParen) => { let args = Arguments::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; - lhs = ast::Expression::from(Call::new(lhs, args)); + lhs = Call::new_boxed(lhs, args).into(); } TokenKind::Punctuator(Punctuator::Dot) => { cursor.advance(interner); @@ -162,25 +170,26 @@ where } }; - lhs = ast::Expression::PropertyAccess(access); + lhs = ast::Expression::boxed(|| ast::Expression::PropertyAccess(access)); } TokenKind::Punctuator(Punctuator::OpenBracket) => { cursor.advance(interner); let idx = Expression::new(true, self.allow_yield, self.allow_await) .parse(cursor, interner)?; cursor.expect(Punctuator::CloseBracket, "call expression", interner)?; - lhs = - ast::Expression::PropertyAccess(SimplePropertyAccess::new(lhs, idx).into()); + lhs = ast::Expression::boxed(|| { + ast::Expression::PropertyAccess(SimplePropertyAccess::new(lhs, idx).into()) + }); } TokenKind::TemplateNoSubstitution { .. } | TokenKind::TemplateMiddle { .. } => { - lhs = TaggedTemplateLiteral::new( + let tagged_literal = TaggedTemplateLiteral::new( self.allow_yield, self.allow_await, tok.start_group(), lhs, ) - .parse(cursor, interner)? - .into(); + .parse(cursor, interner)?; + lhs = ast::Expression::boxed(|| tagged_literal.into()); } _ => break, } diff --git a/core/parser/src/parser/expression/left_hand_side/member.rs b/core/parser/src/parser/expression/left_hand_side/member.rs index 9b98065de5e..fa9b3212fc5 100644 --- a/core/parser/src/parser/expression/left_hand_side/member.rs +++ b/core/parser/src/parser/expression/left_hand_side/member.rs @@ -64,13 +64,46 @@ where type Output = ast::Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("MemberExpression", "Parsing"); + let lhs = match self.parse_boxed_1st_token_except_prim(cursor, interner)? { + Some(expr) => expr, + None => PrimaryExpression::new(self.allow_yield, self.allow_await) + .parse_boxed(cursor, interner)?, + }; + + self.parse_boxed_tail(cursor, interner, lhs) + } +} + +impl MemberExpression { + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + /// + /// # Return + /// * `Err(_)` if error occurs; + /// * `Ok(None)` if next expression is `PrimaryExpression`; + /// * `Ok(Some(Box))` otherwise; + fn parse_boxed_1st_token_except_prim( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult>> { cursor.set_goal(InputElement::RegExp); let token = cursor.peek(0, interner).or_abrupt()?; let position = token.span().start(); - let mut lhs = match token.kind() { + let lhs = match token.kind() { TokenKind::Keyword((Keyword::New | Keyword::Super | Keyword::Import, true)) => { return Err(Error::general( "keyword must not contain escaped characters", @@ -114,7 +147,7 @@ where )); } - ast::Expression::ImportMeta + ast::Expression::boxed(|| ast::Expression::ImportMeta) } TokenKind::Keyword((Keyword::New, false)) => { cursor.advance(interner); @@ -129,7 +162,7 @@ where )); } TokenKind::IdentifierName((Sym::TARGET, ContainsEscapeSequence(false))) => { - ast::Expression::NewTarget + ast::Expression::boxed(|| ast::Expression::NewTarget) } _ => { return Err(Error::general( @@ -139,7 +172,7 @@ where } } } else { - let lhs_inner = self.parse(cursor, interner)?; + let lhs_inner = self.parse_boxed(cursor, interner)?; let args = match cursor.peek(0, interner)? { Some(next) if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) => @@ -149,9 +182,9 @@ where } _ => Box::new([]), }; - let call_node = Call::new(lhs_inner, args); + let call_node = Call::new_boxed(lhs_inner, args); - ast::Expression::from(New::from(call_node)) + ast::Expression::boxed(|| ast::Expression::from(New::from(call_node))) }; lhs_new_target } @@ -189,15 +222,17 @@ where )); } }; - ast::Expression::PropertyAccess(field.into()) + ast::Expression::boxed(|| ast::Expression::PropertyAccess(field.into())) } TokenKind::Punctuator(Punctuator::OpenBracket) => { let expr = Expression::new(true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; cursor.expect(Punctuator::CloseBracket, "super property", interner)?; - ast::Expression::PropertyAccess( - SuperPropertyAccess::new(expr.into()).into(), - ) + ast::Expression::boxed(|| { + ast::Expression::PropertyAccess( + SuperPropertyAccess::new(expr.into()).into(), + ) + }) } _ => { return Err(Error::unexpected( @@ -208,10 +243,22 @@ where } } } - _ => PrimaryExpression::new(self.allow_yield, self.allow_await) - .parse(cursor, interner)?, + _ => return Ok(None), }; + Ok(Some(lhs)) + } + + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_boxed_tail( + self, + cursor: &mut Cursor, + interner: &mut Interner, + mut lhs: Box, + ) -> ParseResult> { cursor.set_goal(InputElement::TemplateTail); while let Some(tok) = cursor.peek(0, interner)? { @@ -252,17 +299,18 @@ where } }; - lhs = ast::Expression::PropertyAccess(access); + lhs = ast::Expression::boxed(|| ast::Expression::PropertyAccess(access)); } TokenKind::Punctuator(Punctuator::OpenBracket) => { cursor .next(interner)? .expect("open bracket punctuator token disappeared"); // We move the parser forward. let idx = Expression::new(true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; cursor.expect(Punctuator::CloseBracket, "member expression", interner)?; - lhs = - ast::Expression::PropertyAccess(SimplePropertyAccess::new(lhs, idx).into()); + lhs = ast::Expression::boxed(|| { + ast::Expression::PropertyAccess(SimplePropertyAccess::new(lhs, idx).into()) + }); } TokenKind::TemplateNoSubstitution { .. } | TokenKind::TemplateMiddle { .. } => { lhs = TaggedTemplateLiteral::new( @@ -271,7 +319,7 @@ where tok.start_group(), lhs, ) - .parse(cursor, interner)? + .parse_boxed(cursor, interner)? .into(); } _ => break, diff --git a/core/parser/src/parser/expression/left_hand_side/mod.rs b/core/parser/src/parser/expression/left_hand_side/mod.rs index 18046fd6382..f1de45ca74d 100644 --- a/core/parser/src/parser/expression/left_hand_side/mod.rs +++ b/core/parser/src/parser/expression/left_hand_side/mod.rs @@ -75,85 +75,131 @@ where type Output = Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - /// Checks if we need to parse a keyword call expression `keyword()`. - /// - /// It first checks if the next token is `keyword`, and if it is, it checks if the second next - /// token is the open parenthesis (`(`) punctuator. - /// - /// This is needed because the `if let` chain is very complex, and putting it inline in the - /// initialization of `lhs` would make it very hard to return an expression over all - /// possible branches of the `if let`s. Instead, we extract the check into its own function, - /// then use it inside the condition of a simple `if ... else` expression. - fn is_keyword_call( - keyword: Keyword, - cursor: &mut Cursor, - interner: &mut Interner, - ) -> ParseResult { - if let Some(next) = cursor.peek(0, interner)? { - if let TokenKind::Keyword((kw, escaped)) = next.kind() { - if kw == &keyword { - if *escaped { - return Err(Error::general( - format!( - "keyword `{}` cannot contain escaped characters", - kw.as_str().0 - ), - next.span().start(), - )); - } - if let Some(next) = cursor.peek(1, interner)? { - if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) { - return Ok(true); - } - } - } - } - } - Ok(false) - } + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("LeftHandSideExpression", "Parsing"); cursor.set_goal(InputElement::TemplateTail); - let mut lhs = if is_keyword_call(Keyword::Super, cursor, interner)? { - cursor.advance(interner); - let args = - Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; - SuperCall::new(args).into() - } else if is_keyword_call(Keyword::Import, cursor, interner)? { - // `import` - cursor.advance(interner); - // `(` - cursor.advance(interner); - - let arg = AssignmentExpression::new(true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; - - cursor.expect( - TokenKind::Punctuator(Punctuator::CloseParen), - "import call", - interner, - )?; - - CallExpressionTail::new( - self.allow_yield, - self.allow_await, - ImportCall::new(arg).into(), - ) - .parse(cursor, interner)? + let lhs = if let Some(lhs) = self.parse_boxed_special_kws(cursor, interner)? { + lhs } else { let mut member = MemberExpression::new(self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; if let Some(tok) = cursor.peek(0, interner)? { if tok.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) { member = CallExpression::new(self.allow_yield, self.allow_await, member) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; } } member }; + self.parse_boxed_tail(cursor, interner, lhs) + } +} + +impl LeftHandSideExpression { + /// Checks if we need to parse a keyword call expression `keyword()`. + /// + /// It first checks if the next token is `keyword`, and if it is, it checks if the second next + /// token is the open parenthesis (`(`) punctuator. + /// + /// This is needed because the `if let` chain is very complex, and putting it inline in the + /// initialization of `lhs` would make it very hard to return an expression over all + /// possible branches of the `if let`s. Instead, we extract the check into its own function, + /// then use it inside the condition of a simple `if ... else` expression. + fn is_keyword_call( + keyword: Keyword, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult { + if let Some(next) = cursor.peek(0, interner)? { + if let TokenKind::Keyword((kw, escaped)) = next.kind() { + if kw == &keyword { + if *escaped { + return Err(Error::general( + format!( + "keyword `{}` cannot contain escaped characters", + kw.as_str().0 + ), + next.span().start(), + )); + } + if let Some(next) = cursor.peek(1, interner)? { + if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) { + return Ok(true); + } + } + } + } + } + Ok(false) + } + + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + /// + /// # Return + /// * `Err(_)` if error occurs; + /// * `Ok(None)` if next expression is not `Keyword((Super, _)) || Keyword((Import, _))`; + /// * `Ok(Some(Box))` otherwise; + fn parse_boxed_special_kws( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult>> { + Ok(Some( + if Self::is_keyword_call(Keyword::Super, cursor, interner)? { + cursor.advance(interner); + let args = + Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; + SuperCall::new(args).into() + } else if Self::is_keyword_call(Keyword::Import, cursor, interner)? { + // `import` + cursor.advance(interner); + // `(` + cursor.advance(interner); + + let arg = AssignmentExpression::new(true, self.allow_yield, self.allow_await) + .parse_boxed(cursor, interner)?; + + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseParen), + "import call", + interner, + )?; + + CallExpressionTail::new( + self.allow_yield, + self.allow_await, + ImportCall::new(arg).into(), + ) + .parse_boxed(cursor, interner)? + } else { + return Ok(None); + }, + )) + } + + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_boxed_tail( + self, + cursor: &mut Cursor, + interner: &mut Interner, + mut lhs: Box, + ) -> ParseResult> { if let Some(tok) = cursor.peek(0, interner)? { if tok.kind() == &TokenKind::Punctuator(Punctuator::Optional) { lhs = OptionalExpression::new(self.allow_yield, self.allow_await, lhs) diff --git a/core/parser/src/parser/expression/left_hand_side/optional/mod.rs b/core/parser/src/parser/expression/left_hand_side/optional/mod.rs index adb88f90c13..3af853a608d 100644 --- a/core/parser/src/parser/expression/left_hand_side/optional/mod.rs +++ b/core/parser/src/parser/expression/left_hand_side/optional/mod.rs @@ -31,7 +31,7 @@ use boa_profiler::Profiler; pub(in crate::parser) struct OptionalExpression { allow_yield: AllowYield, allow_await: AllowAwait, - target: ast::Expression, + target: Box, } impl OptionalExpression { @@ -39,7 +39,7 @@ impl OptionalExpression { pub(in crate::parser) fn new( allow_yield: Y, allow_await: A, - target: ast::Expression, + target: Box, ) -> Self where Y: Into, diff --git a/core/parser/src/parser/expression/left_hand_side/optional/tests.rs b/core/parser/src/parser/expression/left_hand_side/optional/tests.rs index cf640e49a84..0ac2bdbba3a 100644 --- a/core/parser/src/parser/expression/left_hand_side/optional/tests.rs +++ b/core/parser/src/parser/expression/left_hand_side/optional/tests.rs @@ -18,7 +18,7 @@ fn simple() { r#"5?.name"#, vec![Statement::Expression( Optional::new( - Literal::Int(5).into(), + Box::new(Literal::Int(5).into()), vec![OptionalOperation::new( OptionalOperationKind::SimplePropertyAccess { field: PropertyAccessField::Const( diff --git a/core/parser/src/parser/expression/left_hand_side/template.rs b/core/parser/src/parser/expression/left_hand_side/template.rs index 50dca50f6b9..53856b0c5c4 100644 --- a/core/parser/src/parser/expression/left_hand_side/template.rs +++ b/core/parser/src/parser/expression/left_hand_side/template.rs @@ -22,7 +22,7 @@ pub(super) struct TaggedTemplateLiteral { allow_yield: AllowYield, allow_await: AllowAwait, start: PositionGroup, - tag: ast::Expression, + tag: Box, } impl TaggedTemplateLiteral { @@ -31,7 +31,7 @@ impl TaggedTemplateLiteral { allow_yield: Y, allow_await: A, start: PositionGroup, - tag: ast::Expression, + tag: Box, ) -> Self where Y: Into, diff --git a/core/parser/src/parser/expression/mod.rs b/core/parser/src/parser/expression/mod.rs index fe5cbb70ef9..83f956e660f 100644 --- a/core/parser/src/parser/expression/mod.rs +++ b/core/parser/src/parser/expression/mod.rs @@ -70,7 +70,7 @@ pub(in crate::parser) use { /// /// The fifth parameter is an `Option` which sets the goal symbol to set before parsing (or None to leave it as is). macro_rules! expression { - ($name:ident, $lower:ident, [$( $op:path ),*], [$( $low_param:ident ),*], $goal:expr ) => { + ($name:ident, $lower:ident, [$( $op:path ),*], [$( $low_param:ident ),*], $($goal:expr)? ) => { impl TokenParser for $name where R: ReadChar @@ -78,31 +78,61 @@ macro_rules! expression { type Output = ast::Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner)-> ParseResult { - let _timer = Profiler::global().start_event(stringify!($name), "Parsing"); + self.parse_boxed(cursor, interner).map(|ok|*ok) + } - if $goal.is_some() { - cursor.set_goal($goal.unwrap()); + fn parse_boxed(self, cursor: &mut Cursor, interner: &mut Interner)-> ParseResult> { + let mut lhs; + expression!([PREFIX][cursor] $name $(, $goal)?); + expression!([SUBCALL][self; cursor; interner; lhs] $lower, [$($low_param),*]); + expression!([POSTFIX][self; cursor; interner; lhs] $lower, [$($op),*], [$($low_param),*]) + } + } + }; + ([PREFIX][$cursor:ident] $name:ident $(, $goal:expr)? ) => {{ + let _timer = Profiler::global().start_event(stringify!($name), "Parsing"); + $($cursor.set_goal($goal);)? + }}; + ([LOWER_CTOR][$self:ident] $lower:ident, [$( $low_param:ident ),*] ) => { + $lower::new($( $self.$low_param ),*) + }; + ([SUBCALL][$self:ident; $cursor:ident; $interner:ident; $lhs:ident] $lower:ident, [$( $low_param:ident ),*] ) => {{ + $lhs = expression!([LOWER_CTOR][$self] $lower, [$($low_param),*]).parse_boxed($cursor, $interner)?; + }}; + ([POSTFIX][$self:ident; $cursor:ident; $interner:ident; $lhs:ident] $lower:ident, [$( $op:path ),*], [$( $low_param:ident ),*]) => {{ + while let Some(tok) = $cursor.peek(0, $interner)? { + match *tok.kind() { + TokenKind::Punctuator(op) if $( op == $op )||* => { + $cursor.advance($interner); + $lhs = Binary::new_boxed_expr( + op.as_binary_op().expect("Could not get binary operation."), + $lhs, + $lower::new($( $self.$low_param ),*).parse_boxed($cursor, $interner)? + ); } + _ => break + } + } - let mut lhs = $lower::new($( self.$low_param ),*).parse(cursor, interner)?; - while let Some(tok) = cursor.peek(0, interner)? { - match *tok.kind() { - TokenKind::Punctuator(op) if $( op == $op )||* => { - cursor.advance(interner); - lhs = Binary::new( - op.as_binary_op().expect("Could not get binary operation."), - lhs, - $lower::new($( self.$low_param ),*).parse(cursor, interner)? - ).into(); - } - _ => break - } + Ok($lhs) + }}; + ([POSTFIX][$self:ident; $cursor:ident; $interner:ident; $lhs:ident] $lower:ident, [$( $op:path ),*]) => {{ + while let Some(tok) = $cursor.peek(0, $interner)? { + match *tok.kind() { + TokenKind::Punctuator(op) if $( op == $op )||* => { + $cursor.advance($interner); + $lhs = Binary::new_boxed_expr( + op.as_binary_op().expect("Could not get binary operation."), + $lhs, + $lower.parse_boxed($cursor, $interner)? + ); } - - Ok(lhs) + _ => break } } - }; + + $lhs + }}; } /// Expression parsing. @@ -141,12 +171,34 @@ where R: ReadChar, { type Output = ast::Expression; - fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("Expression", "Parsing"); - let mut lhs = AssignmentExpression::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + let lhs = AssignmentExpression::new(self.allow_in, self.allow_yield, self.allow_await) + .parse_boxed(cursor, interner)?; + + self.parse_boxed_tail(cursor, interner, lhs) + } +} +impl Expression { + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_boxed_tail( + self, + cursor: &mut Cursor, + interner: &mut Interner, + mut lhs: Box, + ) -> ParseResult> { while let Some(tok) = cursor.peek(0, interner)? { match *tok.kind() { TokenKind::Punctuator(Punctuator::Comma) => { @@ -164,7 +216,7 @@ where cursor.advance(interner); - lhs = Binary::new( + lhs = Binary::new_boxed_expr( Punctuator::Comma .as_binary_op() .expect("Could not get binary operation."), @@ -174,9 +226,8 @@ where self.allow_yield, self.allow_await, ) - .parse(cursor, interner)?, - ) - .into(); + .parse_boxed(cursor, interner)?, + ); } _ => break, } @@ -252,11 +303,34 @@ where type Output = ast::Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("ShortCircuitExpression", "Parsing"); - let mut current_node = + let current_node = BitwiseORExpression::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; + + self.parse_boxed_tail(cursor, interner, current_node) + } +} +impl ShortCircuitExpression { + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_boxed_tail( + self, + cursor: &mut Cursor, + interner: &mut Interner, + mut current_node: Box, + ) -> ParseResult> { let mut previous = self.previous; while let Some(tok) = cursor.peek(0, interner)? { @@ -273,10 +347,13 @@ where previous = PreviousExpr::Logical; let rhs = BitwiseORExpression::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; - current_node = - Binary::new(BinaryOp::Logical(LogicalOp::And), current_node, rhs).into(); + current_node = Binary::new_boxed_expr( + BinaryOp::Logical(LogicalOp::And), + current_node, + rhs, + ); } TokenKind::Punctuator(Punctuator::BoolOr) => { if previous == PreviousExpr::Coalesce { @@ -294,9 +371,9 @@ where self.allow_await, PreviousExpr::Logical, ) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; current_node = - Binary::new(BinaryOp::Logical(LogicalOp::Or), current_node, rhs).into(); + Binary::new_boxed_expr(BinaryOp::Logical(LogicalOp::Or), current_node, rhs); } TokenKind::Punctuator(Punctuator::Coalesce) => { if previous == PreviousExpr::Logical { @@ -311,10 +388,12 @@ where previous = PreviousExpr::Coalesce; let rhs = BitwiseORExpression::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; - current_node = - Binary::new(BinaryOp::Logical(LogicalOp::Coalesce), current_node, rhs) - .into(); + .parse_boxed(cursor, interner)?; + current_node = Binary::new_boxed_expr( + BinaryOp::Logical(LogicalOp::Coalesce), + current_node, + rhs, + ); } _ => break, } @@ -354,13 +433,65 @@ impl BitwiseORExpression { } } -expression!( - BitwiseORExpression, - BitwiseXORExpression, - [Punctuator::Or], - [allow_in, allow_yield, allow_await], - None:: -); +impl TokenParser for BitwiseORExpression +where + R: ReadChar, +{ + type Output = ast::Expression; + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { + // TODO: recursive `expression!` + // + // unwrapping of + // ``` + // expression!( + // BitwiseORExpression, + // BitwiseXORExpression, + // [Punctuator::Or], + // [allow_in, allow_yield, allow_await], + // ); + // ``` + // with subcall inlining to reduce stack allocation (from X * 4 to X) + let mut lhs: Box; + expression!([PREFIX][cursor] BitwiseORExpression); + let lower = expression!([LOWER_CTOR][self] BitwiseXORExpression, [allow_in, allow_yield, allow_await]); + lhs = { + expression!([PREFIX][cursor] BitwiseXORExpression); + let lower = expression!([LOWER_CTOR][self] BitwiseANDExpression, [allow_in, allow_yield, allow_await]); + lhs = { + expression!([PREFIX][cursor] BitwiseANDExpression); + let lower = expression!([LOWER_CTOR][self] EqualityExpression, [allow_in, allow_yield, allow_await]); + lhs = { + expression!([PREFIX][cursor] EqualityExpression); + let lower = expression!([LOWER_CTOR][self] RelationalExpression, [allow_in, allow_yield, allow_await]); + lhs = lower.parse_boxed(cursor, interner)?; + expression!( + [POSTFIX] + [self; cursor; interner; lhs] + lower, + [Punctuator::Eq, Punctuator::NotEq, Punctuator::StrictEq, Punctuator::StrictNotEq] + ) + }; + expression!([POSTFIX][self; cursor; interner; lhs] lower, [Punctuator::And]) + }; + expression!([POSTFIX][self; cursor; interner; lhs] lower, [Punctuator::Xor]) + }; + lhs = expression!([POSTFIX][self; cursor; interner; lhs] lower, [Punctuator::Or]); + Ok(lhs) + } +} /// Parses a bitwise `XOR` expression. /// @@ -398,7 +529,6 @@ expression!( BitwiseANDExpression, [Punctuator::Xor], [allow_in, allow_yield, allow_await], - None:: ); /// Parses a bitwise `AND` expression. @@ -437,7 +567,6 @@ expression!( EqualityExpression, [Punctuator::And], [allow_in, allow_yield, allow_await], - None:: ); /// Parses an equality expression. @@ -481,7 +610,6 @@ expression!( Punctuator::StrictNotEq ], [allow_in, allow_yield, allow_await], - None:: ); /// Parses a relational expression. @@ -521,9 +649,47 @@ where { type Output = ast::Expression; - fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("Relation Expression", "Parsing"); + if let Some(ok) = self.parse_boxed_private_name(cursor, interner)? { + return Ok(ok); + } + + let lhs = ShiftExpression::new(self.allow_yield, self.allow_await) + .parse_boxed(cursor, interner)?; + + self.parse_boxed_tail(cursor, interner, lhs) + } +} + +impl RelationalExpression { + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + /// + /// # Return + /// * `Err(_)` if error occurs; + /// * `Ok(Some(Box))` if the next expression is `BinaryInPrivate`; + /// * `Ok(None)` otherwise; + fn parse_boxed_private_name( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult>> { if self.allow_in.0 { let token = cursor.peek(0, interner).or_abrupt()?; if let TokenKind::PrivateIdentifier(identifier) = token.kind() { @@ -541,18 +707,29 @@ where cursor.advance(interner); let rhs = ShiftExpression::new(self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; - return Ok(BinaryInPrivate::new(PrivateName::new(identifier), rhs).into()); + return Ok(Some( + BinaryInPrivate::new_boxed(PrivateName::new(identifier), rhs).into(), + )); } _ => {} } } } + Ok(None) + } - let mut lhs = - ShiftExpression::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; - + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_boxed_tail( + self, + cursor: &mut Cursor, + interner: &mut Interner, + mut lhs: Box, + ) -> ParseResult> { while let Some(tok) = cursor.peek(0, interner)? { match *tok.kind() { TokenKind::Punctuator(op) @@ -562,11 +739,11 @@ where || op == Punctuator::GreaterThanOrEq => { cursor.advance(interner); - lhs = Binary::new( + lhs = Binary::new_boxed( op.as_binary_op().expect("Could not get binary operation."), lhs, ShiftExpression::new(self.allow_yield, self.allow_await) - .parse(cursor, interner)?, + .parse_boxed(cursor, interner)?, ) .into(); } @@ -581,11 +758,11 @@ where || (op == Keyword::In && self.allow_in == AllowIn(true)) => { cursor.advance(interner); - lhs = Binary::new( + lhs = Binary::new_boxed( op.as_binary_op().expect("Could not get binary operation."), lhs, ShiftExpression::new(self.allow_yield, self.allow_await) - .parse(cursor, interner)?, + .parse_boxed(cursor, interner)?, ) .into(); } @@ -625,17 +802,59 @@ impl ShiftExpression { } } -expression!( - ShiftExpression, - AdditiveExpression, - [ - Punctuator::LeftSh, - Punctuator::RightSh, - Punctuator::URightSh - ], - [allow_yield, allow_await], - None:: -); +impl TokenParser for ShiftExpression +where + R: ReadChar, +{ + type Output = ast::Expression; + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { + // TODO: recursive `expression!` + // + // unwrapping of + // ``` + // expression!( + // ShiftExpression, + // AdditiveExpression, + // [ + // Punctuator::LeftSh, + // Punctuator::RightSh, + // Punctuator::URightSh + // ], + // [allow_yield, allow_await], + // ); + // ``` + // with subcall inlining to reduce stack allocation (from X * 3 to X) + let mut lhs: Box; + expression!([PREFIX][cursor] ShiftExpression); + let lower = expression!([LOWER_CTOR][self] AdditiveExpression, [allow_yield, allow_await]); + lhs = { + expression!([PREFIX][cursor] AdditiveExpression); + let lower = expression!([LOWER_CTOR][self] MultiplicativeExpression, [allow_yield, allow_await]); + lhs = { + expression!([PREFIX][cursor] MultiplicativeExpression, InputElement::Div); + let lower = expression!([LOWER_CTOR][self] ExponentiationExpression, [allow_yield, allow_await]); + lhs = lower.parse_boxed(cursor, interner)?; + expression!([POSTFIX][self; cursor; interner; lhs] lower, [Punctuator::Mul, Punctuator::Div, Punctuator::Mod]) + }; + expression!([POSTFIX][self; cursor; interner; lhs] lower, [Punctuator::Add, Punctuator::Sub]) + }; + lhs = expression!([POSTFIX][self; cursor; interner; lhs] lower, [Punctuator::LeftSh, Punctuator::RightSh, Punctuator::URightSh]); + Ok(lhs) + } +} /// Parses an additive expression. /// @@ -672,7 +891,6 @@ expression!( MultiplicativeExpression, [Punctuator::Add, Punctuator::Sub], [allow_yield, allow_await], - None:: ); /// Parses a multiplicative expression. @@ -710,7 +928,7 @@ expression!( ExponentiationExpression, [Punctuator::Mul, Punctuator::Div, Punctuator::Mod], [allow_yield, allow_await], - Some(InputElement::Div) + InputElement::Div ); /// Returns an error if `arguments` or `eval` are used as identifier in strict mode. diff --git a/core/parser/src/parser/expression/primary/async_function_expression/mod.rs b/core/parser/src/parser/expression/primary/async_function_expression/mod.rs index 4ac2ce2a286..6a84bf54f2d 100644 --- a/core/parser/src/parser/expression/primary/async_function_expression/mod.rs +++ b/core/parser/src/parser/expression/primary/async_function_expression/mod.rs @@ -44,6 +44,14 @@ where type Output = AsyncFunctionExpressionNode; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("AsyncFunctionExpression", "Parsing"); let token = cursor.expect( (Keyword::Async, false), @@ -148,9 +156,10 @@ where let span = start_linear_span.union(body.linear_pos_end()); - let function = AsyncFunctionExpressionNode::new(name, params, body, span, name.is_some()); + let function = + AsyncFunctionExpressionNode::new_boxed(name, params, body, span, name.is_some()); - if contains(&function, ContainsSymbol::Super) { + if contains(function.as_ref(), ContainsSymbol::Super) { return Err(Error::lex(LexError::Syntax( "invalid super usage".into(), params_start_position, diff --git a/core/parser/src/parser/expression/primary/async_function_expression/tests.rs b/core/parser/src/parser/expression/primary/async_function_expression/tests.rs index 3d0b09a8266..98e27e9701e 100644 --- a/core/parser/src/parser/expression/primary/async_function_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/async_function_expression/tests.rs @@ -27,7 +27,7 @@ fn check_async_expression() { vec![Variable::from_identifier( add.into(), Some( - AsyncFunctionExpression::new( + AsyncFunctionExpression::new_boxed( Some(add.into()), FormalParameterList::default(), FunctionBody::new( @@ -67,7 +67,7 @@ fn check_nested_async_expression() { vec![Variable::from_identifier( a.into(), Some( - AsyncFunctionExpression::new( + AsyncFunctionExpression::new_boxed( Some(a.into()), FormalParameterList::default(), FunctionBody::new( @@ -75,7 +75,7 @@ fn check_nested_async_expression() { vec![Variable::from_identifier( b.into(), Some( - AsyncFunctionExpression::new( + AsyncFunctionExpression::new_boxed( Some(b.into()), FormalParameterList::default(), FunctionBody::new( diff --git a/core/parser/src/parser/expression/primary/async_generator_expression/mod.rs b/core/parser/src/parser/expression/primary/async_generator_expression/mod.rs index cc55d878228..6d8ea5bd7e6 100644 --- a/core/parser/src/parser/expression/primary/async_generator_expression/mod.rs +++ b/core/parser/src/parser/expression/primary/async_generator_expression/mod.rs @@ -52,6 +52,14 @@ where type Output = AsyncGeneratorExpressionNode; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("AsyncGeneratorExpression", "Parsing"); let token = cursor.expect( (Keyword::Async, false), @@ -184,9 +192,10 @@ where let span = start_linear_span.union(body.linear_pos_end()); - let function = AsyncGeneratorExpressionNode::new(name, params, body, span, name.is_some()); + let function = + AsyncGeneratorExpressionNode::new_boxed(name, params, body, span, name.is_some()); - if contains(&function, ContainsSymbol::Super) { + if contains(function.as_ref(), ContainsSymbol::Super) { return Err(Error::lex(LexError::Syntax( "invalid super usage".into(), params_start_position, diff --git a/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs b/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs index 86b3011910b..990298a7dc5 100644 --- a/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs @@ -28,7 +28,7 @@ fn check_async_generator_expr() { vec![Variable::from_identifier( add.into(), Some( - AsyncGeneratorExpression::new( + AsyncGeneratorExpression::new_boxed( Some(add.into()), FormalParameterList::default(), FunctionBody::new( @@ -68,7 +68,7 @@ fn check_nested_async_generator_expr() { vec![Variable::from_identifier( a.into(), Some( - AsyncGeneratorExpression::new( + AsyncGeneratorExpression::new_boxed( Some(a.into()), FormalParameterList::default(), FunctionBody::new( @@ -76,7 +76,7 @@ fn check_nested_async_generator_expr() { vec![Variable::from_identifier( b.into(), Some( - AsyncGeneratorExpression::new( + AsyncGeneratorExpression::new_boxed( Some(b.into()), FormalParameterList::default(), FunctionBody::new( diff --git a/core/parser/src/parser/expression/primary/function_expression/mod.rs b/core/parser/src/parser/expression/primary/function_expression/mod.rs index d285e47e67f..984fda2c669 100644 --- a/core/parser/src/parser/expression/primary/function_expression/mod.rs +++ b/core/parser/src/parser/expression/primary/function_expression/mod.rs @@ -53,6 +53,14 @@ where type Output = FunctionExpressionNode; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("FunctionExpression", "Parsing"); let token = cursor.expect((Keyword::Function, false), "generator expression", interner)?; @@ -140,9 +148,9 @@ where let span = Some(start_linear_span.union(body.linear_pos_end())); - let function = FunctionExpressionNode::new(name, params, body, span, name.is_some()); + let function = FunctionExpressionNode::new_boxed(name, params, body, span, name.is_some()); - if contains(&function, ContainsSymbol::Super) { + if contains(function.as_ref(), ContainsSymbol::Super) { return Err(Error::lex(LexError::Syntax( "invalid super usage".into(), params_start_position, diff --git a/core/parser/src/parser/expression/primary/function_expression/tests.rs b/core/parser/src/parser/expression/primary/function_expression/tests.rs index fa79619982d..3f073081064 100644 --- a/core/parser/src/parser/expression/primary/function_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/function_expression/tests.rs @@ -25,7 +25,7 @@ fn check_function_expression() { vec![Variable::from_identifier( add.into(), Some( - FunctionExpression::new( + FunctionExpression::new_boxed( Some(add.into()), FormalParameterList::default(), FunctionBody::new( @@ -65,7 +65,7 @@ fn check_nested_function_expression() { vec![Variable::from_identifier( a.into(), Some( - FunctionExpression::new( + FunctionExpression::new_boxed( Some(a.into()), FormalParameterList::default(), FunctionBody::new( @@ -73,7 +73,7 @@ fn check_nested_function_expression() { vec![Variable::from_identifier( b.into(), Some( - FunctionExpression::new( + FunctionExpression::new_boxed( Some(b.into()), FormalParameterList::default(), FunctionBody::new( @@ -118,7 +118,7 @@ fn check_function_non_reserved_keyword() { vec![Variable::from_identifier( $interner.get_or_intern_static("add", utf16!("add")).into(), Some( - FunctionExpression::new( + FunctionExpression::new_boxed( Some($interner.get_or_intern_static($keyword, utf16!($keyword)).into()), FormalParameterList::default(), FunctionBody::new( diff --git a/core/parser/src/parser/expression/primary/generator_expression/mod.rs b/core/parser/src/parser/expression/primary/generator_expression/mod.rs index 454d69c6501..09f5c3e24e5 100644 --- a/core/parser/src/parser/expression/primary/generator_expression/mod.rs +++ b/core/parser/src/parser/expression/primary/generator_expression/mod.rs @@ -53,6 +53,14 @@ where type Output = GeneratorExpressionNode; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("GeneratorExpression", "Parsing"); let token = cursor.expect((Keyword::Function, false), "generator expression", interner)?; @@ -156,9 +164,9 @@ where let span = start_linear_span.union(body.linear_pos_end()); - let function = GeneratorExpressionNode::new(name, params, body, span, name.is_some()); + let function = GeneratorExpressionNode::new_boxed(name, params, body, span, name.is_some()); - if contains(&function, ContainsSymbol::Super) { + if contains(function.as_ref(), ContainsSymbol::Super) { return Err(Error::lex(LexError::Syntax( "invalid super usage".into(), params_start_position, diff --git a/core/parser/src/parser/expression/primary/generator_expression/tests.rs b/core/parser/src/parser/expression/primary/generator_expression/tests.rs index 8d3f9e39eeb..93bad2d624b 100644 --- a/core/parser/src/parser/expression/primary/generator_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/generator_expression/tests.rs @@ -25,7 +25,7 @@ fn check_generator_function_expression() { vec![Variable::from_identifier( gen.into(), Some( - GeneratorExpression::new( + GeneratorExpression::new_boxed( Some(gen.into()), FormalParameterList::default(), FunctionBody::new( @@ -62,7 +62,7 @@ fn check_generator_function_delegate_yield_expression() { vec![Variable::from_identifier( gen.into(), Some( - GeneratorExpression::new( + GeneratorExpression::new_boxed( Some(gen.into()), FormalParameterList::default(), FunctionBody::new( diff --git a/core/parser/src/parser/expression/primary/mod.rs b/core/parser/src/parser/expression/primary/mod.rs index cdce6c4f8d8..008f9cd38b9 100644 --- a/core/parser/src/parser/expression/primary/mod.rs +++ b/core/parser/src/parser/expression/primary/mod.rs @@ -118,11 +118,11 @@ where let next_token = cursor.peek(1, interner).or_abrupt()?; if next_token.kind() == &TokenKind::Punctuator(Punctuator::Mul) { GeneratorExpression::new() - .parse(cursor, interner) + .parse_boxed(cursor, interner) .map(Into::into) } else { FunctionExpression::new() - .parse(cursor, interner) + .parse_boxed(cursor, interner) .map(Into::into) } } @@ -154,11 +154,11 @@ where match cursor.peek(2, interner)?.map(Token::kind) { Some(TokenKind::Punctuator(Punctuator::Mul)) => { AsyncGeneratorExpression::new() - .parse(cursor, interner) + .parse_boxed(cursor, interner) .map(Into::into) } _ => AsyncFunctionExpression::new() - .parse(cursor, interner) + .parse_boxed(cursor, interner) .map(Into::into), } } @@ -313,6 +313,22 @@ impl CoverParenthesizedExpressionAndArrowParameterList { } } +#[derive(Debug)] +enum InnerExpression { + Expression(Box), + SpreadObject(Vec), + SpreadArray(Vec), + SpreadBinding(Identifier), +} +impl InnerExpression { + #[allow(clippy::unnecessary_wraps)] + fn parenthesized(expression: &ast::Expression) -> ParseResult { + Ok(ast::Expression::Parenthesized(Parenthesized::new( + expression.clone(), + ))) + } +} + impl TokenParser for CoverParenthesizedExpressionAndArrowParameterList where R: ReadChar, @@ -320,14 +336,6 @@ where type Output = ast::Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - #[derive(Debug)] - enum InnerExpression { - Expression(ast::Expression), - SpreadObject(Vec), - SpreadArray(Vec), - SpreadBinding(Identifier), - } - let _timer = Profiler::global().start_event( "CoverParenthesizedExpressionAndArrowParameterList", "Parsing", @@ -338,8 +346,88 @@ where let mut expressions = Vec::new(); let mut tailing_comma = None; + let span = if let Some(span) = + self.parse_special_initial_expr(cursor, interner, &mut expressions)? + { + span + } else { + let expression = Expression::new(true, self.allow_yield, self.allow_await) + .parse_boxed(cursor, interner)?; + expressions.push(InnerExpression::Expression(expression)); + + self.parse_non_special_expr_tail( + cursor, + interner, + &mut expressions, + &mut tailing_comma, + )? + }; + + let is_arrow = if cursor.peek(0, interner)?.map(Token::kind) + == Some(&TokenKind::Punctuator(Punctuator::Arrow)) + { + !cursor.peek_is_line_terminator(0, interner).or_abrupt()? + } else { + false + }; + + // If the next token is not an arrow, we know that we must parse a parenthesized expression. + if !is_arrow { + if let Some(span) = tailing_comma { + return Err(Error::unexpected( + Punctuator::Comma, + span, + "trailing comma in parenthesized expression", + )); + } + if expressions.is_empty() { + return Err(Error::unexpected( + Punctuator::CloseParen, + span, + "empty parenthesized expression", + )); + } + if expressions.len() != 1 { + return Err(Error::unexpected( + Punctuator::CloseParen, + span, + "multiple expressions in parenthesized expression", + )); + } + if let InnerExpression::Expression(expression) = &expressions[0] { + return InnerExpression::parenthesized(expression); + } + return Err(Error::unexpected( + Punctuator::CloseParen, + span, + "parenthesized expression with spread expressions", + )); + } + + // We know that we must parse an arrow function. + // We parse the expressions in to a parameter list. + formal_parameter_list_ctor(expressions, start_span, tailing_comma, cursor.strict()) + } +} + +impl CoverParenthesizedExpressionAndArrowParameterList { + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse`, + /// and an often called function in recursion stays outside of this function. + /// + /// # Return + /// * `Err(_)` if error occurs; + /// * `Ok(Some(_))` if next expr is `TokenKind::Punctuator(CloseParen) || TokenKind::Punctuator(Spread)`; + /// * `Ok(None)` otherwise; + fn parse_special_initial_expr( + self, + cursor: &mut Cursor, + interner: &mut Interner, + expressions: &mut Vec, + ) -> ParseResult> { let next = cursor.peek(0, interner).or_abrupt()?; - let span = match next.kind() { + Ok(Some(match next.kind() { TokenKind::Punctuator(Punctuator::CloseParen) => { let span = next.span(); cursor.advance(interner); @@ -375,79 +463,73 @@ where )? .span() } - _ => { - let expression = Expression::new(true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; - expressions.push(InnerExpression::Expression(expression)); + _ => return Ok(None), + })) + } + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse`, + /// and an often called function in recursion stays outside of this function. + fn parse_non_special_expr_tail( + self, + cursor: &mut Cursor, + interner: &mut Interner, + expressions: &mut Vec, + tailing_comma: &mut Option, + ) -> ParseResult { + let next = cursor.peek(0, interner).or_abrupt()?; + Ok(match next.kind() { + TokenKind::Punctuator(Punctuator::CloseParen) => { + let span = next.span(); + cursor.advance(interner); + span + } + TokenKind::Punctuator(Punctuator::Comma) => { + cursor.advance(interner); let next = cursor.peek(0, interner).or_abrupt()?; match next.kind() { TokenKind::Punctuator(Punctuator::CloseParen) => { let span = next.span(); + *tailing_comma = Some(next.span()); cursor.advance(interner); span } - TokenKind::Punctuator(Punctuator::Comma) => { + TokenKind::Punctuator(Punctuator::Spread) => { cursor.advance(interner); let next = cursor.peek(0, interner).or_abrupt()?; match next.kind() { - TokenKind::Punctuator(Punctuator::CloseParen) => { - let span = next.span(); - tailing_comma = Some(next.span()); - cursor.advance(interner); - span - } - TokenKind::Punctuator(Punctuator::Spread) => { - cursor.advance(interner); - let next = cursor.peek(0, interner).or_abrupt()?; - match next.kind() { - TokenKind::Punctuator(Punctuator::OpenBlock) => { - let bindings = ObjectBindingPattern::new( - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner)?; - expressions.push(InnerExpression::SpreadObject(bindings)); - } - TokenKind::Punctuator(Punctuator::OpenBracket) => { - let bindings = ArrayBindingPattern::new( - self.allow_yield, - self.allow_await, - ) + TokenKind::Punctuator(Punctuator::OpenBlock) => { + let bindings = + ObjectBindingPattern::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; - expressions.push(InnerExpression::SpreadArray(bindings)); - } - _ => { - let binding = BindingIdentifier::new( - self.allow_yield, - self.allow_await, - ) + expressions.push(InnerExpression::SpreadObject(bindings)); + } + TokenKind::Punctuator(Punctuator::OpenBracket) => { + let bindings = + ArrayBindingPattern::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; - expressions.push(InnerExpression::SpreadBinding(binding)); - } - } - - cursor - .expect( - Punctuator::CloseParen, - "CoverParenthesizedExpressionAndArrowParameterList", - interner, - )? - .span() + expressions.push(InnerExpression::SpreadArray(bindings)); } _ => { - return Err(Error::expected( - vec![")".to_owned(), "...".to_owned()], - next.kind().to_string(interner), - next.span(), - "CoverParenthesizedExpressionAndArrowParameterList", - )) + let binding = + BindingIdentifier::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + expressions.push(InnerExpression::SpreadBinding(binding)); } } + + cursor + .expect( + Punctuator::CloseParen, + "CoverParenthesizedExpressionAndArrowParameterList", + interner, + )? + .span() } _ => { return Err(Error::expected( - vec![")".to_owned(), ",".to_owned()], + vec![")".to_owned(), "...".to_owned()], next.kind().to_string(interner), next.span(), "CoverParenthesizedExpressionAndArrowParameterList", @@ -455,104 +537,68 @@ where } } } - }; + _ => { + return Err(Error::expected( + vec![")".to_owned(), ",".to_owned()], + next.kind().to_string(interner), + next.span(), + "CoverParenthesizedExpressionAndArrowParameterList", + )) + } + }) + } +} - let is_arrow = if cursor.peek(0, interner)?.map(Token::kind) - == Some(&TokenKind::Punctuator(Punctuator::Arrow)) - { - !cursor.peek_is_line_terminator(0, interner).or_abrupt()? - } else { - false - }; +fn formal_parameter_list_ctor( + expressions: Vec, + start_span: Span, + tailing_comma: Option, + strict: bool, +) -> ParseResult { + let mut parameters = Vec::new(); - // If the next token is not an arrow, we know that we must parse a parenthesized expression. - if !is_arrow { - if let Some(span) = tailing_comma { - return Err(Error::unexpected( - Punctuator::Comma, - span, - "trailing comma in parenthesized expression", - )); + for expression in expressions { + match expression { + InnerExpression::Expression(node) => { + expression_to_formal_parameters(&node, &mut parameters, strict, start_span)?; } - if expressions.is_empty() { - return Err(Error::unexpected( - Punctuator::CloseParen, - span, - "empty parenthesized expression", - )); + InnerExpression::SpreadObject(bindings) => { + let declaration = Variable::from_pattern(bindings.into(), None); + let parameter = FormalParameter::new(declaration, true); + parameters.push(parameter); } - if expressions.len() != 1 { - return Err(Error::unexpected( - Punctuator::CloseParen, - span, - "multiple expressions in parenthesized expression", - )); - } - if let InnerExpression::Expression(expression) = &expressions[0] { - return Ok(ast::Expression::Parenthesized(Parenthesized::new( - expression.clone(), - ))); + InnerExpression::SpreadArray(bindings) => { + let declaration = Variable::from_pattern(bindings.into(), None); + let parameter = FormalParameter::new(declaration, true); + parameters.push(parameter); } - return Err(Error::unexpected( - Punctuator::CloseParen, - span, - "parenthesized expression with spread expressions", - )); - } - - // We know that we must parse an arrow function. - // We parse the expressions in to a parameter list. - - let mut parameters = Vec::new(); - - for expression in expressions { - match expression { - InnerExpression::Expression(node) => { - expression_to_formal_parameters( - &node, - &mut parameters, - cursor.strict(), - start_span, - )?; - } - InnerExpression::SpreadObject(bindings) => { - let declaration = Variable::from_pattern(bindings.into(), None); - let parameter = FormalParameter::new(declaration, true); - parameters.push(parameter); - } - InnerExpression::SpreadArray(bindings) => { - let declaration = Variable::from_pattern(bindings.into(), None); - let parameter = FormalParameter::new(declaration, true); - parameters.push(parameter); - } - InnerExpression::SpreadBinding(ident) => { - let declaration = Variable::from_identifier(ident, None); - let parameter = FormalParameter::new(declaration, true); - parameters.push(parameter); - } + InnerExpression::SpreadBinding(ident) => { + let declaration = Variable::from_identifier(ident, None); + let parameter = FormalParameter::new(declaration, true); + parameters.push(parameter); } } + } - let parameters = FormalParameterList::from(parameters); - - if let Some(span) = tailing_comma { - if parameters.has_rest_parameter() { - return Err(Error::general( - "rest parameter must be last formal parameter", - span.start(), - )); - } - } + let parameters = FormalParameterList::from(parameters); - if contains(¶meters, ContainsSymbol::YieldExpression) { + if let Some(span) = tailing_comma { + if parameters.has_rest_parameter() { return Err(Error::general( - "yield expression is not allowed in formal parameter list of arrow function", - start_span.start(), + "rest parameter must be last formal parameter", + span.start(), )); } + } - Ok(ast::Expression::FormalParameterList(parameters)) + if contains(¶meters, ContainsSymbol::YieldExpression) { + return Err(Error::general( + "yield expression is not allowed in formal parameter list of arrow function", + start_span.start(), + )); } + + Ok(ast::Expression::FormalParameterList(parameters)) } /// Convert an expression to a formal parameter and append it to the given parameter list. diff --git a/core/parser/src/parser/expression/tests.rs b/core/parser/src/parser/expression/tests.rs index 8f4a2dddcf2..f561d4bc25b 100644 --- a/core/parser/src/parser/expression/tests.rs +++ b/core/parser/src/parser/expression/tests.rs @@ -692,7 +692,7 @@ fn parse_async_arrow_function_named_of() { check_script_parser( "async of => {}", vec![ - Statement::Expression(Expression::from(AsyncArrowFunction::new( + Statement::Expression(Expression::from(AsyncArrowFunction::new_boxed( None, FormalParameterList::from_parameters(vec![FormalParameter::new( Variable::from_identifier( diff --git a/core/parser/src/parser/expression/unary.rs b/core/parser/src/parser/expression/unary.rs index 2d810529820..bb2e203dc11 100644 --- a/core/parser/src/parser/expression/unary.rs +++ b/core/parser/src/parser/expression/unary.rs @@ -61,6 +61,14 @@ where type Output = Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("UnaryExpression", "Parsing"); let tok = cursor.peek(0, interner).or_abrupt()?; @@ -72,7 +80,7 @@ where TokenKind::Keyword((Keyword::Delete, false)) => { cursor.advance(interner); let position = cursor.peek(0, interner).or_abrupt()?.span().start(); - let target = self.parse(cursor, interner)?; + let target = self.parse_boxed(cursor, interner)?; match target.flatten() { Expression::Identifier(_) if cursor.strict() => { @@ -90,31 +98,31 @@ where _ => {} } - Ok(Unary::new(UnaryOp::Delete, target).into()) + Ok(Unary::new_boxed(UnaryOp::Delete, target).into()) } TokenKind::Keyword((Keyword::Void, false)) => { cursor.advance(interner); - Ok(Unary::new(UnaryOp::Void, self.parse(cursor, interner)?).into()) + Ok(Unary::new_boxed(UnaryOp::Void, self.parse_boxed(cursor, interner)?).into()) } TokenKind::Keyword((Keyword::TypeOf, false)) => { cursor.advance(interner); - Ok(Unary::new(UnaryOp::TypeOf, self.parse(cursor, interner)?).into()) + Ok(Unary::new_boxed(UnaryOp::TypeOf, self.parse_boxed(cursor, interner)?).into()) } TokenKind::Punctuator(Punctuator::Add) => { cursor.advance(interner); - Ok(Unary::new(UnaryOp::Plus, self.parse(cursor, interner)?).into()) + Ok(Unary::new_boxed(UnaryOp::Plus, self.parse_boxed(cursor, interner)?).into()) } TokenKind::Punctuator(Punctuator::Sub) => { cursor.advance(interner); - Ok(Unary::new(UnaryOp::Minus, self.parse(cursor, interner)?).into()) + Ok(Unary::new_boxed(UnaryOp::Minus, self.parse_boxed(cursor, interner)?).into()) } TokenKind::Punctuator(Punctuator::Neg) => { cursor.advance(interner); - Ok(Unary::new(UnaryOp::Tilde, self.parse(cursor, interner)?).into()) + Ok(Unary::new_boxed(UnaryOp::Tilde, self.parse_boxed(cursor, interner)?).into()) } TokenKind::Punctuator(Punctuator::Not) => { cursor.advance(interner); - Ok(Unary::new(UnaryOp::Not, self.parse(cursor, interner)?).into()) + Ok(Unary::new_boxed(UnaryOp::Not, self.parse_boxed(cursor, interner)?).into()) } TokenKind::Keyword((Keyword::Await, true)) if self.allow_await.0 => { Err(Error::general( @@ -122,10 +130,11 @@ where token_start, )) } - TokenKind::Keyword((Keyword::Await, false)) if self.allow_await.0 => { - Ok((AwaitExpression::new(self.allow_yield).parse(cursor, interner)?).into()) - } - _ => UpdateExpression::new(self.allow_yield, self.allow_await).parse(cursor, interner), + TokenKind::Keyword((Keyword::Await, false)) if self.allow_await.0 => Ok(Box::new( + (AwaitExpression::new(self.allow_yield).parse(cursor, interner)?).into(), + )), + _ => UpdateExpression::new(self.allow_yield, self.allow_await) + .parse_boxed(cursor, interner), } } } diff --git a/core/parser/src/parser/expression/update.rs b/core/parser/src/parser/expression/update.rs index bfc7b6777ff..2282590f3ea 100644 --- a/core/parser/src/parser/expression/update.rs +++ b/core/parser/src/parser/expression/update.rs @@ -86,55 +86,95 @@ where type Output = Expression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + self.parse_boxed(cursor, interner).map(|ok| *ok) + } + + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { let _timer = Profiler::global().start_event("UpdateExpression", "Parsing"); let tok = cursor.peek(0, interner).or_abrupt()?; let position = tok.span().start(); match tok.kind() { TokenKind::Punctuator(Punctuator::Inc) => { - cursor - .next(interner)? - .expect("Punctuator::Inc token disappeared"); - - let target = UnaryExpression::new(self.allow_yield, self.allow_await) - .parse(cursor, interner)?; - - // https://tc39.es/ecma262/#sec-update-expressions-static-semantics-early-errors - return (as_simple(&target, position, cursor.strict())?).map_or_else( - || { - Err(Error::lex(LexError::Syntax( - "Invalid left-hand side in assignment".into(), - position, - ))) - }, - |target| Ok(Update::new(UpdateOp::IncrementPre, target).into()), - ); + return self.parse_initial_inc_dec_token( + cursor, + interner, + position, + UpdateOp::IncrementPre, + ) } TokenKind::Punctuator(Punctuator::Dec) => { - cursor - .next(interner)? - .expect("Punctuator::Dec token disappeared"); - - let target = UnaryExpression::new(self.allow_yield, self.allow_await) - .parse(cursor, interner)?; - - // https://tc39.es/ecma262/#sec-update-expressions-static-semantics-early-errors - return (as_simple(&target, position, cursor.strict())?).map_or_else( - || { - Err(Error::lex(LexError::Syntax( - "Invalid left-hand side in assignment".into(), - position, - ))) - }, - |target| Ok(Update::new(UpdateOp::DecrementPre, target).into()), - ); + return self.parse_initial_inc_dec_token( + cursor, + interner, + position, + UpdateOp::DecrementPre, + ) } _ => {} } let lhs = LeftHandSideExpression::new(self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + .parse_boxed(cursor, interner)?; + Self::parse_tail(cursor, interner, position, lhs) + } +} + +#[allow(clippy::expect_fun_call)] +impl UpdateExpression { + fn update_expr_ctor( + expr: &Expression, + pos: Position, + err_pos: Position, + op: UpdateOp, + strict: bool, + ) -> ParseResult> { + as_simple(expr, pos, strict)?.map_or_else( + || { + Err(Error::lex(LexError::Syntax( + "Invalid left-hand side in assignment".into(), + err_pos, + ))) + }, + |target| Ok(Box::new(Update::new(op, target).into())), + ) + } + + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_initial_inc_dec_token( + self, + cursor: &mut Cursor, + interner: &mut Interner, + position: Position, + op: UpdateOp, + ) -> ParseResult> { + cursor.next(interner)?.expect(disappeared_err(op.is_inc())); + + let target = UnaryExpression::new(self.allow_yield, self.allow_await) + .parse_boxed(cursor, interner)?; + + // https://tc39.es/ecma262/#sec-update-expressions-static-semantics-early-errors + Self::update_expr_ctor(&target, position, position, op, cursor.strict()) + } + + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse_boxed`, + /// and an often called function in recursion stays outside of this function. + fn parse_tail( + cursor: &mut Cursor, + interner: &mut Interner, + position: Position, + lhs: Box, + ) -> ParseResult> { if cursor.peek_is_line_terminator(0, interner)?.unwrap_or(true) { return Ok(lhs); } @@ -143,35 +183,27 @@ where let token_start = tok.span().start(); match tok.kind() { TokenKind::Punctuator(Punctuator::Inc) => { - cursor - .next(interner)? - .expect("Punctuator::Inc token disappeared"); + cursor.next(interner)?.expect(disappeared_err(true)); // https://tc39.es/ecma262/#sec-update-expressions-static-semantics-early-errors - return (as_simple(&lhs, position, cursor.strict())?).map_or_else( - || { - Err(Error::lex(LexError::Syntax( - "Invalid left-hand side in assignment".into(), - token_start, - ))) - }, - |target| Ok(Update::new(UpdateOp::IncrementPost, target).into()), + return Self::update_expr_ctor( + &lhs, + position, + token_start, + UpdateOp::IncrementPost, + cursor.strict(), ); } TokenKind::Punctuator(Punctuator::Dec) => { - cursor - .next(interner)? - .expect("Punctuator::Dec token disappeared"); + cursor.next(interner)?.expect(disappeared_err(false)); // https://tc39.es/ecma262/#sec-update-expressions-static-semantics-early-errors - return (as_simple(&lhs, position, cursor.strict())?).map_or_else( - || { - Err(Error::lex(LexError::Syntax( - "Invalid left-hand side in assignment".into(), - token_start, - ))) - }, - |target| Ok(Update::new(UpdateOp::DecrementPost, target).into()), + return Self::update_expr_ctor( + &lhs, + position, + token_start, + UpdateOp::DecrementPost, + cursor.strict(), ); } _ => {} @@ -181,3 +213,11 @@ where Ok(lhs) } } + +const fn disappeared_err(is_inc: bool) -> &'static str { + if is_inc { + "Punctuator::Inc token disappeared" + } else { + "Punctuator::Dec token disappeared" + } +} diff --git a/core/parser/src/parser/function/tests.rs b/core/parser/src/parser/function/tests.rs index 554c859835c..3599a98debb 100644 --- a/core/parser/src/parser/function/tests.rs +++ b/core/parser/src/parser/function/tests.rs @@ -234,13 +234,15 @@ fn check_arrow_only_rest() { assert_eq!(params.length(), 0); check_script_parser( "(...a) => {}", - vec![Statement::Expression(Expression::from(ArrowFunction::new( - None, - params, - FunctionBody::default(), - EMPTY_LINEAR_SPAN, - ))) - .into()], + vec![ + Statement::Expression(Expression::from(ArrowFunction::new_boxed( + None, + params, + FunctionBody::default(), + EMPTY_LINEAR_SPAN, + ))) + .into(), + ], interner, ); } @@ -270,13 +272,15 @@ fn check_arrow_rest() { assert_eq!(params.length(), 2); check_script_parser( "(a, b, ...c) => {}", - vec![Statement::Expression(Expression::from(ArrowFunction::new( - None, - params, - FunctionBody::default(), - EMPTY_LINEAR_SPAN, - ))) - .into()], + vec![ + Statement::Expression(Expression::from(ArrowFunction::new_boxed( + None, + params, + FunctionBody::default(), + EMPTY_LINEAR_SPAN, + ))) + .into(), + ], interner, ); } @@ -299,26 +303,30 @@ fn check_arrow() { assert_eq!(params.length(), 2); check_script_parser( "(a, b) => { return a + b; }", - vec![Statement::Expression(Expression::from(ArrowFunction::new( - None, - params, - FunctionBody::new( - [StatementListItem::Statement(Statement::Return( - Return::new(Some( - Binary::new( - ArithmeticOp::Add.into(), - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))).into(), - Identifier::new(interner.get_or_intern_static("b", utf16!("b"))).into(), - ) - .into(), - )), - ))], - PSEUDO_LINEAR_POS, - false, - ), - EMPTY_LINEAR_SPAN, - ))) - .into()], + vec![ + Statement::Expression(Expression::from(ArrowFunction::new_boxed( + None, + params, + FunctionBody::new( + [StatementListItem::Statement(Statement::Return( + Return::new(Some( + Binary::new( + ArithmeticOp::Add.into(), + Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) + .into(), + Identifier::new(interner.get_or_intern_static("b", utf16!("b"))) + .into(), + ) + .into(), + )), + ))], + PSEUDO_LINEAR_POS, + false, + ), + EMPTY_LINEAR_SPAN, + ))) + .into(), + ], interner, ); } @@ -339,26 +347,30 @@ fn check_arrow_semicolon_insertion() { ]); check_script_parser( "(a, b) => { return a + b }", - vec![Statement::Expression(Expression::from(ArrowFunction::new( - None, - params, - FunctionBody::new( - [StatementListItem::Statement(Statement::Return( - Return::new(Some( - Binary::new( - ArithmeticOp::Add.into(), - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))).into(), - Identifier::new(interner.get_or_intern_static("b", utf16!("b"))).into(), - ) - .into(), - )), - ))], - PSEUDO_LINEAR_POS, - false, - ), - EMPTY_LINEAR_SPAN, - ))) - .into()], + vec![ + Statement::Expression(Expression::from(ArrowFunction::new_boxed( + None, + params, + FunctionBody::new( + [StatementListItem::Statement(Statement::Return( + Return::new(Some( + Binary::new( + ArithmeticOp::Add.into(), + Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) + .into(), + Identifier::new(interner.get_or_intern_static("b", utf16!("b"))) + .into(), + ) + .into(), + )), + ))], + PSEUDO_LINEAR_POS, + false, + ), + EMPTY_LINEAR_SPAN, + ))) + .into(), + ], interner, ); } @@ -379,19 +391,21 @@ fn check_arrow_epty_return() { ]); check_script_parser( "(a, b) => { return; }", - vec![Statement::Expression(Expression::from(ArrowFunction::new( - None, - params, - FunctionBody::new( - [StatementListItem::Statement(Statement::Return( - Return::new(None), - ))], - PSEUDO_LINEAR_POS, - false, - ), - EMPTY_LINEAR_SPAN, - ))) - .into()], + vec![ + Statement::Expression(Expression::from(ArrowFunction::new_boxed( + None, + params, + FunctionBody::new( + [StatementListItem::Statement(Statement::Return( + Return::new(None), + ))], + PSEUDO_LINEAR_POS, + false, + ), + EMPTY_LINEAR_SPAN, + ))) + .into(), + ], interner, ); } @@ -412,19 +426,21 @@ fn check_arrow_empty_return_semicolon_insertion() { ]); check_script_parser( "(a, b) => { return }", - vec![Statement::Expression(Expression::from(ArrowFunction::new( - None, - params, - FunctionBody::new( - [StatementListItem::Statement(Statement::Return( - Return::new(None), - ))], - PSEUDO_LINEAR_POS, - false, - ), - EMPTY_LINEAR_SPAN, - ))) - .into()], + vec![ + Statement::Expression(Expression::from(ArrowFunction::new_boxed( + None, + params, + FunctionBody::new( + [StatementListItem::Statement(Statement::Return( + Return::new(None), + ))], + PSEUDO_LINEAR_POS, + false, + ), + EMPTY_LINEAR_SPAN, + ))) + .into(), + ], interner, ); } @@ -444,7 +460,7 @@ fn check_arrow_assignment() { vec![Variable::from_identifier( Identifier::new(interner.get_or_intern_static("foo", utf16!("foo"))), Some( - ArrowFunction::new( + ArrowFunction::new_boxed( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( @@ -487,7 +503,7 @@ fn check_arrow_assignment_nobrackets() { vec![Variable::from_identifier( interner.get_or_intern_static("foo", utf16!("foo")).into(), Some( - ArrowFunction::new( + ArrowFunction::new_boxed( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( @@ -530,7 +546,7 @@ fn check_arrow_assignment_noparenthesis() { vec![Variable::from_identifier( interner.get_or_intern_static("foo", utf16!("foo")).into(), Some( - ArrowFunction::new( + ArrowFunction::new_boxed( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( @@ -573,7 +589,7 @@ fn check_arrow_assignment_noparenthesis_nobrackets() { vec![Variable::from_identifier( Identifier::new(interner.get_or_intern_static("foo", utf16!("foo"))), Some( - ArrowFunction::new( + ArrowFunction::new_boxed( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( @@ -622,7 +638,7 @@ fn check_arrow_assignment_2arg() { vec![Variable::from_identifier( Identifier::new(interner.get_or_intern_static("foo", utf16!("foo"))), Some( - ArrowFunction::new( + ArrowFunction::new_boxed( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( @@ -671,7 +687,7 @@ fn check_arrow_assignment_2arg_nobrackets() { vec![Variable::from_identifier( Identifier::new(interner.get_or_intern_static("foo", utf16!("foo"))), Some( - ArrowFunction::new( + ArrowFunction::new_boxed( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( @@ -724,7 +740,7 @@ fn check_arrow_assignment_3arg() { vec![Variable::from_identifier( Identifier::new(interner.get_or_intern_static("foo", utf16!("foo"))), Some( - ArrowFunction::new( + ArrowFunction::new_boxed( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( @@ -777,7 +793,7 @@ fn check_arrow_assignment_3arg_nobrackets() { vec![Variable::from_identifier( Identifier::new(interner.get_or_intern_static("foo", utf16!("foo"))), Some( - ArrowFunction::new( + ArrowFunction::new_boxed( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( diff --git a/core/parser/src/parser/mod.rs b/core/parser/src/parser/mod.rs index b24b0c111b3..3bd0e49372b 100644 --- a/core/parser/src/parser/mod.rs +++ b/core/parser/src/parser/mod.rs @@ -56,6 +56,19 @@ where /// /// It will fail if the cursor is not placed at the beginning of the expected non-terminal. fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult; + + /// Parses the token stream into boxed result using the current parser. + /// + /// # Errors + /// + /// It will fail if the cursor is not placed at the beginning of the expected non-terminal. + fn parse_boxed( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { + self.parse(cursor, interner).map(Box::new) + } } /// Boolean representing if the parser should allow a `yield` keyword. @@ -582,12 +595,12 @@ fn name_in_lexically_declared_names( /// Trait to reduce boilerplate in the parser. trait OrAbrupt { - /// Will convert an `Ok(None)` to an [`Error::AbruptEnd`] or return the inner type if not. + /// Will convert an `Ok(None)` to an [`crate::error::ErrorInner::AbruptEnd`] or return the inner type if not. fn or_abrupt(self) -> ParseResult; } impl OrAbrupt for ParseResult> { fn or_abrupt(self) -> ParseResult { - self?.ok_or(Error::AbruptEnd) + self?.ok_or(Error::abrupt_end()) } } diff --git a/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs b/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs index 898f931c91d..b3a8f681e91 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs @@ -104,7 +104,7 @@ fn check_new_target_with_property_access() { let new_target = Expression::PropertyAccess( SimplePropertyAccess::new( - Expression::NewTarget, + Box::new(Expression::NewTarget), interner.get_or_intern_static("name", utf16!("name")), ) .into(), diff --git a/core/parser/src/parser/statement/expression/mod.rs b/core/parser/src/parser/statement/expression/mod.rs index bb6d345f893..4d0d6b0b966 100644 --- a/core/parser/src/parser/statement/expression/mod.rs +++ b/core/parser/src/parser/statement/expression/mod.rs @@ -45,6 +45,26 @@ where fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("ExpressionStatement", "Parsing"); + Self::parse_prefix_keywords(cursor, interner)?; + + let expr = Expression::new(true, self.allow_yield, self.allow_await) + .parse_boxed(cursor, interner)?; + + cursor.expect_semicolon("expression statement", interner)?; + + Ok((*expr).into()) + } +} + +impl ExpressionStatement { + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse`, + /// and an often called function in recursion stays outside of this function. + fn parse_prefix_keywords( + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult<()> { let next_token = cursor.peek(0, interner).or_abrupt()?; match next_token.kind() { TokenKind::Keyword((Keyword::Function | Keyword::Class, true)) => { @@ -99,12 +119,6 @@ where } _ => {} } - - let expr = - Expression::new(true, self.allow_yield, self.allow_await).parse(cursor, interner)?; - - cursor.expect_semicolon("expression statement", interner)?; - - Ok(expr.into()) + Ok(()) } } diff --git a/core/parser/src/parser/statement/iteration/for_statement.rs b/core/parser/src/parser/statement/iteration/for_statement.rs index cd392382553..b1033710552 100644 --- a/core/parser/src/parser/statement/iteration/for_statement.rs +++ b/core/parser/src/parser/statement/iteration/for_statement.rs @@ -204,10 +204,10 @@ where cursor.advance(interner); let expr = if in_loop { Expression::new(true, self.allow_yield, self.allow_await) - .parse(cursor, interner)? + .parse_boxed(cursor, interner)? } else { AssignmentExpression::new(true, self.allow_yield, self.allow_await) - .parse(cursor, interner)? + .parse_boxed(cursor, interner)? }; cursor.expect(Punctuator::CloseParen, "for in/of statement", interner)?; diff --git a/core/parser/src/parser/statement/mod.rs b/core/parser/src/parser/statement/mod.rs index 0a8abcff697..65eb3c31acb 100644 --- a/core/parser/src/parser/statement/mod.rs +++ b/core/parser/src/parser/statement/mod.rs @@ -119,6 +119,30 @@ where fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("Statement", "Parsing"); + + match self.parse_non_default_cases(cursor, interner)? { + Some(ok) => Ok(ok), + None => { + ExpressionStatement::new(self.allow_yield, self.allow_await).parse(cursor, interner) + } + } + } +} +impl Statement { + /// This function was added to optimize the stack size. + /// It has an stack size optimization impact only for `profile.#.opt-level = 0`. + /// It allow to reduce stack size allocation in `parse`, + /// and an often called function in recursion stays outside of this function. + /// + /// # Return + /// * `Err(_)` if error occurs; + /// * `Ok(None)` if next expression is a `ExpressionStatement`; + /// * `Ok(Some(Expr))` otherwise; + fn parse_non_default_cases( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult> { // TODO: add BreakableStatement and divide Whiles, fors and so on to another place. let tok = cursor.peek(0, interner).or_abrupt()?; @@ -214,17 +238,16 @@ where self.allow_return, ) .parse(cursor, interner) - .map(ast::Statement::from); + .map(|x| Some(ast::Statement::from(x))); } } ExpressionStatement::new(self.allow_yield, self.allow_await).parse(cursor, interner) } - _ => { - ExpressionStatement::new(self.allow_yield, self.allow_await).parse(cursor, interner) - } + _ => return Ok(None), } + .map(Some) } } diff --git a/core/parser/src/parser/tests/mod.rs b/core/parser/src/parser/tests/mod.rs index cb27b8e02b7..b8f05343d2b 100644 --- a/core/parser/src/parser/tests/mod.rs +++ b/core/parser/src/parser/tests/mod.rs @@ -531,17 +531,19 @@ fn spread_in_arrow_function() { assert_eq!(params.length(), 0); check_script_parser( s, - vec![Statement::Expression(Expression::from(ArrowFunction::new( - None, - params, - FunctionBody::new( - [Statement::Expression(Expression::from(Identifier::from(b))).into()], - PSEUDO_LINEAR_POS, - false, - ), - EMPTY_LINEAR_SPAN, - ))) - .into()], + vec![ + Statement::Expression(Expression::from(ArrowFunction::new_boxed( + None, + params, + FunctionBody::new( + [Statement::Expression(Expression::from(Identifier::from(b))).into()], + PSEUDO_LINEAR_POS, + false, + ), + EMPTY_LINEAR_SPAN, + ))) + .into(), + ], interner, ); } diff --git a/tests/tester/src/exec/mod.rs b/tests/tester/src/exec/mod.rs index 6bca5c1f163..8d54edaa12e 100644 --- a/tests/tester/src/exec/mod.rs +++ b/tests/tester/src/exec/mod.rs @@ -35,7 +35,7 @@ impl TestSuite { max_edition: SpecEdition, optimizer_options: OptimizerOptions, console: bool, - ) -> SuiteResult { + ) -> Box { if verbose != 0 { println!("Suite {}:", self.path.display()); } @@ -44,7 +44,7 @@ impl TestSuite { self.suites .par_iter() .map(|suite| { - suite.run( + *suite.run( harness, verbose, parallel, @@ -58,7 +58,7 @@ impl TestSuite { self.suites .iter() .map(|suite| { - suite.run( + *suite.run( harness, verbose, parallel, @@ -150,14 +150,14 @@ impl TestSuite { (es_next.passed as f64 / es_next.total as f64) * 100.0 ); } - SuiteResult { - name: self.name.clone(), - stats: es_next, + SuiteResult::new_boxed( + self.name.clone(), + es_next, versioned_stats, suites, tests, features, - } + ) } } @@ -521,18 +521,25 @@ impl Test { harness: &Harness, optimizer_options: OptimizerOptions, console: bool, - ) -> Result<(Context, AsyncResult, WorkerHandles), String> { + ) -> Result<(Box, AsyncResult, WorkerHandles), String> { + fn create_boxed_ctx(test: &Test, loader: &Rc) -> Box { + Box::new( + Context::builder() + .module_loader(loader.clone()) + .can_block(!test.flags.contains(TestFlags::CAN_BLOCK_IS_FALSE)) + .build() + .expect("cannot fail with default global object"), + ) + } + let async_result = AsyncResult::default(); let handles = WorkerHandles::new(); let loader = Rc::new( SimpleModuleLoader::new(self.path.parent().expect("test should have a parent dir")) .expect("test path should be canonicalizable"), ); - let mut context = Context::builder() - .module_loader(loader.clone()) - .can_block(!self.flags.contains(TestFlags::CAN_BLOCK_IS_FALSE)) - .build() - .expect("cannot fail with default global object"); + + let mut context = create_boxed_ctx(self, &loader); context.set_optimizer_options(optimizer_options); diff --git a/tests/tester/src/main.rs b/tests/tester/src/main.rs index 986ee338f8f..da07e3a62f5 100644 --- a/tests/tester/src/main.rs +++ b/tests/tester/src/main.rs @@ -467,64 +467,7 @@ fn run_test_suite( console, ); - if versioned { - let mut table = comfy_table::Table::new(); - table.load_preset(comfy_table::presets::UTF8_HORIZONTAL_ONLY); - table.set_header(vec![ - "Edition", "Total", "Passed", "Ignored", "Failed", "Panics", "%", - ]); - for column in table.column_iter_mut().skip(1) { - column.set_cell_alignment(comfy_table::CellAlignment::Right); - } - for (v, stats) in SpecEdition::all_editions() - .filter(|v| *v <= edition) - .map(|v| { - let stats = results.versioned_stats.get(v).unwrap_or(results.stats); - (v, stats) - }) - { - let Statistics { - total, - passed, - ignored, - panic, - } = stats; - let failed = total - passed - ignored; - let conformance = (passed as f64 / total as f64) * 100.0; - let conformance = format!("{conformance:.2}"); - table.add_row(vec![ - v.to_string(), - total.to_string(), - passed.to_string(), - ignored.to_string(), - failed.to_string(), - panic.to_string(), - conformance, - ]); - } - println!("\n\nResults\n"); - println!("{table}"); - } else { - let Statistics { - total, - passed, - ignored, - panic, - } = results.stats; - println!("\n\nResults ({edition}):"); - println!("Total tests: {total}"); - println!("Passed tests: {}", passed.to_string().green()); - println!("Ignored tests: {}", ignored.to_string().yellow()); - println!( - "Failed tests: {} ({})", - (total - passed - ignored).to_string().red(), - format!("{panic} panics").red() - ); - println!( - "Conformance: {:.2}%", - (passed as f64 / total as f64) * 100.0 - ); - } + print_statistic(&results, versioned, edition); if let Some(output) = output { write_json(results, output, verbose, test262_path) @@ -535,6 +478,67 @@ fn run_test_suite( Ok(()) } +fn print_statistic(results: &SuiteResult, versioned: bool, edition: SpecEdition) { + if versioned { + let mut table = comfy_table::Table::new(); + table.load_preset(comfy_table::presets::UTF8_HORIZONTAL_ONLY); + table.set_header(vec![ + "Edition", "Total", "Passed", "Ignored", "Failed", "Panics", "%", + ]); + for column in table.column_iter_mut().skip(1) { + column.set_cell_alignment(comfy_table::CellAlignment::Right); + } + for (v, stats) in SpecEdition::all_editions() + .filter(|v| *v <= edition) + .map(|v| { + let stats = results.versioned_stats.get(v).unwrap_or(results.stats); + (v, stats) + }) + { + let Statistics { + total, + passed, + ignored, + panic, + } = stats; + let failed = total - passed - ignored; + let conformance = (passed as f64 / total as f64) * 100.0; + let conformance = format!("{conformance:.2}"); + table.add_row(vec![ + v.to_string(), + total.to_string(), + passed.to_string(), + ignored.to_string(), + failed.to_string(), + panic.to_string(), + conformance, + ]); + } + println!("\n\nResults\n"); + println!("{table}"); + } else { + let Statistics { + total, + passed, + ignored, + panic, + } = results.stats; + println!("\n\nResults ({edition}):"); + println!("Total tests: {total}"); + println!("Passed tests: {}", passed.to_string().green()); + println!("Ignored tests: {}", ignored.to_string().yellow()); + println!( + "Failed tests: {} ({})", + (total - passed - ignored).to_string().red(), + format!("{panic} panics").red() + ); + println!( + "Conformance: {:.2}%", + (passed as f64 / total as f64) * 100.0 + ); + } +} + /// All the harness include files. #[derive(Debug, Clone)] struct Harness { @@ -774,6 +778,27 @@ struct SuiteResult { features: FxHashSet, } +impl SuiteResult { + #[allow(clippy::large_types_passed_by_value)] + fn new_boxed( + name: Box, + stats: Statistics, + versioned_stats: VersionedStats, + suites: Vec, + tests: Vec, + features: FxHashSet, + ) -> Box { + Box::new(Self { + name, + stats, + versioned_stats, + suites, + tests, + features, + }) + } +} + /// Outcome of a test. #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(dead_code)] @@ -820,7 +845,7 @@ struct Test { impl Test { /// Creates a new test. - fn new(name: N, path: C, metadata: MetaData) -> Result + fn new(name: N, path: C, metadata: MetaData) -> Result> where N: Into>, C: Into>, @@ -828,7 +853,7 @@ impl Test { let edition = SpecEdition::from_test_metadata(&metadata) .map_err(|feats| eyre!("test metadata contained unknown features: {feats:?}"))?; - Ok(Self { + Ok(Box::new(Self { edition, name: name.into(), description: metadata.description, @@ -841,7 +866,7 @@ impl Test { locale: metadata.locale, path: path.into(), ignored: false, - }) + })) } /// Sets the test as ignored. diff --git a/tests/tester/src/read.rs b/tests/tester/src/read.rs index f9af9bd4ce9..fc1576aabdc 100644 --- a/tests/tester/src/read.rs +++ b/tests/tester/src/read.rs @@ -93,7 +93,7 @@ pub(super) enum TestFlag { } /// Reads the Test262 defined bindings. -pub(super) fn read_harness(test262_path: &Path) -> Result { +pub(super) fn read_harness(test262_path: &Path) -> Result> { let mut includes: HashMap, HarnessFile, FxBuildHasher> = FxHashMap::default(); let harness_path = &test262_path.join("harness"); @@ -110,12 +110,12 @@ pub(super) fn read_harness(test262_path: &Path) -> Result { .remove("doneprintHandle.js") .ok_or_eyre("failed to load harness file `donePrintHandle.js`")?; - Ok(Harness { + Ok(Box::new(Harness { assert, sta, doneprint_handle, includes, - }) + })) } fn read_harness_dir( @@ -215,7 +215,7 @@ pub(super) fn read_suite( { test.set_ignored(); } - tests.push(test); + tests.push(*test); } Ok(TestSuite { @@ -227,7 +227,7 @@ pub(super) fn read_suite( } /// Reads information about a given test case. -pub(super) fn read_test(path: &Path) -> Result { +pub(super) fn read_test(path: &Path) -> Result> { let name = path .file_stem() .and_then(OsStr::to_str) diff --git a/tests/tester/src/results.rs b/tests/tester/src/results.rs index c169bb0c238..19555291fff 100644 --- a/tests/tester/src/results.rs +++ b/tests/tester/src/results.rs @@ -82,7 +82,7 @@ const FEATURES_FILE_NAME: &str = "features.json"; /// /// It will append the results to the ones already present, in an array. pub(crate) fn write_json( - results: SuiteResult, + results: Box, output_dir: &Path, verbose: u8, test262_path: &Path, @@ -111,7 +111,7 @@ pub(crate) fn write_json( let new_results = ResultInfo { commit: env::var("GITHUB_SHA").unwrap_or_default().into_boxed_str(), test262_commit: get_test262_commit(test262_path)?, - results, + results: *results, }; let latest = BufWriter::new(fs::File::create(latest)?);