diff --git a/boa_ast/src/declaration/export.rs b/boa_ast/src/declaration/export.rs index 89c4bea40b..aee99c78c4 100644 --- a/boa_ast/src/declaration/export.rs +++ b/boa_ast/src/declaration/export.rs @@ -22,7 +22,9 @@ use crate::{ use boa_interner::Sym; /// The kind of re-export in an [`ExportDeclaration`]. -#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ReExportKind { /// Namespaced Re-export (`export * as name from "module-name"`). Namespaced { @@ -76,7 +78,8 @@ impl VisitWith for ReExportKind { /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ExportDeclaration -#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq)] pub enum ExportDeclaration { /// Re-export. ReExport { @@ -165,7 +168,9 @@ impl VisitWith for ExportDeclaration { /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ExportSpecifier -#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, Copy, PartialEq, Eq)] pub struct ExportSpecifier { alias: Sym, private_name: Sym, diff --git a/boa_ast/src/declaration/import.rs b/boa_ast/src/declaration/import.rs index cdb4b215f6..3c8ef43c02 100644 --- a/boa_ast/src/declaration/import.rs +++ b/boa_ast/src/declaration/import.rs @@ -21,7 +21,9 @@ use boa_interner::Sym; use super::ModuleSpecifier; /// The kind of import in an [`ImportDeclaration`]. -#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ImportKind { /// Default (`import defaultName from "module-name"`) or unnamed (`import "module-name"). DefaultOrUnnamed, @@ -77,7 +79,8 @@ impl VisitWith for ImportKind { /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ImportDeclaration -#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ImportDeclaration { /// Binding for the default export of `specifier`. default: Option, @@ -155,7 +158,9 @@ impl VisitWith for ImportDeclaration { /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ImportSpecifier -#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ImportSpecifier { binding: Identifier, export_name: Sym, diff --git a/boa_ast/src/declaration/mod.rs b/boa_ast/src/declaration/mod.rs index 95dca8940e..4f15e95856 100644 --- a/boa_ast/src/declaration/mod.rs +++ b/boa_ast/src/declaration/mod.rs @@ -105,7 +105,9 @@ impl VisitWith for Declaration { /// This is equivalent to the [`ModuleSpecifier`] production. /// /// [`FromClause`]: https://tc39.es/ecma262/#prod-ModuleSpecifier -#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, Copy, PartialEq, Eq)] pub struct ModuleSpecifier { module: Sym, } diff --git a/boa_ast/src/expression/call.rs b/boa_ast/src/expression/call.rs index 5663e5acb0..8954d9e021 100644 --- a/boa_ast/src/expression/call.rs +++ b/boa_ast/src/expression/call.rs @@ -162,3 +162,68 @@ impl VisitWith for SuperCall { ControlFlow::Continue(()) } } + +/// The import() syntax, commonly called dynamic import, is a function-like expression that allows +/// loading an ECMAScript module asynchronously and dynamically into a potentially non-module +/// environment. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ImportCall +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, PartialEq)] +pub struct ImportCall { + arg: Box, +} + +impl ImportCall { + /// Creates a new `ImportCall` AST node. + pub fn new(arg: A) -> Self + where + A: Into, + { + Self { + arg: Box::new(arg.into()), + } + } + + /// Retrieves the single argument of the import call. + #[must_use] + pub const fn argument(&self) -> &Expression { + &self.arg + } +} + +impl ToInternedString for ImportCall { + #[inline] + fn to_interned_string(&self, interner: &Interner) -> String { + format!("import({})", self.arg.to_interned_string(interner)) + } +} + +impl From for Expression { + #[inline] + fn from(call: ImportCall) -> Self { + Self::ImportCall(call) + } +} + +impl VisitWith for ImportCall { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_expression(&self.arg) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_expression_mut(&mut self.arg) + } +} diff --git a/boa_ast/src/expression/literal/object.rs b/boa_ast/src/expression/literal/object.rs index cafb3cb7eb..cca61e63f5 100644 --- a/boa_ast/src/expression/literal/object.rs +++ b/boa_ast/src/expression/literal/object.rs @@ -256,16 +256,32 @@ impl ToIndentedString for ObjectLiteral { MethodDefinition::Get(expression) | MethodDefinition::Set(expression) | MethodDefinition::Ordinary(expression) => { - block_to_string(expression.body(), interner, indent_n + 1) + block_to_string( + expression.body().statements(), + interner, + indent_n + 1, + ) } MethodDefinition::Generator(expression) => { - block_to_string(expression.body(), interner, indent_n + 1) + block_to_string( + expression.body().statements(), + interner, + indent_n + 1, + ) } MethodDefinition::AsyncGenerator(expression) => { - block_to_string(expression.body(), interner, indent_n + 1) + block_to_string( + expression.body().statements(), + interner, + indent_n + 1, + ) } MethodDefinition::Async(expression) => { - block_to_string(expression.body(), interner, indent_n + 1) + block_to_string( + expression.body().statements(), + interner, + indent_n + 1, + ) } }, ) diff --git a/boa_ast/src/expression/mod.rs b/boa_ast/src/expression/mod.rs index 3288ece120..46462c7d91 100644 --- a/boa_ast/src/expression/mod.rs +++ b/boa_ast/src/expression/mod.rs @@ -33,7 +33,7 @@ mod tagged_template; mod r#yield; use crate::visitor::{VisitWith, Visitor, VisitorMut}; -pub use call::{Call, SuperCall}; +pub use call::{Call, ImportCall, SuperCall}; pub use identifier::{Identifier, RESERVED_IDENTIFIERS_STRICT}; pub use new::New; pub use optional::{Optional, OptionalOperation, OptionalOperationKind}; @@ -121,10 +121,12 @@ pub enum Expression { /// See [`SuperCall`]. SuperCall(SuperCall), + /// See [`ImportCall`]. + ImportCall(ImportCall), + /// See [`Optional`]. Optional(Optional), - // TODO: Import calls /// See [`TaggedTemplate`]. TaggedTemplate(TaggedTemplate), @@ -192,6 +194,7 @@ impl Expression { Self::New(new) => new.to_interned_string(interner), Self::Call(call) => call.to_interned_string(interner), Self::SuperCall(supc) => supc.to_interned_string(interner), + Self::ImportCall(impc) => impc.to_interned_string(interner), Self::Optional(opt) => opt.to_interned_string(interner), Self::NewTarget => "new.target".to_owned(), Self::TaggedTemplate(tag) => tag.to_interned_string(interner), @@ -300,6 +303,7 @@ impl VisitWith for Expression { Self::New(n) => visitor.visit_new(n), Self::Call(c) => visitor.visit_call(c), Self::SuperCall(sc) => visitor.visit_super_call(sc), + Self::ImportCall(ic) => visitor.visit_import_call(ic), Self::Optional(opt) => visitor.visit_optional(opt), Self::TaggedTemplate(tt) => visitor.visit_tagged_template(tt), Self::Assign(a) => visitor.visit_assign(a), @@ -341,6 +345,7 @@ impl VisitWith for Expression { Self::New(n) => visitor.visit_new_mut(n), Self::Call(c) => visitor.visit_call_mut(c), Self::SuperCall(sc) => visitor.visit_super_call_mut(sc), + Self::ImportCall(ic) => visitor.visit_import_call_mut(ic), Self::Optional(opt) => visitor.visit_optional_mut(opt), Self::TaggedTemplate(tt) => visitor.visit_tagged_template_mut(tt), Self::Assign(a) => visitor.visit_assign_mut(a), diff --git a/boa_ast/src/function/arrow_function.rs b/boa_ast/src/function/arrow_function.rs index bf633a1b0e..ca9af3b8c9 100644 --- a/boa_ast/src/function/arrow_function.rs +++ b/boa_ast/src/function/arrow_function.rs @@ -2,12 +2,12 @@ use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ expression::{Expression, Identifier}, - join_nodes, StatementList, + join_nodes, }; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; -use super::FormalParameterList; +use super::{FormalParameterList, FunctionBody}; /// An arrow function expression, as defined by the [spec]. /// @@ -24,7 +24,7 @@ use super::FormalParameterList; pub struct ArrowFunction { name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, } impl ArrowFunction { @@ -34,7 +34,7 @@ impl ArrowFunction { pub const fn new( name: Option, params: FormalParameterList, - body: StatementList, + body: FunctionBody, ) -> Self { Self { name, @@ -66,7 +66,7 @@ impl ArrowFunction { /// Gets the body of the arrow function. #[inline] #[must_use] - pub const fn body(&self) -> &StatementList { + pub const fn body(&self) -> &FunctionBody { &self.body } } @@ -102,7 +102,7 @@ impl VisitWith for ArrowFunction { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_statement_list(&self.body) + visitor.visit_script(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -113,6 +113,6 @@ impl VisitWith for ArrowFunction { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_statement_list_mut(&mut self.body) + visitor.visit_script_mut(&mut self.body) } } diff --git a/boa_ast/src/function/async_arrow_function.rs b/boa_ast/src/function/async_arrow_function.rs index 6f6648bda2..b3a3278263 100644 --- a/boa_ast/src/function/async_arrow_function.rs +++ b/boa_ast/src/function/async_arrow_function.rs @@ -1,11 +1,11 @@ use std::ops::ControlFlow; -use super::FormalParameterList; +use super::{FormalParameterList, FunctionBody}; use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ expression::{Expression, Identifier}, - join_nodes, StatementList, + join_nodes, }; use boa_interner::{Interner, ToIndentedString}; @@ -24,7 +24,7 @@ use boa_interner::{Interner, ToIndentedString}; pub struct AsyncArrowFunction { name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, } impl AsyncArrowFunction { @@ -34,7 +34,7 @@ impl AsyncArrowFunction { pub const fn new( name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, ) -> Self { Self { name, @@ -66,7 +66,7 @@ impl AsyncArrowFunction { /// Gets the body of the arrow function. #[inline] #[must_use] - pub const fn body(&self) -> &StatementList { + pub const fn body(&self) -> &FunctionBody { &self.body } } @@ -102,7 +102,7 @@ impl VisitWith for AsyncArrowFunction { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_statement_list(&self.body) + visitor.visit_script(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -113,6 +113,6 @@ impl VisitWith for AsyncArrowFunction { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_statement_list_mut(&mut self.body) + visitor.visit_script_mut(&mut self.body) } } diff --git a/boa_ast/src/function/async_function.rs b/boa_ast/src/function/async_function.rs index 2b6f77b929..022d82825b 100644 --- a/boa_ast/src/function/async_function.rs +++ b/boa_ast/src/function/async_function.rs @@ -4,12 +4,12 @@ use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ expression::{Expression, Identifier}, - join_nodes, Declaration, StatementList, + join_nodes, Declaration, }; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; -use super::FormalParameterList; +use super::{FormalParameterList, FunctionBody}; /// An async function definition, as defined by the [spec]. /// @@ -25,7 +25,7 @@ use super::FormalParameterList; pub struct AsyncFunction { name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, has_binding_identifier: bool, } @@ -36,7 +36,7 @@ impl AsyncFunction { pub const fn new( name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, has_binding_identifier: bool, ) -> Self { Self { @@ -64,7 +64,7 @@ impl AsyncFunction { /// Gets the body of the function declaration. #[inline] #[must_use] - pub const fn body(&self) -> &StatementList { + pub const fn body(&self) -> &FunctionBody { &self.body } @@ -122,7 +122,7 @@ impl VisitWith for AsyncFunction { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_statement_list(&self.body) + visitor.visit_script(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -133,6 +133,6 @@ impl VisitWith for AsyncFunction { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_statement_list_mut(&mut self.body) + visitor.visit_script_mut(&mut self.body) } } diff --git a/boa_ast/src/function/async_generator.rs b/boa_ast/src/function/async_generator.rs index 37841f2f75..56d7a88f57 100644 --- a/boa_ast/src/function/async_generator.rs +++ b/boa_ast/src/function/async_generator.rs @@ -4,12 +4,12 @@ use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ block_to_string, expression::{Expression, Identifier}, - join_nodes, Declaration, StatementList, + join_nodes, Declaration, }; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; -use super::FormalParameterList; +use super::{FormalParameterList, FunctionBody}; /// An async generator definition, as defined by the [spec]. /// @@ -24,7 +24,7 @@ use super::FormalParameterList; pub struct AsyncGenerator { name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, has_binding_identifier: bool, } @@ -35,7 +35,7 @@ impl AsyncGenerator { pub const fn new( name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, has_binding_identifier: bool, ) -> Self { Self { @@ -63,7 +63,7 @@ impl AsyncGenerator { /// Gets the body of the async generator expression #[inline] #[must_use] - pub const fn body(&self) -> &StatementList { + pub const fn body(&self) -> &FunctionBody { &self.body } @@ -84,7 +84,7 @@ impl ToIndentedString for AsyncGenerator { buf.push_str(&format!( "({}) {}", join_nodes(interner, self.parameters.as_ref()), - block_to_string(&self.body, interner, indentation) + block_to_string(self.body.statements(), interner, indentation) )); buf @@ -114,7 +114,7 @@ impl VisitWith for AsyncGenerator { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_statement_list(&self.body) + visitor.visit_script(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -125,6 +125,6 @@ impl VisitWith for AsyncGenerator { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_statement_list_mut(&mut self.body) + visitor.visit_script_mut(&mut self.body) } } diff --git a/boa_ast/src/function/class.rs b/boa_ast/src/function/class.rs index 7fb02e29c4..e1f8155d89 100644 --- a/boa_ast/src/function/class.rs +++ b/boa_ast/src/function/class.rs @@ -6,7 +6,7 @@ use crate::{ property::{MethodDefinition, PropertyName}, try_break, visitor::{VisitWith, Visitor, VisitorMut}, - Declaration, StatementList, ToStringEscaped, + Declaration, ToStringEscaped, }; use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString}; use core::ops::ControlFlow; @@ -121,7 +121,7 @@ impl ToIndentedString for Class { buf.push_str(&format!( "{indentation}constructor({}) {}\n", join_nodes(interner, expr.parameters().as_ref()), - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) )); } for element in self.elements.iter() { @@ -155,16 +155,16 @@ impl ToIndentedString for Class { MethodDefinition::Get(expr) | MethodDefinition::Set(expr) | MethodDefinition::Ordinary(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::Generator(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::AsyncGenerator(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::Async(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } }, ) @@ -198,16 +198,16 @@ impl ToIndentedString for Class { MethodDefinition::Get(expr) | MethodDefinition::Set(expr) | MethodDefinition::Ordinary(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::Generator(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::AsyncGenerator(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::Async(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } }, ) @@ -268,16 +268,16 @@ impl ToIndentedString for Class { MethodDefinition::Get(expr) | MethodDefinition::Set(expr) | MethodDefinition::Ordinary(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::Generator(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::AsyncGenerator(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::Async(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } }, ) @@ -311,16 +311,16 @@ impl ToIndentedString for Class { MethodDefinition::Get(expr) | MethodDefinition::Set(expr) | MethodDefinition::Ordinary(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::Generator(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::AsyncGenerator(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } MethodDefinition::Async(expr) => { - block_to_string(expr.body(), interner, indent_n + 1) + block_to_string(expr.body().statements(), interner, indent_n + 1) } }, ) @@ -355,10 +355,10 @@ impl ToIndentedString for Class { ) } }, - ClassElement::StaticBlock(statement_list) => { + ClassElement::StaticBlock(body) => { format!( "{indentation}static {}\n", - block_to_string(statement_list, interner, indent_n + 1) + block_to_string(body.statements(), interner, indent_n + 1) ) } }); @@ -420,6 +420,13 @@ impl VisitWith for Class { } } +/// The body of a class' static block, as defined by the [spec]. +/// +/// Just an alias for [`Script`](crate::Script), since it has the same exact semantics. +/// +/// [spec]: https://tc39.es/ecma262/#prod-ClassStaticBlockBody +type StaticBlockBody = crate::Script; + /// An element that can be within a [`Class`], as defined by the [spec]. /// /// [spec]: https://tc39.es/ecma262/#prod-ClassElement @@ -454,7 +461,7 @@ pub enum ClassElement { PrivateStaticFieldDefinition(PrivateName, Option), /// A static block, where a class can have initialization logic for its static fields. - StaticBlock(StatementList), + StaticBlock(StaticBlockBody), } impl VisitWith for ClassElement { @@ -489,7 +496,7 @@ impl VisitWith for ClassElement { ControlFlow::Continue(()) } } - Self::StaticBlock(sl) => visitor.visit_statement_list(sl), + Self::StaticBlock(sl) => visitor.visit_script(sl), } } @@ -524,7 +531,7 @@ impl VisitWith for ClassElement { ControlFlow::Continue(()) } } - Self::StaticBlock(sl) => visitor.visit_statement_list_mut(sl), + Self::StaticBlock(sl) => visitor.visit_script_mut(sl), } } } diff --git a/boa_ast/src/function/generator.rs b/boa_ast/src/function/generator.rs index 21789b1ad5..50d77611a9 100644 --- a/boa_ast/src/function/generator.rs +++ b/boa_ast/src/function/generator.rs @@ -1,7 +1,7 @@ use crate::{ block_to_string, expression::{Expression, Identifier}, - join_nodes, Declaration, StatementList, + join_nodes, Declaration, }; use core::ops::ControlFlow; @@ -9,7 +9,7 @@ use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use boa_interner::{Interner, ToIndentedString}; -use super::FormalParameterList; +use super::{FormalParameterList, FunctionBody}; /// A generator definition, as defined by the [spec]. /// @@ -26,7 +26,7 @@ use super::FormalParameterList; pub struct Generator { name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, has_binding_identifier: bool, } @@ -37,7 +37,7 @@ impl Generator { pub const fn new( name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, has_binding_identifier: bool, ) -> Self { Self { @@ -65,7 +65,7 @@ impl Generator { /// Gets the body of the generator declaration. #[inline] #[must_use] - pub const fn body(&self) -> &StatementList { + pub const fn body(&self) -> &FunctionBody { &self.body } @@ -86,7 +86,7 @@ impl ToIndentedString for Generator { buf.push_str(&format!( "({}) {}", join_nodes(interner, self.parameters.as_ref()), - block_to_string(&self.body, interner, indentation) + block_to_string(self.body.statements(), interner, indentation) )); buf @@ -116,7 +116,7 @@ impl VisitWith for Generator { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_statement_list(&self.body) + visitor.visit_script(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -127,6 +127,6 @@ impl VisitWith for Generator { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_statement_list_mut(&mut self.body) + visitor.visit_script_mut(&mut self.body) } } diff --git a/boa_ast/src/function/mod.rs b/boa_ast/src/function/mod.rs index 8e3084649b..fc6a230868 100644 --- a/boa_ast/src/function/mod.rs +++ b/boa_ast/src/function/mod.rs @@ -38,9 +38,9 @@ use core::ops::ControlFlow; pub use generator::Generator; pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags}; -use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; -use crate::{block_to_string, join_nodes, StatementList}; +use crate::{block_to_string, join_nodes}; +use crate::{try_break, Script}; use boa_interner::{Interner, ToIndentedString}; use super::expression::{Expression, Identifier}; @@ -62,7 +62,7 @@ use super::Declaration; pub struct Function { name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, has_binding_identifier: bool, } @@ -73,7 +73,7 @@ impl Function { pub const fn new( name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, ) -> Self { Self { name, @@ -89,7 +89,7 @@ impl Function { pub const fn new_with_binding_identifier( name: Option, parameters: FormalParameterList, - body: StatementList, + body: FunctionBody, has_binding_identifier: bool, ) -> Self { Self { @@ -117,7 +117,7 @@ impl Function { /// Gets the body of the function declaration. #[inline] #[must_use] - pub const fn body(&self) -> &StatementList { + pub const fn body(&self) -> &FunctionBody { &self.body } @@ -138,7 +138,7 @@ impl ToIndentedString for Function { buf.push_str(&format!( "({}) {}", join_nodes(interner, self.parameters.as_ref()), - block_to_string(&self.body, interner, indentation) + block_to_string(self.body.statements(), interner, indentation) )); buf @@ -168,7 +168,7 @@ impl VisitWith for Function { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_statement_list(&self.body) + visitor.visit_script(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -179,6 +179,17 @@ impl VisitWith for Function { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_statement_list_mut(&mut self.body) + visitor.visit_script_mut(&mut self.body) } } + +/// A Function body. +/// +/// Since [`Script`] and `FunctionBody` has the same semantics, this is currently +/// only an alias of the former. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-FunctionBody +pub type FunctionBody = Script; diff --git a/boa_ast/src/lib.rs b/boa_ast/src/lib.rs index 30a1aed191..0939bda588 100644 --- a/boa_ast/src/lib.rs +++ b/boa_ast/src/lib.rs @@ -75,15 +75,16 @@ clippy::option_if_let_else )] +mod module_item_list; mod position; mod punctuator; +mod source; mod statement_list; pub mod declaration; pub mod expression; pub mod function; pub mod keyword; -pub mod module_item_list; pub mod operations; pub mod pattern; pub mod property; @@ -99,6 +100,7 @@ pub use self::{ module_item_list::{ModuleItem, ModuleItemList}, position::{Position, Span}, punctuator::Punctuator, + source::{Module, Script}, statement::Statement, statement_list::{StatementList, StatementListItem}, }; diff --git a/boa_ast/src/module_item_list/mod.rs b/boa_ast/src/module_item_list/mod.rs index cbb622b996..5c8e380cc6 100644 --- a/boa_ast/src/module_item_list/mod.rs +++ b/boa_ast/src/module_item_list/mod.rs @@ -31,7 +31,8 @@ use crate::{ /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ModuleItemList -#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq)] pub struct ModuleItemList { items: Box<[ModuleItem]>, } @@ -462,7 +463,8 @@ impl VisitWith for ModuleItemList { /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-ModuleItem -#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq)] pub enum ModuleItem { /// See [`ImportDeclaration`]. ImportDeclaration(ImportDeclaration), diff --git a/boa_ast/src/operations.rs b/boa_ast/src/operations.rs index 306f9a3791..ebe753fa4f 100644 --- a/boa_ast/src/operations.rs +++ b/boa_ast/src/operations.rs @@ -9,7 +9,9 @@ use boa_interner::{Interner, Sym}; use rustc_hash::FxHashSet; use crate::{ - declaration::{Binding, ExportDeclaration, ImportDeclaration, VarDeclaration, Variable}, + declaration::{ + Binding, ExportDeclaration, ImportDeclaration, LexicalDeclaration, VarDeclaration, Variable, + }, expression::{ access::{PrivatePropertyAccess, SuperPropertyAccess}, operator::BinaryInPrivate, @@ -26,7 +28,7 @@ use crate::{ }, try_break, visitor::{NodeRef, VisitWith, Visitor}, - Declaration, Expression, ModuleItem, Statement, StatementList, StatementListItem, + Declaration, Expression, ModuleItem, Script, Statement, StatementList, StatementListItem, }; /// Represents all the possible symbols searched for by the [`Contains`][contains] operation. @@ -451,63 +453,101 @@ struct LexicallyDeclaredNamesVisitor<'a, T: IdentList>(&'a mut T); impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T> { type BreakTy = Infallible; + fn visit_script(&mut self, node: &'ast Script) -> ControlFlow { + top_level_lexicals(node.statements(), self.0); + ControlFlow::Continue(()) + } + + fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { + match node { + // ModuleItem : ImportDeclaration + ModuleItem::ImportDeclaration(import) => { + // 1. Return the BoundNames of ImportDeclaration. + BoundNamesVisitor(self.0).visit_import_declaration(import) + } + + // ModuleItem : ExportDeclaration + ModuleItem::ExportDeclaration(export) => { + // 1. If ExportDeclaration is export VariableStatement, return a new empty List. + if matches!(export, ExportDeclaration::VarStatement(_)) { + ControlFlow::Continue(()) + } else { + // 2. Return the BoundNames of ExportDeclaration. + BoundNamesVisitor(self.0).visit_export_declaration(export) + } + } + + // ModuleItem : StatementListItem + ModuleItem::StatementListItem(item) => { + // 1. Return LexicallyDeclaredNames of StatementListItem. + self.visit_statement_list_item(item) + } + } + } + fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow { ControlFlow::Continue(()) } + fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { if let Statement::Labelled(labelled) = node { return self.visit_labelled(labelled); } ControlFlow::Continue(()) } + fn visit_declaration(&mut self, node: &'ast Declaration) -> ControlFlow { BoundNamesVisitor(self.0).visit_declaration(node) } + fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { match node { LabelledItem::Function(f) => BoundNamesVisitor(self.0).visit_function(f), LabelledItem::Statement(_) => ControlFlow::Continue(()), } } + fn visit_function(&mut self, node: &'ast Function) -> ControlFlow { - top_level_lexicals(node.body(), self.0); - ControlFlow::Continue(()) + self.visit_script(node.body()) } + fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow { - top_level_lexicals(node.body(), self.0); - ControlFlow::Continue(()) + self.visit_script(node.body()) } + fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow { - top_level_lexicals(node.body(), self.0); - ControlFlow::Continue(()) + self.visit_script(node.body()) } + fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow { - top_level_lexicals(node.body(), self.0); - ControlFlow::Continue(()) + self.visit_script(node.body()) } + fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow { - top_level_lexicals(node.body(), self.0); - ControlFlow::Continue(()) + self.visit_script(node.body()) } + fn visit_async_arrow_function( &mut self, node: &'ast AsyncArrowFunction, ) -> ControlFlow { - top_level_lexicals(node.body(), self.0); - ControlFlow::Continue(()) + self.visit_script(node.body()) } + fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { - if let ClassElement::StaticBlock(stmts) = node { - top_level_lexicals(stmts, self.0); + if let ClassElement::StaticBlock(body) = node { + self.visit_script(body); } ControlFlow::Continue(()) } + fn visit_import_declaration( &mut self, node: &'ast ImportDeclaration, ) -> ControlFlow { BoundNamesVisitor(self.0).visit_import_declaration(node) } + fn visit_export_declaration( &mut self, node: &'ast ExportDeclaration, @@ -517,10 +557,6 @@ impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T> } BoundNamesVisitor(self.0).visit_export_declaration(node) } - - // TODO: ScriptBody : StatementList - // 1. Return TopLevelLexicallyDeclaredNames of StatementList. - // But we don't have that node yet. In the meantime, use `top_level_lexically_declared_names` directly. } /// Returns a list with the lexical bindings of a node, which may contain duplicates. @@ -562,6 +598,34 @@ struct VarDeclaredNamesVisitor<'a>(&'a mut FxHashSet); impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { type BreakTy = Infallible; + fn visit_script(&mut self, node: &'ast Script) -> ControlFlow { + top_level_vars(node.statements(), self.0); + ControlFlow::Continue(()) + } + + fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { + match node { + // ModuleItem : ImportDeclaration + ModuleItem::ImportDeclaration(_) => { + // 1. Return a new empty List. + ControlFlow::Continue(()) + } + + // ModuleItem : ExportDeclaration + ModuleItem::ExportDeclaration(export) => { + // 1. If ExportDeclaration is export VariableStatement, return BoundNames of ExportDeclaration. + if let ExportDeclaration::VarStatement(var) = export { + BoundNamesVisitor(self.0).visit_var_declaration(var) + } else { + // 2. Return a new empty List. + ControlFlow::Continue(()) + } + } + + ModuleItem::StatementListItem(item) => self.visit_statement_list_item(item), + } + } + fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { match node { Statement::Empty @@ -682,28 +746,24 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { } fn visit_function(&mut self, node: &'ast Function) -> ControlFlow { - top_level_vars(node.body(), self.0); - ControlFlow::Continue(()) + self.visit_script(node.body()) } fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow { - top_level_vars(node.body(), self.0); - ControlFlow::Continue(()) + self.visit_script(node.body()) } fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow { - top_level_vars(node.body(), self.0); - ControlFlow::Continue(()) + self.visit_script(node.body()) } fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow { - top_level_vars(node.body(), self.0); - ControlFlow::Continue(()) + self.visit_script(node.body()) } fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { - if let ClassElement::StaticBlock(stmts) = node { - top_level_vars(stmts, self.0); + if let ClassElement::StaticBlock(body) = node { + self.visit_script(body); } node.visit_with(self) } @@ -726,10 +786,6 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { _ => ControlFlow::Continue(()), } } - - // TODO: ScriptBody : StatementList - // 1. Return TopLevelVarDeclaredNames of StatementList. - // But we don't have that node yet. In the meantime, use `top_level_var_declared_names` directly. } /// Returns a set with the var bindings of a node, with no duplicates. @@ -748,6 +804,10 @@ where } /// Utility function that collects the top level lexicals of a statement list into `names`. +/// +/// This is equivalent to the [`TopLevelLexicallyDeclaredNames`][spec] syntax operation in the spec. +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallydeclarednames fn top_level_lexicals(stmts: &StatementList, names: &mut T) { for stmt in stmts.statements() { if let StatementListItem::Declaration(decl) = stmt { @@ -770,20 +830,11 @@ fn top_level_lexicals(stmts: &StatementList, names: &mut T) { } } -/// Returns a list with the lexical bindings of a top-level statement list, which may contain duplicates. +/// Utility function that collects the top level vars of a statement list into `names`. /// -/// This is equivalent to the [`TopLevelLexicallyDeclaredNames`][spec] syntax operation in the spec. +/// This is equivalent to the [`TopLevelVarDeclaredNames`][spec] syntax operation in the spec. /// -/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallydeclarednames -#[must_use] -#[inline] -pub fn top_level_lexically_declared_names(stmts: &StatementList) -> Vec { - let mut names = Vec::new(); - top_level_lexicals(stmts, &mut names); - names -} - -/// Utility function that collects the top level vars of a statement list into `names`. +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvardeclarednames fn top_level_vars(stmts: &StatementList, names: &mut FxHashSet) { for stmt in stmts.statements() { match stmt { @@ -826,19 +877,6 @@ fn top_level_vars(stmts: &StatementList, names: &mut FxHashSet) { } } -/// Returns a list with the var bindings of a top-level statement list, with no duplicates. -/// -/// This is equivalent to the [`TopLevelVarDeclaredNames`][spec] syntax operation in the spec. -/// -/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvardeclarednames -#[must_use] -#[inline] -pub fn top_level_var_declared_names(stmts: &StatementList) -> FxHashSet { - let mut names = FxHashSet::default(); - top_level_vars(stmts, &mut names); - names -} - /// Returns `true` if all private identifiers in a node are valid. /// /// This is equivalent to the [`AllPrivateIdentifiersValid`][spec] syntax operation in the spec. @@ -1319,13 +1357,69 @@ where node.visit_with(&mut visitor).is_break() } +/// The type of a lexically scoped declaration. +#[derive(Clone, Debug)] +pub enum LexicallyScopedDeclaration { + /// See [`LexicalDeclaration`] + LexicalDeclaration(LexicalDeclaration), + + /// See [`Function`] + Function(Function), + + /// See [`Generator`] + Generator(Generator), + + /// See [`AsyncFunction`] + AsyncFunction(AsyncFunction), + + /// See [`AsyncGenerator`] + AsyncGenerator(AsyncGenerator), + + /// See [`Class`] + Class(Class), + + /// A default assignment expression as an export declaration. + /// + /// Only valid inside module exports. + AssignmentExpression(Expression), +} + +impl LexicallyScopedDeclaration { + /// Return the bound names of the declaration. + #[must_use] + pub fn bound_names(&self) -> Vec { + match self { + Self::LexicalDeclaration(v) => bound_names(v), + Self::Function(f) => bound_names(f), + Self::Generator(g) => bound_names(g), + Self::AsyncFunction(f) => bound_names(f), + Self::AsyncGenerator(g) => bound_names(g), + Self::Class(cl) => bound_names(cl), + Self::AssignmentExpression(expr) => bound_names(expr), + } + } +} + +impl From for LexicallyScopedDeclaration { + fn from(value: Declaration) -> Self { + match value { + Declaration::Function(f) => Self::Function(f), + Declaration::Generator(g) => Self::Generator(g), + Declaration::AsyncFunction(af) => Self::AsyncFunction(af), + Declaration::AsyncGenerator(ag) => Self::AsyncGenerator(ag), + Declaration::Class(c) => Self::Class(c), + Declaration::Lexical(lex) => Self::LexicalDeclaration(lex), + } + } +} + /// Returns a list of lexically scoped declarations of the given node. /// /// This is equivalent to the [`LexicallyScopedDeclarations`][spec] syntax operation in the spec. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallyscopeddeclarations #[must_use] -pub fn lexically_scoped_declarations<'a, N>(node: &'a N) -> Vec +pub fn lexically_scoped_declarations<'a, N>(node: &'a N) -> Vec where &'a N: Into>, { @@ -1336,22 +1430,91 @@ where /// The [`Visitor`] used to obtain the lexically scoped declarations of a node. #[derive(Debug)] -struct LexicallyScopedDeclarationsVisitor<'a>(&'a mut Vec); +struct LexicallyScopedDeclarationsVisitor<'a>(&'a mut Vec); impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_> { type BreakTy = Infallible; + // ScriptBody : StatementList + fn visit_script(&mut self, node: &'ast Script) -> ControlFlow { + // 1. Return TopLevelLexicallyScopedDeclarations of StatementList. + TopLevelLexicallyScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements()) + } + + fn visit_export_declaration( + &mut self, + node: &'ast ExportDeclaration, + ) -> ControlFlow { + let decl = match node { + // ExportDeclaration : + // export ExportFromClause FromClause ; + // export NamedExports ; + // export VariableStatement + ExportDeclaration::ReExport { .. } + | ExportDeclaration::List(_) + | ExportDeclaration::VarStatement(_) => { + // 1. Return a new empty List. + return ControlFlow::Continue(()); + } + + // ExportDeclaration : export Declaration + ExportDeclaration::Declaration(decl) => { + // 1. Return a List whose sole element is DeclarationPart of Declaration. + decl.clone().into() + } + + // ExportDeclaration : export default HoistableDeclaration + // 1. Return a List whose sole element is DeclarationPart of HoistableDeclaration. + ExportDeclaration::DefaultFunction(f) => { + LexicallyScopedDeclaration::Function(f.clone()) + } + ExportDeclaration::DefaultGenerator(g) => { + LexicallyScopedDeclaration::Generator(g.clone()) + } + ExportDeclaration::DefaultAsyncFunction(af) => { + LexicallyScopedDeclaration::AsyncFunction(af.clone()) + } + ExportDeclaration::DefaultAsyncGenerator(ag) => { + LexicallyScopedDeclaration::AsyncGenerator(ag.clone()) + } + + // ExportDeclaration : export default ClassDeclaration + ExportDeclaration::DefaultClassDeclaration(c) => { + // 1. Return a List whose sole element is ClassDeclaration. + LexicallyScopedDeclaration::Class(c.clone()) + } + + // ExportDeclaration : export default AssignmentExpression ; + ExportDeclaration::DefaultAssignmentExpression(expr) => { + // 1. Return a List whose sole element is this ExportDeclaration. + LexicallyScopedDeclaration::AssignmentExpression(expr.clone()) + } + }; + + self.0.push(decl); + + ControlFlow::Continue(()) + } + fn visit_statement_list_item( &mut self, node: &'ast StatementListItem, ) -> ControlFlow { match node { + // StatementListItem : Statement StatementListItem::Statement(Statement::Labelled(labelled)) => { + // 1. If Statement is Statement : LabelledStatement , return LexicallyScopedDeclarations of LabelledStatement. self.visit_labelled(labelled) } - StatementListItem::Statement(_) => ControlFlow::Continue(()), + StatementListItem::Statement(_) => { + // 2. Return a new empty List. + ControlFlow::Continue(()) + } + + // StatementListItem : Declaration StatementListItem::Declaration(declaration) => { - self.0.push(declaration.clone()); + // 1. Return a List whose sole element is DeclarationPart of Declaration. + self.0.push(declaration.clone().into()); ControlFlow::Continue(()) } } @@ -1359,45 +1522,76 @@ impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_> { fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { match node { + // LabelledItem : FunctionDeclaration LabelledItem::Function(f) => { - self.0.push(f.clone().into()); - ControlFlow::Continue(()) + // 1. Return « FunctionDeclaration ». + self.0.push(LexicallyScopedDeclaration::Function(f.clone())); + } + + // LabelledItem : Statement + LabelledItem::Statement(_) => { + // 1. Return a new empty List. } - LabelledItem::Statement(_) => ControlFlow::Continue(()), } + ControlFlow::Continue(()) } fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { ModuleItem::StatementListItem(item) => self.visit_statement_list_item(item), - ModuleItem::ExportDeclaration(ExportDeclaration::Declaration(declaration)) => { - self.0.push(declaration.clone()); - ControlFlow::Continue(()) - } - ModuleItem::ExportDeclaration(ExportDeclaration::DefaultFunction(f)) => { - self.0.push(f.clone().into()); - ControlFlow::Continue(()) - } - ModuleItem::ExportDeclaration(ExportDeclaration::DefaultGenerator(f)) => { - self.0.push(f.clone().into()); - ControlFlow::Continue(()) - } - ModuleItem::ExportDeclaration(ExportDeclaration::DefaultAsyncFunction(f)) => { - self.0.push(f.clone().into()); - ControlFlow::Continue(()) - } - ModuleItem::ExportDeclaration(ExportDeclaration::DefaultAsyncGenerator(f)) => { - self.0.push(f.clone().into()); - ControlFlow::Continue(()) - } - ModuleItem::ExportDeclaration(ExportDeclaration::DefaultClassDeclaration(f)) => { - self.0.push(f.clone().into()); + ModuleItem::ExportDeclaration(export) => self.visit_export_declaration(export), + + // ModuleItem : ImportDeclaration + ModuleItem::ImportDeclaration(_) => { + // 1. Return a new empty List. ControlFlow::Continue(()) } - ModuleItem::ImportDeclaration(_) | ModuleItem::ExportDeclaration(_) => { - ControlFlow::Continue(()) + } + } +} +/// The [`Visitor`] used to obtain the top level lexically scoped declarations of a node. +/// +/// This is equivalent to the [`TopLevelLexicallyScopedDeclarations`][spec] syntax operation in the spec. +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallyscopeddeclarations +#[derive(Debug)] +struct TopLevelLexicallyScopedDeclarationsVisitor<'a>(&'a mut Vec); + +impl<'ast> Visitor<'ast> for TopLevelLexicallyScopedDeclarationsVisitor<'_> { + type BreakTy = Infallible; + + fn visit_statement_list_item( + &mut self, + node: &'ast StatementListItem, + ) -> ControlFlow { + match node { + // StatementListItem : Declaration + StatementListItem::Declaration(d) => match d { + // 1. If Declaration is Declaration : HoistableDeclaration , then + Declaration::Function(_) + | Declaration::Generator(_) + | Declaration::AsyncFunction(_) + | Declaration::AsyncGenerator(_) => { + // a. Return a new empty List. + } + + // 2. Return « Declaration ». + Declaration::Class(cl) => { + self.0.push(LexicallyScopedDeclaration::Class(cl.clone())); + } + Declaration::Lexical(lex) => { + self.0 + .push(LexicallyScopedDeclaration::LexicalDeclaration(lex.clone())); + } + }, + + // StatementListItem : Statement + StatementListItem::Statement(_) => { + // 1. Return a new empty List. } } + + ControlFlow::Continue(()) } } @@ -1456,6 +1650,12 @@ struct VarScopedDeclarationsVisitor<'a>(&'a mut Vec); impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> { type BreakTy = Infallible; + // ScriptBody : StatementList + fn visit_script(&mut self, node: &'ast Script) -> ControlFlow { + // 1. Return TopLevelVarScopedDeclarations of StatementList. + TopLevelVarScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements()) + } + fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { match node { Statement::Block(s) => self.visit(s), @@ -1590,29 +1790,31 @@ impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> { fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { - ModuleItem::ExportDeclaration(ExportDeclaration::VarStatement(var)) => self.visit(var), - ModuleItem::StatementListItem(item) => self.visit(item), - _ => ControlFlow::Continue(()), + // ModuleItem : ExportDeclaration + ModuleItem::ExportDeclaration(decl) => { + if let ExportDeclaration::VarStatement(var) = decl { + // 1. If ExportDeclaration is export VariableStatement, return VarScopedDeclarations of VariableStatement. + self.visit_var_declaration(var); + } + // 2. Return a new empty List. + } + ModuleItem::StatementListItem(item) => { + self.visit_statement_list_item(item); + } + // ModuleItem : ImportDeclaration + ModuleItem::ImportDeclaration(_) => { + // 1. Return a new empty List. + } } + ControlFlow::Continue(()) } } -/// Returns a list of top level var scoped declarations of the given node. +/// The [`Visitor`] used to obtain the top level var scoped declarations of a node. /// /// This is equivalent to the [`TopLevelVarScopedDeclarations`][spec] syntax operation in the spec. /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvarscopeddeclarations -#[must_use] -pub fn top_level_var_scoped_declarations<'a, N>(node: &'a N) -> Vec -where - &'a N: Into>, -{ - let mut declarations = Vec::new(); - TopLevelVarScopedDeclarationsVisitor(&mut declarations).visit(node.into()); - declarations -} - -/// The [`Visitor`] used to obtain the top level var scoped declarations of a node. #[derive(Debug)] struct TopLevelVarScopedDeclarationsVisitor<'a>(&'a mut Vec); diff --git a/boa_ast/src/source.rs b/boa_ast/src/source.rs new file mode 100644 index 0000000000..10ca0d727b --- /dev/null +++ b/boa_ast/src/source.rs @@ -0,0 +1,111 @@ +use std::ops::ControlFlow; + +use boa_interner::ToIndentedString; + +use crate::{ + visitor::{VisitWith, Visitor, VisitorMut}, + ModuleItemList, StatementList, +}; + +/// A Script source. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-scripts +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Script { + statements: StatementList, +} + +impl Script { + /// Creates a new `ScriptNode`. + #[must_use] + pub const fn new(statements: StatementList) -> Self { + Self { statements } + } + + /// Gets the list of statements of this `ScriptNode`. + #[must_use] + pub const fn statements(&self) -> &StatementList { + &self.statements + } + + /// Gets a mutable reference to the list of statements of this `ScriptNode`. + pub fn statements_mut(&mut self) -> &mut StatementList { + &mut self.statements + } + + /// Gets the strict mode. + #[inline] + #[must_use] + pub const fn strict(&self) -> bool { + self.statements.strict() + } +} + +impl VisitWith for Script { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + self.statements.visit_with(visitor) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + self.statements.visit_with_mut(visitor) + } +} + +impl ToIndentedString for Script { + fn to_indented_string(&self, interner: &boa_interner::Interner, indentation: usize) -> String { + self.statements.to_indented_string(interner, indentation) + } +} + +/// A Module source. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-modules +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Module { + items: ModuleItemList, +} + +impl Module { + /// Creates a new `ModuleNode`. + #[must_use] + pub const fn new(items: ModuleItemList) -> Self { + Self { items } + } + + /// Gets the list of itemos of this `ModuleNode`. + #[must_use] + pub const fn items(&self) -> &ModuleItemList { + &self.items + } +} + +impl VisitWith for Module { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + self.items.visit_with(visitor) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + self.items.visit_with_mut(visitor) + } +} diff --git a/boa_ast/src/statement_list.rs b/boa_ast/src/statement_list.rs index 1f9d927ef8..f4ac4da3af 100644 --- a/boa_ast/src/statement_list.rs +++ b/boa_ast/src/statement_list.rs @@ -8,6 +8,7 @@ use crate::{ }; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; +use std::ops::Deref; /// An item inside a [`StatementList`] Parse Node, as defined by the [spec]. /// @@ -149,6 +150,14 @@ impl From> for StatementList { } } +impl Deref for StatementList { + type Target = [StatementListItem]; + + fn deref(&self) -> &Self::Target { + &self.statements + } +} + impl ToIndentedString for StatementList { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { let mut buf = String::new(); diff --git a/boa_ast/src/visitor.rs b/boa_ast/src/visitor.rs index df017da17b..3938dc4c75 100644 --- a/boa_ast/src/visitor.rs +++ b/boa_ast/src/visitor.rs @@ -21,7 +21,7 @@ use crate::{ assign::{Assign, AssignTarget}, Binary, BinaryInPrivate, Conditional, Unary, Update, }, - Await, Call, Expression, Identifier, New, Optional, OptionalOperation, + Await, Call, Expression, Identifier, ImportCall, New, Optional, OptionalOperation, OptionalOperationKind, Parenthesized, Spread, SuperCall, TaggedTemplate, Yield, }, function::{ @@ -38,7 +38,7 @@ use crate::{ Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw, Try, With, }, - ModuleItem, ModuleItemList, StatementList, StatementListItem, + Module, ModuleItem, ModuleItemList, Script, StatementList, StatementListItem, }; use boa_interner::Sym; @@ -118,6 +118,8 @@ macro_rules! node_ref { } node_ref! { + Script, + Module, StatementList, StatementListItem, Statement, @@ -164,6 +166,7 @@ node_ref! { New, Call, SuperCall, + ImportCall, Optional, TaggedTemplate, Assign, @@ -217,6 +220,8 @@ pub trait Visitor<'ast>: Sized { /// Type which will be propagated from the visitor if completing early. type BreakTy; + define_visit!(visit_script, Script); + define_visit!(visit_module, Module); define_visit!(visit_statement_list, StatementList); define_visit!(visit_statement_list_item, StatementListItem); define_visit!(visit_statement, Statement); @@ -263,6 +268,7 @@ pub trait Visitor<'ast>: Sized { define_visit!(visit_new, New); define_visit!(visit_call, Call); define_visit!(visit_super_call, SuperCall); + define_visit!(visit_import_call, ImportCall); define_visit!(visit_optional, Optional); define_visit!(visit_tagged_template, TaggedTemplate); define_visit!(visit_assign, Assign); @@ -313,6 +319,8 @@ pub trait Visitor<'ast>: Sized { fn visit>>(&mut self, node: N) -> ControlFlow { let node = node.into(); match node { + NodeRef::Script(n) => self.visit_script(n), + NodeRef::Module(n) => self.visit_module(n), NodeRef::StatementList(n) => self.visit_statement_list(n), NodeRef::StatementListItem(n) => self.visit_statement_list_item(n), NodeRef::Statement(n) => self.visit_statement(n), @@ -359,6 +367,7 @@ pub trait Visitor<'ast>: Sized { NodeRef::New(n) => self.visit_new(n), NodeRef::Call(n) => self.visit_call(n), NodeRef::SuperCall(n) => self.visit_super_call(n), + NodeRef::ImportCall(n) => self.visit_import_call(n), NodeRef::Optional(n) => self.visit_optional(n), NodeRef::TaggedTemplate(n) => self.visit_tagged_template(n), NodeRef::Assign(n) => self.visit_assign(n), @@ -414,6 +423,8 @@ pub trait VisitorMut<'ast>: Sized { /// Type which will be propagated from the visitor if completing early. type BreakTy; + define_visit_mut!(visit_script_mut, Script); + define_visit_mut!(visit_module_mut, Module); define_visit_mut!(visit_statement_list_mut, StatementList); define_visit_mut!(visit_statement_list_item_mut, StatementListItem); define_visit_mut!(visit_statement_mut, Statement); @@ -460,6 +471,7 @@ pub trait VisitorMut<'ast>: Sized { define_visit_mut!(visit_new_mut, New); define_visit_mut!(visit_call_mut, Call); define_visit_mut!(visit_super_call_mut, SuperCall); + define_visit_mut!(visit_import_call_mut, ImportCall); define_visit_mut!(visit_optional_mut, Optional); define_visit_mut!(visit_tagged_template_mut, TaggedTemplate); define_visit_mut!(visit_assign_mut, Assign); @@ -510,6 +522,8 @@ pub trait VisitorMut<'ast>: Sized { fn visit>>(&mut self, node: N) -> ControlFlow { let node = node.into(); match node { + NodeRefMut::Script(n) => self.visit_script_mut(n), + NodeRefMut::Module(n) => self.visit_module_mut(n), NodeRefMut::StatementList(n) => self.visit_statement_list_mut(n), NodeRefMut::StatementListItem(n) => self.visit_statement_list_item_mut(n), NodeRefMut::Statement(n) => self.visit_statement_mut(n), @@ -556,6 +570,7 @@ pub trait VisitorMut<'ast>: Sized { NodeRefMut::New(n) => self.visit_new_mut(n), NodeRefMut::Call(n) => self.visit_call_mut(n), NodeRefMut::SuperCall(n) => self.visit_super_call_mut(n), + NodeRefMut::ImportCall(n) => self.visit_import_call_mut(n), NodeRefMut::Optional(n) => self.visit_optional_mut(n), NodeRefMut::TaggedTemplate(n) => self.visit_tagged_template_mut(n), NodeRefMut::Assign(n) => self.visit_assign_mut(n), diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index e7e7210c7a..723d8c44f0 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -59,10 +59,11 @@ )] #![allow(clippy::option_if_let_else, clippy::redundant_pub_crate)] +use boa_ast as _; + mod debug; mod helper; -use boa_ast::StatementList; use boa_engine::{ builtins::promise::PromiseState, context::ContextBuilder, @@ -70,15 +71,19 @@ use boa_engine::{ module::{Module, ModuleLoader, SimpleModuleLoader}, optimizer::OptimizerOptions, property::Attribute, + script::Script, vm::flowgraph::{Direction, Graph}, - Context, JsNativeError, JsResult, Source, + Context, JsError, JsNativeError, JsResult, Source, }; use boa_runtime::Console; use clap::{Parser, ValueEnum, ValueHint}; use colored::{Color, Colorize}; use debug::init_boa_debug_object; use rustyline::{config::Config, error::ReadlineError, EditMode, Editor}; -use std::{cell::RefCell, collections::VecDeque, fs::read, fs::OpenOptions, io, path::PathBuf}; +use std::{ + cell::RefCell, collections::VecDeque, eprintln, fs::read, fs::OpenOptions, io, path::PathBuf, + println, +}; #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] #[cfg_attr( @@ -174,7 +179,7 @@ impl Opt { } } -#[derive(Debug, Clone, ValueEnum)] +#[derive(Debug, Copy, Clone, Default, ValueEnum)] enum DumpFormat { /// The different types of format available for dumping. // NOTE: This can easily support other formats just by @@ -185,6 +190,7 @@ enum DumpFormat { // arg_enum! macro does not support it. // This is the default format that you get from std::fmt::Debug. + #[default] Debug, // This is a minified json format. @@ -212,19 +218,6 @@ enum FlowgraphDirection { RightToLeft, } -/// Parses the the token stream into an AST and returns it. -/// -/// Returns a error of type String with a message, -/// if the token stream has a parsing error. -fn parse_tokens(src: &S, context: &mut Context<'_>) -> Result -where - S: AsRef<[u8]> + ?Sized, -{ - boa_parser::Parser::new(Source::from_bytes(&src)) - .parse_script(context.interner_mut()) - .map_err(|e| format!("Uncaught SyntaxError: {e}")) -} - /// Dumps the AST to stdout with format controlled by the given arguments. /// /// Returns a error of type String with a error message, @@ -233,25 +226,41 @@ fn dump(src: &S, args: &Opt, context: &mut Context<'_>) -> Result<(), String> where S: AsRef<[u8]> + ?Sized, { - if let Some(ref arg) = args.dump_ast { - let mut ast = parse_tokens(src, context)?; + if let Some(arg) = args.dump_ast { + let arg = arg.unwrap_or_default(); + let mut parser = boa_parser::Parser::new(Source::from_bytes(src)); + let dump = + if args.module { + let module = parser + .parse_module(context.interner_mut()) + .map_err(|e| format!("Uncaught SyntaxError: {e}"))?; + + match arg { + DumpFormat::Json => serde_json::to_string(&module) + .expect("could not convert AST to a JSON string"), + DumpFormat::JsonPretty => serde_json::to_string_pretty(&module) + .expect("could not convert AST to a pretty JSON string"), + DumpFormat::Debug => format!("{module:#?}"), + } + } else { + let mut script = parser + .parse_script(context.interner_mut()) + .map_err(|e| format!("Uncaught SyntaxError: {e}"))?; - if args.optimize { - context.optimize_statement_list(&mut ast); - } + if args.optimize { + context.optimize_statement_list(script.statements_mut()); + } - match arg { - Some(DumpFormat::Json) => println!( - "{}", - serde_json::to_string(&ast).expect("could not convert AST to a JSON string") - ), - Some(DumpFormat::JsonPretty) => println!( - "{}", - serde_json::to_string_pretty(&ast) - .expect("could not convert AST to a pretty JSON string") - ), - Some(DumpFormat::Debug) | None => println!("{ast:#?}"), - } + match arg { + DumpFormat::Json => serde_json::to_string(&script) + .expect("could not convert AST to a JSON string"), + DumpFormat::JsonPretty => serde_json::to_string_pretty(&script) + .expect("could not convert AST to a pretty JSON string"), + DumpFormat::Debug => format!("{script:#?}"), + } + }; + + println!("{dump}"); } Ok(()) @@ -263,8 +272,8 @@ fn generate_flowgraph( format: FlowgraphFormat, direction: Option, ) -> JsResult { - let ast = context.parse_script(Source::from_bytes(src))?; - let code = context.compile_script(&ast)?; + let script = Script::parse(Source::from_bytes(src), None, context)?; + let code = script.codeblock(context)?; let direction = match direction { Some(FlowgraphDirection::TopToBottom) | None => Direction::TopToBottom, @@ -327,11 +336,25 @@ fn evaluate_files( Ok(PromiseState::Fulfilled(_)) => {} Ok(PromiseState::Rejected(err)) => { eprintln!("Uncaught {}", err.display()); + + if let Ok(err) = JsError::from_opaque(err).try_native(context) { + if let Some(cause) = err.cause() { + eprintln!("\tCaused by: {cause}"); + } + } + } + Err(err) => { + eprintln!("Uncaught {err}"); + + if let Ok(err) = err.try_native(context) { + if let Some(cause) = err.cause() { + eprintln!("\tCaused by: {cause}"); + } + } } - Err(err) => eprintln!("Uncaught {err}"), } } else { - match context.eval_script(Source::from_bytes(&buffer)) { + match context.eval(Source::from_bytes(&buffer)) { Ok(v) => println!("{}", v.display()), Err(v) => eprintln!("Uncaught {v}"), } @@ -425,7 +448,7 @@ fn main() -> Result<(), io::Error> { Err(v) => eprintln!("Uncaught {v}"), } } else { - match context.eval_script(Source::from_bytes(line.trim_end())) { + match context.eval(Source::from_bytes(line.trim_end())) { Ok(v) => { println!("{}", v.display()); } diff --git a/boa_engine/benches/full.rs b/boa_engine/benches/full.rs index f59a92dca5..2478f187fb 100644 --- a/boa_engine/benches/full.rs +++ b/boa_engine/benches/full.rs @@ -2,7 +2,7 @@ use boa_engine::{ context::DefaultHooks, object::shape::RootShape, optimizer::OptimizerOptions, realm::Realm, - Context, Source, + script::Script, Context, Source, }; use criterion::{criterion_group, criterion_main, Criterion}; use std::hint::black_box; @@ -33,7 +33,13 @@ macro_rules! full_benchmarks { context.set_optimizer_options(OptimizerOptions::empty()); c.bench_function(concat!($id, " (Parser)"), move |b| { - b.iter(|| context.parse_script(black_box(Source::from_bytes(CODE)))) + b.iter(|| { + Script::parse( + black_box(Source::from_bytes(CODE)), + None, + &mut context, + ).unwrap() + }) }); } )* @@ -42,15 +48,19 @@ macro_rules! full_benchmarks { $( { static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); - let mut context = Context::default(); + let context = &mut Context::default(); // Disable optimizations context.set_optimizer_options(OptimizerOptions::empty()); - let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed"); + let script = Script::parse( + black_box(Source::from_bytes(CODE)), + None, + context, + ).unwrap(); c.bench_function(concat!($id, " (Compiler)"), move |b| { b.iter(|| { - context.compile_script(black_box(&statement_list)) + script.codeblock(context).unwrap() }) }); } @@ -60,16 +70,20 @@ macro_rules! full_benchmarks { $( { static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); - let mut context = Context::default(); + let context = &mut Context::default(); // Disable optimizations context.set_optimizer_options(OptimizerOptions::empty()); - let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed"); - let code_block = context.compile_script(&statement_list).unwrap(); + let script = Script::parse( + black_box(Source::from_bytes(CODE)), + None, + context, + ).unwrap(); + script.codeblock(context).unwrap(); c.bench_function(concat!($id, " (Execution)"), move |b| { b.iter(|| { - context.execute(black_box(code_block.clone())).unwrap() + script.evaluate(context).unwrap(); }) }); } diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 5881a0f069..0a4f0b3815 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -10,8 +10,14 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval use crate::{ - builtins::BuiltInObject, bytecompiler::ByteCompiler, context::intrinsics::Intrinsics, - environments::Environment, error::JsNativeError, object::JsObject, realm::Realm, vm::Opcode, + builtins::BuiltInObject, + bytecompiler::ByteCompiler, + context::intrinsics::Intrinsics, + environments::Environment, + error::JsNativeError, + object::JsObject, + realm::Realm, + vm::{CallFrame, Opcode}, Context, JsArgs, JsResult, JsString, JsValue, }; use boa_ast::operations::{contains, contains_arguments, ContainsSymbol}; @@ -229,7 +235,7 @@ impl Eval { let push_env = compiler.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); compiler.eval_declaration_instantiation(&body, strict)?; - compiler.compile_statement_list(&body, true, false); + compiler.compile_statement_list(body.statements(), true, false); let env_info = compiler.pop_compile_environment(); compiler.patch_jump_with_target(push_env.0, env_info.num_bindings); @@ -246,6 +252,11 @@ impl Eval { context.vm.environments.extend_outer_function_environment(); } - context.execute(code_block) + context.vm.push_frame(CallFrame::new(code_block)); + context.realm().resize_global_env(); + let record = context.run(); + context.vm.pop_frame(); + + record.consume() } } diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index b8b5c13fb2..2eeb1ab864 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -26,16 +26,15 @@ use crate::{ string::utf16, symbol::JsSymbol, value::IntegerOrInfinity, - vm::CodeBlock, + vm::{ActiveRunnable, CodeBlock}, Context, JsArgs, JsResult, JsString, JsValue, }; use boa_ast::{ - function::FormalParameterList, + function::{FormalParameterList, FunctionBody}, operations::{ all_private_identifiers_valid, bound_names, contains, lexically_declared_names, ContainsSymbol, }, - StatementList, }; use boa_gc::{self, custom_trace, Finalize, Gc, Trace}; use boa_interner::Sym; @@ -177,6 +176,9 @@ pub(crate) enum FunctionKind { /// The class object that this function is associated with. class_object: Option, + + /// The `[[ScriptOrModule]]` internal slot. + script_or_module: Option, }, /// A bytecode async function. @@ -192,6 +194,9 @@ pub(crate) enum FunctionKind { /// The class object that this function is associated with. class_object: Option, + + /// The `[[ScriptOrModule]]` internal slot. + script_or_module: Option, }, /// A bytecode generator function. @@ -207,6 +212,9 @@ pub(crate) enum FunctionKind { /// The class object that this function is associated with. class_object: Option, + + /// The `[[ScriptOrModule]]` internal slot. + script_or_module: Option, }, /// A bytecode async generator function. @@ -222,6 +230,9 @@ pub(crate) enum FunctionKind { /// The class object that this function is associated with. class_object: Option, + + /// The `[[ScriptOrModule]]` internal slot. + script_or_module: Option, }, } @@ -256,7 +267,16 @@ unsafe impl Trace for FunctionKind { custom_trace! {this, { match this { Self::Native { function, .. } => {mark(function)} - Self::Ordinary { code, environments, home_object, fields, private_methods, class_object, .. } => { + Self::Ordinary { + code, + environments, + home_object, + fields, + private_methods, + class_object, + script_or_module, + .. + } => { mark(code); mark(environments); mark(home_object); @@ -267,14 +287,16 @@ unsafe impl Trace for FunctionKind { mark(elem); } mark(class_object); + mark(script_or_module); } - Self::Async { code, environments, home_object, class_object } - | Self::Generator { code, environments, home_object, class_object} - | Self::AsyncGenerator { code, environments, home_object, class_object} => { + Self::Async { code, environments, home_object, class_object, script_or_module } + | Self::Generator { code, environments, home_object, class_object, script_or_module} + | Self::AsyncGenerator { code, environments, home_object, class_object, script_or_module} => { mark(code); mark(environments); mark(home_object); mark(class_object); + mark(script_or_module); } } }} @@ -753,7 +775,7 @@ impl BuiltInFunctionObject { .generator(true) .compile( &FormalParameterList::default(), - &StatementList::default(), + &FunctionBody::default(), context.realm().environment().compile_env(), context, ); @@ -771,7 +793,7 @@ impl BuiltInFunctionObject { } else { let code = FunctionCompiler::new().name(Sym::ANONYMOUS).compile( &FormalParameterList::default(), - &StatementList::default(), + &FunctionBody::default(), context.realm().environment().compile_env(), context, ); diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index 26fedf0cf3..20b80fef10 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -29,6 +29,7 @@ use crate::{ string::{utf16, CodePoint}, symbol::JsSymbol, value::IntegerOrInfinity, + vm::CallFrame, Context, JsArgs, JsResult, JsString, JsValue, }; use boa_gc::Gc; @@ -114,19 +115,25 @@ impl Json { // 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral. let mut parser = Parser::new(Source::from_bytes(&script_string)); parser.set_json_parse(); - let statement_list = parser.parse_script(context.interner_mut())?; + let script = parser.parse_script(context.interner_mut())?; let code_block = { let mut compiler = ByteCompiler::new( Sym::MAIN, - statement_list.strict(), + script.strict(), true, context.realm().environment().compile_env(), context, ); - compiler.compile_statement_list(&statement_list, true, false); + compiler.compile_statement_list(script.statements(), true, false); Gc::new(compiler.finish()) }; - let unfiltered = context.execute(code_block)?; + + context.vm.push_frame(CallFrame::new(code_block)); + context.realm().resize_global_env(); + let record = context.run(); + context.vm.pop_frame(); + + let unfiltered = record.consume()?; // 11. If IsCallable(reviver) is true, then if let Some(obj) = args.get_or_undefined(1).as_callable() { diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 000c8bc9b8..7ced9a8848 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -2333,7 +2333,7 @@ fn new_promise_reaction_job( }; // 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }. - NativeJob::with_realm(job, realm) + NativeJob::with_realm(job, realm, context) } /// More information: @@ -2387,5 +2387,5 @@ fn new_promise_resolve_thenable_job( }; // 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }. - NativeJob::with_realm(job, realm) + NativeJob::with_realm(job, realm, context) } diff --git a/boa_engine/src/bytecompiler/class.rs b/boa_engine/src/bytecompiler/class.rs index bd1ebbd53e..f4b9ea0cbc 100644 --- a/boa_engine/src/bytecompiler/class.rs +++ b/boa_engine/src/bytecompiler/class.rs @@ -47,7 +47,7 @@ impl ByteCompiler<'_, '_> { false, ); - compiler.compile_statement_list(expr.body(), false, false); + compiler.compile_statement_list(expr.body().statements(), false, false); let env_info = compiler.pop_compile_environment(); @@ -370,7 +370,7 @@ impl ByteCompiler<'_, '_> { let index = self.get_or_insert_private_name(*name); self.emit(Opcode::DefinePrivateField, &[index]); } - ClassElement::StaticBlock(statement_list) => { + ClassElement::StaticBlock(body) => { self.emit_opcode(Opcode::Dup); let mut compiler = ByteCompiler::new( Sym::EMPTY_STRING, @@ -384,14 +384,14 @@ impl ByteCompiler<'_, '_> { compiler.push_compile_environment(true); compiler.function_declaration_instantiation( - statement_list, + body, &FormalParameterList::default(), false, true, false, ); - compiler.compile_statement_list(statement_list, false, false); + compiler.compile_statement_list(body.statements(), false, false); let env_info = compiler.pop_compile_environment(); compiler.pop_compile_environment(); compiler.num_bindings = env_info.num_bindings; diff --git a/boa_engine/src/bytecompiler/declarations.rs b/boa_engine/src/bytecompiler/declarations.rs index d610132681..8eae34789f 100644 --- a/boa_engine/src/bytecompiler/declarations.rs +++ b/boa_engine/src/bytecompiler/declarations.rs @@ -5,14 +5,14 @@ use crate::{ }; use boa_ast::{ declaration::{Binding, LexicalDeclaration, VariableList}, - function::FormalParameterList, + function::{FormalParameterList, FunctionBody}, operations::{ - all_private_identifiers_valid, bound_names, lexically_scoped_declarations, - top_level_lexically_declared_names, top_level_var_declared_names, - top_level_var_scoped_declarations, VarScopedDeclaration, + all_private_identifiers_valid, bound_names, lexically_declared_names, + lexically_scoped_declarations, var_declared_names, var_scoped_declarations, + LexicallyScopedDeclaration, VarScopedDeclaration, }, visitor::NodeRef, - Declaration, StatementList, StatementListItem, + Declaration, Script, StatementListItem, }; use boa_interner::Sym; @@ -26,15 +26,12 @@ impl ByteCompiler<'_, '_> { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation - pub(crate) fn global_declaration_instantiation( - &mut self, - script: &StatementList, - ) -> JsResult<()> { + pub(crate) fn global_declaration_instantiation(&mut self, script: &Script) -> JsResult<()> { // 1. Let lexNames be the LexicallyDeclaredNames of script. - let lex_names = top_level_lexically_declared_names(script); + let lex_names = lexically_declared_names(script); // 2. Let varNames be the VarDeclaredNames of script. - let var_names = top_level_var_declared_names(script); + let var_names = var_declared_names(script); // 3. For each element name of lexNames, do for name in lex_names { @@ -71,7 +68,7 @@ impl ByteCompiler<'_, '_> { // 5. Let varDeclarations be the VarScopedDeclarations of script. // Note: VarScopedDeclarations for a Script node is TopLevelVarScopedDeclarations. - let var_declarations = top_level_var_scoped_declarations(script); + let var_declarations = var_scoped_declarations(script); // 6. Let functionsToInitialize be a new empty List. let mut functions_to_initialize = Vec::new(); @@ -162,7 +159,7 @@ impl ByteCompiler<'_, '_> { // b. If strict is false, then #[cfg(feature = "annex-b")] if !script.strict() { - let lex_names = top_level_lexically_declared_names(script); + let lex_names = lexically_declared_names(script); // i. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames. // ii. For each FunctionDeclaration f that is directly contained in the StatementList of a Block, CaseClause, @@ -208,7 +205,7 @@ impl ByteCompiler<'_, '_> { // 13. Let lexDeclarations be the LexicallyScopedDeclarations of script. // 14. Let privateEnv be null. // 15. For each element d of lexDeclarations, do - for statement in script.statements() { + for statement in &**script.statements() { // a. NOTE: Lexically declared names are only instantiated here but not initialized. // b. For each element dn of the BoundNames of d, do // i. If IsConstantDeclaration of d is true, then @@ -315,7 +312,8 @@ impl ByteCompiler<'_, '_> { // 3. For each element d of declarations, do for d in &declarations { // i. If IsConstantDeclaration of d is true, then - if let Declaration::Lexical(LexicalDeclaration::Const(d)) = d { + if let LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(d)) = d + { // a. For each element dn of the BoundNames of d, do for dn in bound_names::<'_, VariableList>(d) { // 1. Perform ! env.CreateImmutableBinding(dn, true). @@ -325,7 +323,7 @@ impl ByteCompiler<'_, '_> { // ii. Else, else { // a. For each element dn of the BoundNames of d, do - for dn in bound_names::<'_, Declaration>(d) { + for dn in d.bound_names() { #[cfg(not(feature = "annex-b"))] // 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6. env.create_mutable_binding(dn, false); @@ -352,16 +350,16 @@ impl ByteCompiler<'_, '_> { // TODO: Support B.3.2.6. for d in &declarations { match d { - Declaration::Function(function) => { + LexicallyScopedDeclaration::Function(function) => { self.function(function.into(), NodeKind::Declaration, false); } - Declaration::Generator(function) => { + LexicallyScopedDeclaration::Generator(function) => { self.function(function.into(), NodeKind::Declaration, false); } - Declaration::AsyncFunction(function) => { + LexicallyScopedDeclaration::AsyncFunction(function) => { self.function(function.into(), NodeKind::Declaration, false); } - Declaration::AsyncGenerator(function) => { + LexicallyScopedDeclaration::AsyncGenerator(function) => { self.function(function.into(), NodeKind::Declaration, false); } _ => {} @@ -379,7 +377,7 @@ impl ByteCompiler<'_, '_> { /// [spec]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation pub(crate) fn eval_declaration_instantiation( &mut self, - body: &StatementList, + body: &Script, strict: bool, ) -> JsResult<()> { let var_environment_is_global = self @@ -390,12 +388,12 @@ impl ByteCompiler<'_, '_> { && !strict; // 2. Let varDeclarations be the VarScopedDeclarations of body. - let var_declarations = top_level_var_scoped_declarations(body); + let var_declarations = var_scoped_declarations(body); // 3. If strict is false, then if !strict { // 1. Let varNames be the VarDeclaredNames of body. - let var_names = top_level_var_declared_names(body); + let var_names = var_declared_names(body); // a. If varEnv is a Global Environment Record, then // i. For each element name of varNames, do @@ -494,7 +492,7 @@ impl ByteCompiler<'_, '_> { // 11. If strict is false, then #[cfg(feature = "annex-b")] if !strict { - let lexically_declared_names = top_level_lexically_declared_names(body); + let lexically_declared_names = lexically_declared_names(body); // a. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames. // b. For each FunctionDeclaration f that is directly contained in the StatementList @@ -620,7 +618,7 @@ impl ByteCompiler<'_, '_> { // 15. Let lexDeclarations be the LexicallyScopedDeclarations of body. // 16. For each element d of lexDeclarations, do - for statement in body.statements() { + for statement in &**body.statements() { // a. NOTE: Lexically declared names are only instantiated here but not initialized. // b. For each element dn of the BoundNames of d, do // i. If IsConstantDeclaration of d is true, then @@ -774,7 +772,7 @@ impl ByteCompiler<'_, '_> { /// [spec]: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation pub(crate) fn function_declaration_instantiation( &mut self, - code: &StatementList, + body: &FunctionBody, formals: &FormalParameterList, arrow: bool, strict: bool, @@ -801,13 +799,13 @@ impl ByteCompiler<'_, '_> { let has_parameter_expressions = formals.has_expressions(); // 9. Let varNames be the VarDeclaredNames of code. - let var_names = top_level_var_declared_names(code); + let var_names = var_declared_names(body); // 10. Let varDeclarations be the VarScopedDeclarations of code. - let var_declarations = top_level_var_scoped_declarations(code); + let var_declarations = var_scoped_declarations(body); // 11. Let lexicalNames be the LexicallyDeclaredNames of code. - let lexical_names = top_level_lexically_declared_names(code); + let lexical_names = lexically_declared_names(body); // 12. Let functionNames be a new empty List. let mut function_names = Vec::new(); @@ -1064,7 +1062,7 @@ impl ByteCompiler<'_, '_> { if !strict { // a. For each FunctionDeclaration f that is directly contained in the StatementList // of a Block, CaseClause, or DefaultClause, do - for f in annex_b_function_declarations_names(code) { + for f in annex_b_function_declarations_names(body) { // i. Let F be StringValue of the BindingIdentifier of f. // ii. If replacing the FunctionDeclaration f with a VariableStatement that has F // as a BindingIdentifier would not produce any Early Errors @@ -1120,7 +1118,7 @@ impl ByteCompiler<'_, '_> { // 1. Perform ! lexEnv.CreateImmutableBinding(dn, true). // ii. Else, // 1. Perform ! lexEnv.CreateMutableBinding(dn, false). - for statement in code.statements() { + for statement in &**body.statements() { if let StatementListItem::Declaration(declaration) = statement { match declaration { Declaration::Class(class) => { diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index 2203c95d4c..2351dd6e47 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -320,6 +320,13 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::Pop); } } + Expression::ImportCall(import) => { + self.compile_expr(import.argument(), true); + self.emit_opcode(Opcode::ImportCall); + if !use_expr { + self.emit_opcode(Opcode::Pop); + } + } Expression::NewTarget => { if use_expr { self.emit_opcode(Opcode::PushNewTarget); diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs index d500d47553..f1a40bc7db 100644 --- a/boa_engine/src/bytecompiler/function.rs +++ b/boa_engine/src/bytecompiler/function.rs @@ -5,7 +5,7 @@ use crate::{ vm::{CodeBlock, Opcode}, Context, }; -use boa_ast::{function::FormalParameterList, StatementList}; +use boa_ast::function::{FormalParameterList, FunctionBody}; use boa_gc::{Gc, GcRefCell}; use boa_interner::Sym; @@ -87,7 +87,7 @@ impl FunctionCompiler { pub(crate) fn compile( mut self, parameters: &FormalParameterList, - body: &StatementList, + body: &FunctionBody, outer_env: Gc>, context: &mut Context<'_>, ) -> Gc { @@ -125,7 +125,7 @@ impl FunctionCompiler { self.generator, ); - compiler.compile_statement_list(body, false, false); + compiler.compile_statement_list(body.statements(), false, false); if let Some(env_labels) = env_labels { let env_info = compiler.pop_compile_environment(); diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 741b85f055..330fa8330b 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -27,7 +27,7 @@ use boa_ast::{ }, function::{ ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, - FormalParameterList, Function, Generator, PrivateName, + FormalParameterList, Function, FunctionBody, Generator, PrivateName, }, pattern::Pattern, Declaration, Expression, Statement, StatementList, StatementListItem, @@ -64,7 +64,7 @@ pub(crate) struct FunctionSpec<'a> { kind: FunctionKind, pub(crate) name: Option, parameters: &'a FormalParameterList, - body: &'a StatementList, + body: &'a FunctionBody, has_binding_identifier: bool, } diff --git a/boa_engine/src/context/hooks.rs b/boa_engine/src/context/hooks.rs index 34024854de..e6cd49c6be 100644 --- a/boa_engine/src/context/hooks.rs +++ b/boa_engine/src/context/hooks.rs @@ -41,7 +41,7 @@ use super::intrinsics::Intrinsics; /// } /// let hooks: &dyn HostHooks = &Hooks; // Can have additional state. /// let context = &mut ContextBuilder::new().host_hooks(hooks).build().unwrap(); -/// let result = context.eval_script(Source::from_bytes(r#"eval("let a = 5")"#)); +/// let result = context.eval(Source::from_bytes(r#"eval("let a = 5")"#)); /// assert_eq!(result.unwrap_err().to_string(), "TypeError: eval calls not available"); /// ``` /// diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 5d8551a820..d6b517784f 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -18,7 +18,6 @@ use std::{io::Read, path::Path, rc::Rc}; use crate::{ builtins, - bytecompiler::ByteCompiler, class::{Class, ClassBuilder}, job::{JobQueue, NativeJob, SimpleJobQueue}, module::{ModuleLoader, SimpleModuleLoader}, @@ -27,13 +26,12 @@ use crate::{ optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, - vm::{CallFrame, CodeBlock, Vm}, + script::Script, + vm::{CallFrame, Vm}, JsResult, JsValue, Source, }; use boa_ast::{expression::Identifier, StatementList}; -use boa_gc::Gc; -use boa_interner::{Interner, Sym}; -use boa_parser::Parser; +use boa_interner::Interner; use boa_profiler::Profiler; use crate::vm::RuntimeLimits; @@ -57,18 +55,18 @@ use crate::vm::RuntimeLimits; /// }; /// /// let script = r#" -/// function test(arg1) { -/// if(arg1 != null) { -/// return arg1.x; +/// function test(arg1) { +/// if(arg1 != null) { +/// return arg1.x; +/// } +/// return 112233; /// } -/// return 112233; -/// } /// "#; /// /// let mut context = Context::default(); /// /// // Populate the script definition to the context. -/// context.eval_script(Source::from_bytes(script)).unwrap(); +/// context.eval(Source::from_bytes(script)).unwrap(); /// /// // Create an object that can be used in eval calls. /// let arg = ObjectInitializer::new(&mut context) @@ -76,7 +74,7 @@ use crate::vm::RuntimeLimits; /// .build(); /// context.register_global_property("arg", arg, Attribute::all()); /// -/// let value = context.eval_script(Source::from_bytes("test(arg)")).unwrap(); +/// let value = context.eval(Source::from_bytes("test(arg)")).unwrap(); /// /// assert_eq!(value.as_number(), Some(12.0)) /// ``` @@ -153,7 +151,7 @@ impl<'host> Context<'host> { ContextBuilder::default() } - /// Evaluates the given script `src` by compiling down to bytecode, then interpreting the + /// Evaluates the given source by compiling down to bytecode, then interpreting the /// bytecode into a value. /// /// # Examples @@ -162,7 +160,7 @@ impl<'host> Context<'host> { /// let mut context = Context::default(); /// /// let source = Source::from_bytes("1 + 3"); - /// let value = context.eval_script(source).unwrap(); + /// let value = context.eval(source).unwrap(); /// /// assert!(value.is_number()); /// assert_eq!(value.as_number().unwrap(), 4.0); @@ -171,12 +169,10 @@ impl<'host> Context<'host> { /// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`] /// on the context or [`JobQueue::run_jobs`] on the provided queue to run them. #[allow(clippy::unit_arg, clippy::drop_copy)] - pub fn eval_script(&mut self, src: Source<'_, R>) -> JsResult { + pub fn eval(&mut self, src: Source<'_, R>) -> JsResult { let main_timer = Profiler::global().start_event("Script evaluation", "Main"); - let script = self.parse_script(src)?; - let code_block = self.compile_script(&script)?; - let result = self.execute(code_block); + let result = Script::parse(src, None, self)?.evaluate(self); // The main_timer needs to be dropped before the Profiler is. drop(main_timer); @@ -194,61 +190,6 @@ impl<'host> Context<'host> { optimizer.apply(statement_list) } - /// Parse the given source script. - pub fn parse_script(&mut self, src: Source<'_, R>) -> JsResult { - let _timer = Profiler::global().start_event("Script parsing", "Main"); - let mut parser = Parser::new(src); - parser.set_identifier(self.next_parser_identifier()); - if self.strict { - parser.set_strict(); - } - let mut result = parser.parse_script(&mut self.interner)?; - if !self.optimizer_options().is_empty() { - self.optimize_statement_list(&mut result); - } - Ok(result) - } - - /// Compile the script AST into a `CodeBlock` ready to be executed by the VM. - pub fn compile_script(&mut self, statement_list: &StatementList) -> JsResult> { - let _timer = Profiler::global().start_event("Script compilation", "Main"); - - let mut compiler = ByteCompiler::new( - Sym::MAIN, - statement_list.strict(), - false, - self.realm.environment().compile_env(), - self, - ); - compiler.global_declaration_instantiation(statement_list)?; - compiler.compile_statement_list(statement_list, true, false); - Ok(Gc::new(compiler.finish())) - } - - /// Call the VM with a `CodeBlock` and return the result. - /// - /// Since this function receives a `Gc`, cloning the code is very cheap, since it's - /// just a pointer copy. Therefore, if you'd like to execute the same `CodeBlock` multiple - /// times, there is no need to re-compile it, and you can just call `clone()` on the - /// `Gc` returned by the [`Context::compile_script`] function. - /// - /// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`] - /// on the context or [`JobQueue::run_jobs`] on the provided queue to run them. - pub fn execute(&mut self, code_block: Gc) -> JsResult { - let _timer = Profiler::global().start_event("Execution", "Main"); - - self.vm.push_frame(CallFrame::new(code_block)); - - // TODO: Here should be https://tc39.es/ecma262/#sec-globaldeclarationinstantiation - - self.realm().resize_global_env(); - let record = self.run(); - self.vm.pop_frame(); - self.clear_kept_objects(); - - record.consume() - } - /// Register a global property. /// /// It will return an error if the property is already defined. @@ -737,6 +678,11 @@ impl Context<'_> { // 6. Return true. Ok(true) } + + /// Returns `true` if this context is in strict mode. + pub(crate) const fn is_strict(&self) -> bool { + self.strict + } } impl<'host> Context<'host> { diff --git a/boa_engine/src/job.rs b/boa_engine/src/job.rs index 45d7c58a94..7e9a2a1bf6 100644 --- a/boa_engine/src/job.rs +++ b/boa_engine/src/job.rs @@ -22,6 +22,7 @@ use std::{any::Any, cell::RefCell, collections::VecDeque, fmt::Debug, future::Fu use crate::{ object::{JsFunction, NativeObject}, realm::Realm, + vm::ActiveRunnable, Context, JsResult, JsValue, }; use boa_gc::{Finalize, Trace}; @@ -68,6 +69,7 @@ pub struct NativeJob { #[allow(clippy::type_complexity)] f: Box) -> JsResult>, realm: Option, + active_runnable: Option, } impl Debug for NativeJob { @@ -85,17 +87,19 @@ impl NativeJob { Self { f: Box::new(f), realm: None, + active_runnable: None, } } /// Creates a new `NativeJob` from a closure and an execution realm. - pub fn with_realm(f: F, realm: Realm) -> Self + pub fn with_realm(f: F, realm: Realm, context: &mut Context<'_>) -> Self where F: FnOnce(&mut Context<'_>) -> JsResult + 'static, { Self { f: Box::new(f), realm: Some(realm), + active_runnable: context.vm.active_runnable.clone(), } } @@ -110,11 +114,24 @@ impl NativeJob { /// /// If the native job has an execution realm defined, this sets the running execution /// context to the realm's before calling the inner closure, and resets it after execution. - pub fn call(self, context: &mut Context<'_>) -> JsResult { + pub fn call(mut self, context: &mut Context<'_>) -> JsResult { + // If realm is not null, each time job is invoked the implementation must perform + // implementation-defined steps such that execution is prepared to evaluate ECMAScript + // code at the time of job's invocation. if let Some(realm) = self.realm { let old_realm = context.enter_realm(realm); + + // Let scriptOrModule be GetActiveScriptOrModule() at the time HostEnqueuePromiseJob is + // invoked. If realm is not null, each time job is invoked the implementation must + // perform implementation-defined steps such that scriptOrModule is the active script or + // module at the time of job's invocation. + std::mem::swap(&mut context.vm.active_runnable, &mut self.active_runnable); + let result = (self.f)(context); + context.enter_realm(old_realm); + std::mem::swap(&mut context.vm.active_runnable, &mut self.active_runnable); + result } else { (self.f)(context) @@ -173,8 +190,22 @@ impl JobCallback { pub trait JobQueue { /// [`HostEnqueuePromiseJob ( job, realm )`][spec]. /// - /// Enqueues a [`NativeJob`] on the job queue. Note that host-defined [Jobs] need to satisfy - /// a set of requirements for them to be spec-compliant. + /// Enqueues a [`NativeJob`] on the job queue. + /// + /// # Requirements + /// + /// Per the [spec]: + /// > An implementation of `HostEnqueuePromiseJob` must conform to the requirements in [9.5][Jobs] as well as the + /// following: + /// > - If `realm` is not null, each time `job` is invoked the implementation must perform implementation-defined steps + /// such that execution is prepared to evaluate ECMAScript code at the time of job's invocation. + /// > - Let `scriptOrModule` be `GetActiveScriptOrModule()` at the time `HostEnqueuePromiseJob` is invoked. If realm + /// is not null, each time job is invoked the implementation must perform implementation-defined steps such that + /// `scriptOrModule` is the active script or module at the time of job's invocation. + /// > - Jobs must run in the same order as the `HostEnqueuePromiseJob` invocations that scheduled them. + /// + /// Of all the requirements, Boa guarantees the first two by its internal implementation of `NativeJob`, meaning + /// the implementer must only guarantee that jobs are run in the same order as they're enqueued. /// /// [spec]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob /// [Jobs]: https://tc39.es/ecma262/#sec-jobs diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index d7d8a18550..1080b1708d 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -22,7 +22,7 @@ //! let mut context = Context::default(); //! //! // Parse the source code -//! match context.eval_script(Source::from_bytes(js_code)) { +//! match context.eval(Source::from_bytes(js_code)) { //! Ok(res) => { //! println!( //! "{}", @@ -137,6 +137,7 @@ pub mod object; pub mod optimizer; pub mod property; pub mod realm; +pub mod script; pub mod string; pub mod symbol; // pub(crate) mod tagged; @@ -153,6 +154,7 @@ pub mod prelude { module::Module, native_function::NativeFunction, object::JsObject, + script::Script, Context, JsBigInt, JsResult, JsString, JsValue, }; pub use boa_parser::Source; @@ -169,6 +171,7 @@ pub use crate::{ module::Module, native_function::NativeFunction, object::JsObject, + script::Script, string::JsString, symbol::JsSymbol, value::JsValue, @@ -336,7 +339,7 @@ fn run_test_actions(actions: impl IntoIterator) { fn run_test_actions_with(actions: impl IntoIterator, context: &mut Context<'_>) { #[track_caller] fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult { - context.eval_script(Source::from_bytes(source)) + context.eval(Source::from_bytes(source)) } #[track_caller] diff --git a/boa_engine/src/module/mod.rs b/boa_engine/src/module/mod.rs index a1b25e246e..6db077ffa4 100644 --- a/boa_engine/src/module/mod.rs +++ b/boa_engine/src/module/mod.rs @@ -41,6 +41,8 @@ use boa_parser::{Parser, Source}; use boa_profiler::Profiler; use crate::object::FunctionObjectBuilder; +use crate::script::Script; +use crate::vm::ActiveRunnable; use crate::{ builtins::promise::{PromiseCapability, PromiseState}, environments::DeclarativeEnvironment, @@ -51,12 +53,23 @@ use crate::{ use crate::{js_string, JsNativeError, NativeFunction}; /// The referrer from which a load request of a module originates. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Referrer { /// A [**Source Text Module Record**](https://tc39.es/ecma262/#sec-source-text-module-records). Module(Module), /// A [**Realm**](https://tc39.es/ecma262/#sec-code-realms). - Realm(Realm), // TODO: script + Realm(Realm), + /// A [**Script Record**](https://tc39.es/ecma262/#sec-script-records) + Script(Script), +} + +impl From for Referrer { + fn from(value: ActiveRunnable) -> Self { + match value { + ActiveRunnable::Script(script) => Self::Script(script), + ActiveRunnable::Module(module) => Self::Module(module), + } + } } /// Module loading related host hooks. @@ -182,7 +195,7 @@ impl ModuleLoader for SimpleModuleLoader { .with_cause(JsError::from_opaque(js_string!(err.to_string()).into())) })?; let module = Module::parse(source, None, context).map_err(|err| { - JsNativeError::error() + JsNativeError::syntax() .with_message(format!("could not parse module `{}`", short_path.display())) .with_cause(err) })?; diff --git a/boa_engine/src/module/source.rs b/boa_engine/src/module/source.rs index bc4afc52a7..bb8505612f 100644 --- a/boa_engine/src/module/source.rs +++ b/boa_engine/src/module/source.rs @@ -7,9 +7,8 @@ use boa_ast::{ }, operations::{ bound_names, contains, lexically_scoped_declarations, var_scoped_declarations, - ContainsSymbol, + ContainsSymbol, LexicallyScopedDeclaration, }, - Declaration, ModuleItemList, }; use boa_gc::{custom_trace, empty_trace, Finalize, Gc, GcRefCell, Trace}; use boa_interner::Sym; @@ -22,7 +21,7 @@ use crate::{ module::ModuleKind, object::{FunctionObjectBuilder, JsPromise, RecursionLimiter}, realm::Realm, - vm::{CallFrame, CodeBlock, CompletionRecord, Opcode}, + vm::{ActiveRunnable, CallFrame, CodeBlock, CompletionRecord, Opcode}, Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsString, JsValue, NativeFunction, }; @@ -275,7 +274,7 @@ struct Inner { struct ModuleCode { has_tla: bool, requested_modules: FxHashSet, - node: ModuleItemList, + source: boa_ast::Module, import_entries: Vec, local_export_entries: Vec, indirect_export_entries: Vec, @@ -297,16 +296,16 @@ impl SourceTextModule { .expect("parent module must be initialized") } - /// Creates a new `SourceTextModule` from a parsed `ModuleItemList`. + /// Creates a new `SourceTextModule` from a parsed `ModuleSource`. /// /// Contains part of the abstract operation [`ParseModule`][parse]. /// /// [parse]: https://tc39.es/ecma262/#sec-parsemodule - pub(super) fn new(code: ModuleItemList) -> Self { + pub(super) fn new(code: boa_ast::Module) -> Self { // 3. Let requestedModules be the ModuleRequests of body. - let requested_modules = code.requests(); + let requested_modules = code.items().requests(); // 4. Let importEntries be ImportEntries of body. - let import_entries = code.import_entries(); + let import_entries = code.items().import_entries(); // 5. Let importedBoundNames be ImportedLocalNames(importEntries). // Can be ignored because this is just a simple `Iter::map` @@ -319,7 +318,7 @@ impl SourceTextModule { let mut star_export_entries = Vec::new(); // 10. For each ExportEntry Record ee of exportEntries, do - for ee in code.export_entries() { + for ee in code.items().export_entries() { match ee { // a. If ee.[[ModuleRequest]] is null, then ExportEntry::Ordinary(entry) => { @@ -389,7 +388,7 @@ impl SourceTextModule { async_parent_modules: GcRefCell::default(), import_meta: GcRefCell::default(), code: ModuleCode { - node: code, + source: code, requested_modules, has_tla, import_entries, @@ -615,8 +614,8 @@ impl SourceTextModule { // iii. Else, // 1. Assert: module imports a specific binding for this export. // 2. Return importedModule.ResolveExport(e.[[ImportName]], resolveSet). - ReExportImportName::Name(_) => { - imported_module.resolve_export(export_name, resolve_set) + ReExportImportName::Name(name) => { + imported_module.resolve_export(name, resolve_set) } }; } @@ -1470,7 +1469,7 @@ impl SourceTextModule { // 18. Let code be module.[[ECMAScriptCode]]. // 19. Let varDeclarations be the VarScopedDeclarations of code. - let var_declarations = var_scoped_declarations(&self.inner.code.node); + let var_declarations = var_scoped_declarations(&self.inner.code.source); // 20. Let declaredVarNames be a new empty List. let mut declared_var_names = Vec::new(); // 21. For each element d of varDeclarations, do @@ -1494,80 +1493,60 @@ impl SourceTextModule { // 22. Let lexDeclarations be the LexicallyScopedDeclarations of code. // 23. Let privateEnv be null. - let lex_declarations = lexically_scoped_declarations(&self.inner.code.node); + let lex_declarations = lexically_scoped_declarations(&self.inner.code.source); // 24. For each element d of lexDeclarations, do - for declaration in lex_declarations { - match &declaration { - // i. If IsConstantDeclaration of d is true, then - Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { - // a. For each element dn of the BoundNames of d, do - for name in bound_names(declaration) { - // 1. Perform ! env.CreateImmutableBinding(dn, true). - compiler.create_immutable_binding(name, true); - } + for declaration in &lex_declarations { + // i. If IsConstantDeclaration of d is true, then + if let LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const( + decl, + )) = declaration + { + // a. For each element dn of the BoundNames of d, do + for name in bound_names(decl) { + // 1. Perform ! env.CreateImmutableBinding(dn, true). + compiler.create_immutable_binding(name, true); } + } else { // ii. Else, - Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { - // a. For each element dn of the BoundNames of d, do - for name in bound_names(declaration) { - // 1. Perform ! env.CreateMutableBinding(dn, false). - compiler.create_mutable_binding(name, false); - } - } - // iii. If d is either a FunctionDeclaration, a GeneratorDeclaration, an - // AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then - Declaration::Function(function) => { - // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. - // 2. Perform ! env.InitializeBinding(dn, fo). - for name in bound_names(function) { - compiler.create_mutable_binding(name, false); - } - compiler.function(function.into(), NodeKind::Declaration, false); - } - Declaration::Generator(function) => { - // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. - // 2. Perform ! env.InitializeBinding(dn, fo). - for name in bound_names(function) { - compiler.create_mutable_binding(name, false); - } - compiler.function(function.into(), NodeKind::Declaration, false); - } - Declaration::AsyncFunction(function) => { - // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. - // 2. Perform ! env.InitializeBinding(dn, fo). - for name in bound_names(function) { - compiler.create_mutable_binding(name, false); - } - compiler.function(function.into(), NodeKind::Declaration, false); - } - Declaration::AsyncGenerator(function) => { - // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. - // 2. Perform ! env.InitializeBinding(dn, fo). - for name in bound_names(function) { - compiler.create_mutable_binding(name, false); - } - compiler.function(function.into(), NodeKind::Declaration, false); - } - Declaration::Class(class) => { - // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. - // 2. Perform ! env.InitializeBinding(dn, fo). - for name in bound_names(class) { - compiler.create_mutable_binding(name, false); - } + // a. For each element dn of the BoundNames of d, do + for name in declaration.bound_names() { + // 1. Perform ! env.CreateMutableBinding(dn, false). + compiler.create_mutable_binding(name, false); } } + + // iii. If d is either a FunctionDeclaration, a GeneratorDeclaration, an + // AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then + // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. + // 2. Perform ! env.InitializeBinding(dn, fo). + let spec = match declaration { + LexicallyScopedDeclaration::Function(f) => f.into(), + LexicallyScopedDeclaration::Generator(g) => g.into(), + LexicallyScopedDeclaration::AsyncFunction(af) => af.into(), + LexicallyScopedDeclaration::AsyncGenerator(ag) => ag.into(), + LexicallyScopedDeclaration::Class(_) + | LexicallyScopedDeclaration::LexicalDeclaration(_) + | LexicallyScopedDeclaration::AssignmentExpression(_) => continue, + }; + + compiler.function(spec, NodeKind::Declaration, false); } - compiler.compile_module_item_list(&self.inner.code.node); + compiler.compile_module_item_list(self.inner.code.source.items()); Gc::new(compiler.finish()) }; // 8. Let moduleContext be a new ECMAScript code execution context. - // 12. Set the ScriptOrModule of moduleContext to module. let mut envs = EnvironmentStack::new(global_env); envs.push_module(module_compile_env); + // 12. Set the ScriptOrModule of moduleContext to module. + let active_runnable = context + .vm + .active_runnable + .replace(ActiveRunnable::Module(parent.clone())); + // 13. Set the VariableEnvironment of moduleContext to module.[[Environment]]. // 14. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. // 15. Set the PrivateEnvironment of moduleContext to null. @@ -1623,6 +1602,7 @@ impl SourceTextModule { std::mem::swap(&mut context.vm.environments, &mut envs); context.vm.stack = stack; context.vm.active_function = active_function; + context.vm.active_runnable = active_runnable; context.swap_realm(&mut realm); debug_assert!(envs.current().as_declarative().is_some()); @@ -1674,6 +1654,11 @@ impl SourceTextModule { callframe.promise_capability = capability; // 4. Set the ScriptOrModule of moduleContext to module. + let active_runnable = context + .vm + .active_runnable + .replace(ActiveRunnable::Module(self.parent())); + // 5. Assert: module has been linked and declarations in its module environment have been instantiated. // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]]. // 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. @@ -1700,6 +1685,7 @@ impl SourceTextModule { std::mem::swap(&mut context.vm.environments, &mut environments); context.vm.stack = stack; context.vm.active_function = function; + context.vm.active_runnable = active_runnable; context.swap_realm(&mut realm); context.vm.pop_frame(); @@ -1712,6 +1698,11 @@ impl SourceTextModule { Ok(()) } } + + /// Gets the loaded modules of this module. + pub(crate) fn loaded_modules(&self) -> &GcRefCell> { + &self.inner.loaded_modules + } } /// Abstract operation [`AsyncModuleExecutionFulfilled ( module )`][spec]. diff --git a/boa_engine/src/object/builtins/jspromise.rs b/boa_engine/src/object/builtins/jspromise.rs index 735961c2fd..b4efdddf4f 100644 --- a/boa_engine/src/object/builtins/jspromise.rs +++ b/boa_engine/src/object/builtins/jspromise.rs @@ -229,7 +229,7 @@ impl JsPromise { /// # fn main() -> Result<(), Box> { /// let context = &mut Context::default(); /// - /// let promise = context.eval_script(Source::from_bytes("new Promise((resolve, reject) => resolve())"))?; + /// let promise = context.eval(Source::from_bytes("new Promise((resolve, reject) => resolve())"))?; /// let promise = promise.as_object().cloned().unwrap(); /// /// let promise = JsPromise::from_object(promise)?; diff --git a/boa_engine/src/script.rs b/boa_engine/src/script.rs new file mode 100644 index 0000000000..737337bfea --- /dev/null +++ b/boa_engine/src/script.rs @@ -0,0 +1,163 @@ +//! Boa's implementation of ECMAScript's Scripts. +//! +//! This module contains the [`Script`] type, which represents a [**Script Record**][script]. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-scripts +//! [script]: https://tc39.es/ecma262/#sec-script-records + +use std::io::Read; + +use boa_gc::{Finalize, Gc, GcRefCell, Trace}; +use boa_interner::Sym; +use boa_parser::{Parser, Source}; +use boa_profiler::Profiler; +use rustc_hash::FxHashMap; + +use crate::{ + bytecompiler::ByteCompiler, + realm::Realm, + vm::{ActiveRunnable, CallFrame, CodeBlock}, + Context, JsResult, JsString, JsValue, Module, +}; + +/// ECMAScript's [**Script Record**][spec]. +/// +/// [spec]: https://tc39.es/ecma262/#sec-script-records +#[derive(Clone, Trace, Finalize)] +pub struct Script { + inner: Gc, +} + +impl std::fmt::Debug for Script { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Script") + .field("realm", &self.inner.realm.addr()) + .field("code", &self.inner.source) + .field("loaded_modules", &self.inner.loaded_modules) + .field("host_defined", &self.inner.host_defined) + .finish() + } +} + +#[derive(Trace, Finalize)] +struct Inner { + realm: Realm, + #[unsafe_ignore_trace] + source: boa_ast::Script, + codeblock: GcRefCell>>, + loaded_modules: GcRefCell>, + host_defined: (), +} + +impl Script { + /// Gets the realm of this script. + pub fn realm(&self) -> &Realm { + &self.inner.realm + } + + /// Gets the loaded modules of this script. + pub(crate) fn loaded_modules(&self) -> &GcRefCell> { + &self.inner.loaded_modules + } + + /// Abstract operation [`ParseScript ( sourceText, realm, hostDefined )`][spec]. + /// + /// Parses the provided `src` as an ECMAScript script, returning an error if parsing fails. + /// + /// [spec]: https://tc39.es/ecma262/#sec-parse-script + pub fn parse( + src: Source<'_, R>, + realm: Option, + context: &mut Context<'_>, + ) -> JsResult { + let _timer = Profiler::global().start_event("Script parsing", "Main"); + let mut parser = Parser::new(src); + parser.set_identifier(context.next_parser_identifier()); + if context.is_strict() { + parser.set_strict(); + } + let mut code = parser.parse_script(context.interner_mut())?; + if !context.optimizer_options().is_empty() { + context.optimize_statement_list(code.statements_mut()); + } + + Ok(Self { + inner: Gc::new(Inner { + realm: realm.unwrap_or_else(|| context.realm().clone()), + source: code, + codeblock: GcRefCell::default(), + loaded_modules: GcRefCell::default(), + host_defined: (), + }), + }) + } + + /// Compiles the codeblock of this script. + /// + /// This is a no-op if this has been called previously. + pub fn codeblock(&self, context: &mut Context<'_>) -> JsResult> { + let mut codeblock = self.inner.codeblock.borrow_mut(); + + if let Some(codeblock) = &*codeblock { + return Ok(codeblock.clone()); + }; + + let _timer = Profiler::global().start_event("Script compilation", "Main"); + + let mut compiler = ByteCompiler::new( + Sym::MAIN, + self.inner.source.strict(), + false, + self.inner.realm.environment().compile_env(), + context, + ); + // TODO: move to `Script::evaluate` to make this operation infallible. + compiler.global_declaration_instantiation(&self.inner.source)?; + compiler.compile_statement_list(self.inner.source.statements(), true, false); + + let cb = Gc::new(compiler.finish()); + + *codeblock = Some(cb.clone()); + + Ok(cb) + } + + /// Evaluates this script and returns its result. + /// + /// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`] + /// on the context or [`JobQueue::run_jobs`] on the provided queue to run them. + /// + /// [`JobQueue::run_jobs`]: crate::job::JobQueue::run_jobs + pub fn evaluate(&self, context: &mut Context<'_>) -> JsResult { + let _timer = Profiler::global().start_event("Execution", "Main"); + + let codeblock = self.codeblock(context)?; + + let old_realm = context.enter_realm(self.inner.realm.clone()); + let active_function = context.vm.active_function.take(); + let stack = std::mem::take(&mut context.vm.stack); + let old_active = context + .vm + .active_runnable + .replace(ActiveRunnable::Script(self.clone())); + context.vm.push_frame(CallFrame::new(codeblock)); + + // TODO: Here should be https://tc39.es/ecma262/#sec-globaldeclarationinstantiation + + self.realm().resize_global_env(); + let record = context.run(); + context.vm.pop_frame(); + + context.vm.stack = stack; + context.vm.active_function = active_function; + context.vm.active_runnable = old_active; + context.enter_realm(old_realm); + + context.clear_kept_objects(); + + record.consume() + } +} diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index c346739ab7..30d79d1791 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -511,6 +511,7 @@ impl CodeBlock { | Opcode::IsObject | Opcode::SetNameByLocator | Opcode::PopPrivateEnvironment + | Opcode::ImportCall | Opcode::Nop => String::new(), } } @@ -612,6 +613,8 @@ pub(crate) fn create_function_object( let length: JsValue = code.length.into(); + let script_or_module = context.vm.active_runnable.clone(); + let function = if r#async { Function::new( FunctionKind::Async { @@ -619,6 +622,7 @@ pub(crate) fn create_function_object( environments: context.vm.environments.clone(), home_object: None, class_object: None, + script_or_module, }, context.realm().clone(), ) @@ -632,6 +636,7 @@ pub(crate) fn create_function_object( fields: ThinVec::new(), private_methods: ThinVec::new(), class_object: None, + script_or_module, }, context.realm().clone(), ) @@ -693,12 +698,15 @@ pub(crate) fn create_function_object_fast( let length: JsValue = code.length.into(); + let script_or_module = context.vm.active_runnable.clone(); + let function = if r#async { FunctionKind::Async { code, environments: context.vm.environments.clone(), home_object: None, class_object: None, + script_or_module, } } else { FunctionKind::Ordinary { @@ -709,6 +717,7 @@ pub(crate) fn create_function_object_fast( fields: ThinVec::new(), private_methods: ThinVec::new(), class_object: None, + script_or_module, } }; @@ -799,6 +808,8 @@ pub(crate) fn create_generator_function_object( ObjectData::ordinary(), ); + let script_or_module = context.vm.active_runnable.clone(); + let constructor = if r#async { let function = Function::new( FunctionKind::AsyncGenerator { @@ -806,6 +817,7 @@ pub(crate) fn create_generator_function_object( environments: context.vm.environments.clone(), home_object: None, class_object: None, + script_or_module, }, context.realm().clone(), ); @@ -821,6 +833,7 @@ pub(crate) fn create_generator_function_object( environments: context.vm.environments.clone(), home_object: None, class_object: None, + script_or_module, }, context.realm().clone(), ); @@ -875,80 +888,91 @@ impl JsObject { context.enter_realm(realm); context.vm.active_function = Some(active_function); - let (code, mut environments, class_object, async_, gen) = match function_object.kind() { - FunctionKind::Native { - function, - constructor, - } => { - let function = function.clone(); - let constructor = *constructor; - drop(object); - - return if constructor.is_some() { - function.call(&JsValue::undefined(), args, context) - } else { - function.call(this, args, context) + let (code, mut environments, class_object, mut script_or_module, async_, gen) = + match function_object.kind() { + FunctionKind::Native { + function, + constructor, + } => { + let function = function.clone(); + let constructor = *constructor; + drop(object); + + return if constructor.is_some() { + function.call(&JsValue::undefined(), args, context) + } else { + function.call(this, args, context) + } + .map_err(|err| err.inject_realm(context.realm().clone())); } - .map_err(|err| err.inject_realm(context.realm().clone())); - } - FunctionKind::Ordinary { - code, - environments, - class_object, - .. - } => { - let code = code.clone(); - if code.is_class_constructor { - return Err(JsNativeError::typ() - .with_message("class constructor cannot be invoked without 'new'") - .with_realm(context.realm().clone()) - .into()); + FunctionKind::Ordinary { + code, + environments, + class_object, + script_or_module, + .. + } => { + let code = code.clone(); + if code.is_class_constructor { + return Err(JsNativeError::typ() + .with_message("class constructor cannot be invoked without 'new'") + .with_realm(context.realm().clone()) + .into()); + } + ( + code, + environments.clone(), + class_object.clone(), + script_or_module.clone(), + false, + false, + ) } - ( + FunctionKind::Async { code, + environments, + class_object, + + script_or_module, + .. + } => ( + code.clone(), environments.clone(), class_object.clone(), + script_or_module.clone(), + true, false, + ), + FunctionKind::Generator { + code, + environments, + class_object, + script_or_module, + .. + } => ( + code.clone(), + environments.clone(), + class_object.clone(), + script_or_module.clone(), false, - ) - } - FunctionKind::Async { - code, - environments, - class_object, - .. - } => ( - code.clone(), - environments.clone(), - class_object.clone(), - true, - false, - ), - FunctionKind::Generator { - code, - environments, - class_object, - .. - } => ( - code.clone(), - environments.clone(), - class_object.clone(), - false, - true, - ), - FunctionKind::AsyncGenerator { - code, - environments, - class_object, - .. - } => ( - code.clone(), - environments.clone(), - class_object.clone(), - true, - true, - ), - }; + true, + ), + FunctionKind::AsyncGenerator { + code, + environments, + class_object, + + script_or_module, + .. + } => ( + code.clone(), + environments.clone(), + class_object.clone(), + script_or_module.clone(), + true, + true, + ), + }; drop(object); @@ -1063,6 +1087,8 @@ impl JsObject { .with_arg_count(arg_count); frame.promise_capability = promise_capability.clone(); + std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module); + context.vm.push_frame(frame); let result = context @@ -1073,6 +1099,7 @@ impl JsObject { let call_frame = context.vm.pop_frame().expect("frame must exist"); std::mem::swap(&mut environments, &mut context.vm.environments); std::mem::swap(&mut context.vm.stack, &mut stack); + std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module); if let Some(promise_capability) = promise_capability { Ok(promise_capability.promise().clone().into()) @@ -1208,10 +1235,12 @@ impl JsObject { code, environments, constructor_kind, + script_or_module, .. } => { let code = code.clone(); let mut environments = environments.clone(); + let mut script_or_module = script_or_module.clone(); let constructor_kind = *constructor_kind; drop(object); @@ -1317,6 +1346,8 @@ impl JsObject { let param_count = code.params.as_ref().len(); let has_binding_identifier = code.has_binding_identifier; + std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module); + context.vm.push_frame( CallFrame::new(code) .with_param_count(param_count) @@ -1328,6 +1359,7 @@ impl JsObject { context.vm.pop_frame(); std::mem::swap(&mut environments, &mut context.vm.environments); + std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module); let environment = if has_binding_identifier { environments.truncate(environments_len + 2); diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs index 817475b684..3fd578db32 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -593,9 +593,10 @@ impl CodeBlock { | Opcode::SetPrototype | Opcode::IsObject | Opcode::SetNameByLocator - | Opcode::Nop | Opcode::PushObjectEnvironment - | Opcode::PopPrivateEnvironment => { + | Opcode::PopPrivateEnvironment + | Opcode::ImportCall + | Opcode::Nop => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 584a86f639..e48029a70b 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -9,11 +9,12 @@ use crate::JsNativeError; use crate::{ builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, environments::{DeclarativeEnvironment, EnvironmentStack}, + script::Script, vm::code_block::Readable, - Context, JsError, JsObject, JsResult, JsValue, + Context, JsError, JsObject, JsResult, JsValue, Module, }; -use boa_gc::Gc; +use boa_gc::{custom_trace, Finalize, Gc, Trace}; use boa_profiler::Profiler; use std::{convert::TryInto, mem::size_of}; @@ -58,6 +59,23 @@ pub struct Vm { pub(crate) trace: bool, pub(crate) runtime_limits: RuntimeLimits, pub(crate) active_function: Option, + pub(crate) active_runnable: Option, +} + +/// Active runnable in the current vm context. +#[derive(Debug, Clone, Finalize)] +pub(crate) enum ActiveRunnable { + Script(Script), + Module(Module), +} + +unsafe impl Trace for ActiveRunnable { + custom_trace!(this, { + match this { + Self::Script(script) => mark(script), + Self::Module(module) => mark(module), + } + }); } impl Vm { @@ -72,6 +90,7 @@ impl Vm { trace: false, runtime_limits: RuntimeLimits::default(), active_function: None, + active_runnable: None, } } diff --git a/boa_engine/src/vm/opcode/call/mod.rs b/boa_engine/src/vm/opcode/call/mod.rs index e25bf3d149..c8fa501db4 100644 --- a/boa_engine/src/vm/opcode/call/mod.rs +++ b/boa_engine/src/vm/opcode/call/mod.rs @@ -1,8 +1,10 @@ use crate::{ - builtins::function::FunctionKind, + builtins::{function::FunctionKind, promise::PromiseCapability, Promise}, error::JsNativeError, + module::{ModuleKind, Referrer}, + object::FunctionObjectBuilder, vm::{opcode::Operation, CompletionType}, - Context, JsResult, JsValue, + Context, JsResult, JsValue, NativeFunction, }; /// `CallEval` implements the Opcode Operation for `Opcode::CallEval` @@ -250,3 +252,220 @@ impl Operation for CallSpread { Ok(CompletionType::Normal) } } + +/// `ImportCall` implements the Opcode Operation for `Opcode::ImportCall` +/// +/// Operation: +/// - Dynamically imports a module +#[derive(Debug, Clone, Copy)] +pub(crate) struct ImportCall; + +impl Operation for ImportCall { + const NAME: &'static str = "ImportCall"; + const INSTRUCTION: &'static str = "INST - ImportCall"; + + fn execute(context: &mut Context<'_>) -> JsResult { + // Import Calls + // Runtime Semantics: Evaluation + // https://tc39.es/ecma262/#sec-import-call-runtime-semantics-evaluation + + // 1. Let referrer be GetActiveScriptOrModule(). + // 2. If referrer is null, set referrer to the current Realm Record. + let referrer = context + .vm + .active_runnable + .clone() + .map_or_else(|| Referrer::Realm(context.realm().clone()), Into::into); + + // 3. Let argRef be ? Evaluation of AssignmentExpression. + // 4. Let specifier be ? GetValue(argRef). + let arg = context.vm.pop(); + + // 5. Let promiseCapability be ! NewPromiseCapability(%Promise%). + let cap = PromiseCapability::new( + &context.intrinsics().constructors().promise().constructor(), + context, + ) + .expect("operation cannot fail for the %Promise% intrinsic"); + let promise = cap.promise().clone(); + + // 6. Let specifierString be Completion(ToString(specifier)). + match arg.to_string(context) { + // 7. IfAbruptRejectPromise(specifierString, promiseCapability). + Err(err) => { + let err = err.to_opaque(context); + cap.reject().call(&JsValue::undefined(), &[err], context)?; + } + // 8. Perform HostLoadImportedModule(referrer, specifierString, empty, promiseCapability). + Ok(specifier) => context.module_loader().load_imported_module( + referrer.clone(), + specifier.clone(), + Box::new(move |completion, context| { + // `ContinueDynamicImport ( promiseCapability, moduleCompletion )` + // https://tc39.es/ecma262/#sec-ContinueDynamicImport + + // `FinishLoadingImportedModule ( referrer, specifier, payload, result )` + // https://tc39.es/ecma262/#sec-FinishLoadingImportedModule + let module = match completion { + // 1. If result is a normal completion, then + Ok(m) => { + match referrer { + Referrer::Module(module) => { + let ModuleKind::SourceText(src) = module.kind() else { + panic!("referrer cannot be a synthetic module"); + }; + + let sym = context.interner_mut().get_or_intern(&*specifier); + + let mut loaded_modules = src.loaded_modules().borrow_mut(); + + // a. If referrer.[[LoadedModules]] contains a Record whose [[Specifier]] is specifier, then + // b. Else, + // i. Append the Record { [[Specifier]]: specifier, [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]]. + let entry = + loaded_modules.entry(sym).or_insert_with(|| m.clone()); + + // i. Assert: That Record's [[Module]] is result.[[Value]]. + debug_assert_eq!(&m, entry); + + // Same steps apply to referrers below + } + Referrer::Realm(realm) => { + let mut loaded_modules = realm.loaded_modules().borrow_mut(); + let entry = loaded_modules + .entry(specifier) + .or_insert_with(|| m.clone()); + debug_assert_eq!(&m, entry); + } + Referrer::Script(script) => { + let mut loaded_modules = script.loaded_modules().borrow_mut(); + let entry = loaded_modules + .entry(specifier) + .or_insert_with(|| m.clone()); + debug_assert_eq!(&m, entry); + } + } + + m + } + // 1. If moduleCompletion is an abrupt completion, then + Err(err) => { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « moduleCompletion.[[Value]] »). + let err = err.to_opaque(context); + cap.reject() + .call(&JsValue::undefined(), &[err], context) + .expect("default `reject` function cannot throw"); + + // b. Return unused. + return; + } + }; + + // 2. Let module be moduleCompletion.[[Value]]. + // 3. Let loadPromise be module.LoadRequestedModules(). + let load = module.load(context); + + // 4. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures promiseCapability and performs the following steps when called: + // 5. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). + let on_rejected = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + |_, args, cap, context| { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »). + cap.reject() + .call(&JsValue::undefined(), args, context) + .expect("default `reject` function cannot throw"); + + // b. Return unused. + Ok(JsValue::undefined()) + }, + cap.clone(), + ), + ) + .build(); + + // 6. Let linkAndEvaluateClosure be a new Abstract Closure with no parameters that captures module, promiseCapability, and onRejected and performs the following steps when called: + // 7. Let linkAndEvaluate be CreateBuiltinFunction(linkAndEvaluateClosure, 0, "", « »). + let link_evaluate = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + |_, _, (module, cap, on_rejected), context| { + // a. Let link be Completion(module.Link()). + // b. If link is an abrupt completion, then + if let Err(e) = module.link(context) { + // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « link.[[Value]] »). + let e = e.to_opaque(context); + cap.reject() + .call(&JsValue::undefined(), &[e], context) + .expect("default `reject` function cannot throw"); + // ii. Return unused. + return Ok(JsValue::undefined()); + } + + // c. Let evaluatePromise be module.Evaluate(). + let evaluate = module.evaluate(context); + + // d. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and promiseCapability and performs the following steps when called: + // e. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »). + let fulfill = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + |_, _, (module, cap), context| { + // i. Let namespace be GetModuleNamespace(module). + let namespace = module.namespace(context); + + // ii. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace »). + cap.resolve() + .call( + &JsValue::undefined(), + &[namespace.into()], + context, + ) + .expect("default `resolve` function cannot throw"); + + // iii. Return unused. + Ok(JsValue::undefined()) + }, + (module.clone(), cap.clone()), + ), + ) + .build(); + + // f. Perform PerformPromiseThen(evaluatePromise, onFulfilled, onRejected). + Promise::perform_promise_then( + &evaluate, + Some(fulfill), + Some(on_rejected.clone()), + None, + context, + ); + + // g. Return unused. + Ok(JsValue::undefined()) + }, + (module.clone(), cap.clone(), on_rejected.clone()), + ), + ) + .build(); + + // 8. Perform PerformPromiseThen(loadPromise, linkAndEvaluate, onRejected). + Promise::perform_promise_then( + &load, + Some(link_evaluate), + Some(on_rejected), + None, + context, + ); + + // 9. Return unused. + }), + context, + ), + }; + + // 9. Return promiseCapability.[[Promise]]. + context.vm.push(promise); + + Ok(CompletionType::Normal) + } +} diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index bdab2afcc0..41bc77a6c5 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -1230,6 +1230,13 @@ generate_impl! { /// Stack: argument_1, ... argument_n **=>** SuperCallDerived, + /// Dynamically import a module. + /// + /// Operands: + /// + /// Stack: specifier **=>** promise + ImportCall, + /// Pop the two values of the stack, strict equal compares the two values, /// if true jumps to address, otherwise push the second pop'ed value. /// diff --git a/boa_examples/src/bin/classes.rs b/boa_examples/src/bin/classes.rs index 54f6aeca8d..60e95bff59 100644 --- a/boa_examples/src/bin/classes.rs +++ b/boa_examples/src/bin/classes.rs @@ -149,7 +149,7 @@ fn main() { // Having done all of that, we can execute Javascript code with `eval`, // and access the `Person` class defined in Rust! context - .eval_script(Source::from_bytes( + .eval(Source::from_bytes( r" let person = new Person('John', 19); person.sayHello(); diff --git a/boa_examples/src/bin/closures.rs b/boa_examples/src/bin/closures.rs index eb4b89705f..14eb3d0b12 100644 --- a/boa_examples/src/bin/closures.rs +++ b/boa_examples/src/bin/closures.rs @@ -38,10 +38,7 @@ fn main() -> Result<(), JsError> { ) .unwrap(); - assert_eq!( - context.eval_script(Source::from_bytes("closure()"))?, - 255.into() - ); + assert_eq!(context.eval(Source::from_bytes("closure()"))?, 255.into()); // We have created a closure with moved variables and executed that closure // inside Javascript! @@ -124,13 +121,13 @@ fn main() -> Result<(), JsError> { .unwrap(); assert_eq!( - context.eval_script(Source::from_bytes("createMessage()"))?, + context.eval(Source::from_bytes("createMessage()"))?, "message from `Boa dev`: Hello!".into() ); // The data mutates between calls assert_eq!( - context.eval_script(Source::from_bytes("createMessage(); createMessage();"))?, + context.eval(Source::from_bytes("createMessage(); createMessage();"))?, "message from `Boa dev`: Hello! Hello! Hello!".into() ); @@ -174,7 +171,7 @@ fn main() -> Result<(), JsError> { .unwrap(); // First call should return the array `[0]`. - let result = context.eval_script(Source::from_bytes("enumerate()"))?; + let result = context.eval(Source::from_bytes("enumerate()"))?; let object = result .as_object() .cloned() @@ -185,7 +182,7 @@ fn main() -> Result<(), JsError> { assert_eq!(array.get(1, &mut context)?, JsValue::undefined()); // First call should return the array `[0, 1]`. - let result = context.eval_script(Source::from_bytes("enumerate()"))?; + let result = context.eval(Source::from_bytes("enumerate()"))?; let object = result .as_object() .cloned() diff --git a/boa_examples/src/bin/commuter_visitor.rs b/boa_examples/src/bin/commuter_visitor.rs index d266682a69..fffe692404 100644 --- a/boa_examples/src/bin/commuter_visitor.rs +++ b/boa_examples/src/bin/commuter_visitor.rs @@ -69,14 +69,14 @@ fn main() { Parser::new(Source::from_filepath(Path::new("boa_examples/scripts/calc.js")).unwrap()); let mut ctx = Context::default(); - let mut statements = parser.parse_script(ctx.interner_mut()).unwrap(); + let mut script = parser.parse_script(ctx.interner_mut()).unwrap(); let mut visitor = CommutorVisitor::default(); assert!(matches!( - visitor.visit_statement_list_mut(&mut statements), + visitor.visit_statement_list_mut(script.statements_mut()), ControlFlow::Continue(_) )); - println!("{}", statements.to_interned_string(ctx.interner())); + println!("{}", script.to_interned_string(ctx.interner())); } diff --git a/boa_examples/src/bin/derive.rs b/boa_examples/src/bin/derive.rs index fa4c101c0c..81deb46057 100644 --- a/boa_examples/src/bin/derive.rs +++ b/boa_examples/src/bin/derive.rs @@ -26,7 +26,7 @@ fn main() { let js = Source::from_bytes(js_str); let mut context = Context::default(); - let res = context.eval_script(js).unwrap(); + let res = context.eval(js).unwrap(); let str = TestStruct::try_from_js(&res, &mut context) .map_err(|e| e.to_string()) diff --git a/boa_examples/src/bin/futures.rs b/boa_examples/src/bin/futures.rs index a7bee2ae46..634496bcfd 100644 --- a/boa_examples/src/bin/futures.rs +++ b/boa_examples/src/bin/futures.rs @@ -166,7 +166,7 @@ fn main() { "#; let now = Instant::now(); - context.eval_script(Source::from_bytes(script)).unwrap(); + context.eval(Source::from_bytes(script)).unwrap(); // Important to run this after evaluating, since this is what triggers to run the enqueued jobs. context.run_jobs(); diff --git a/boa_examples/src/bin/loadfile.rs b/boa_examples/src/bin/loadfile.rs index 07492b0cd8..72a0784bd0 100644 --- a/boa_examples/src/bin/loadfile.rs +++ b/boa_examples/src/bin/loadfile.rs @@ -13,7 +13,7 @@ fn main() { // Instantiate the execution context let mut context = Context::default(); // Parse the source code - match context.eval_script(src) { + match context.eval(src) { Ok(res) => { println!( "{}", diff --git a/boa_examples/src/bin/loadstring.rs b/boa_examples/src/bin/loadstring.rs index 52fc116a6e..dca029a152 100644 --- a/boa_examples/src/bin/loadstring.rs +++ b/boa_examples/src/bin/loadstring.rs @@ -9,7 +9,7 @@ fn main() { let mut context = Context::default(); // Parse the source code - match context.eval_script(Source::from_bytes(js_code)) { + match context.eval(Source::from_bytes(js_code)) { Ok(res) => { println!( "{}", diff --git a/boa_examples/src/bin/modulehandler.rs b/boa_examples/src/bin/modulehandler.rs index fef855a2ad..d96a1efb9e 100644 --- a/boa_examples/src/bin/modulehandler.rs +++ b/boa_examples/src/bin/modulehandler.rs @@ -33,8 +33,7 @@ fn main() { // Instantiating the engine with the execution context // Loading, parsing and executing the JS code from the source file - ctx.eval_script(Source::from_bytes(&buffer.unwrap())) - .unwrap(); + ctx.eval(Source::from_bytes(&buffer.unwrap())).unwrap(); } // Custom implementation that mimics the 'require' module loader @@ -55,8 +54,7 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult TokenParser for ConciseBody where R: Read, { - type Output = StatementList; + type Output = ast::function::FunctionBody; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - match cursor.peek(0, interner).or_abrupt()?.kind() { - TokenKind::Punctuator(Punctuator::OpenBlock) => { - cursor.advance(interner); - let body = FunctionBody::new(false, false).parse(cursor, interner)?; - cursor.expect(Punctuator::CloseBlock, "arrow function", interner)?; - Ok(body) - } - _ => Ok(StatementList::from(vec![ast::Statement::Return( - Return::new( - ExpressionBody::new(self.allow_in, false) - .parse(cursor, interner)? - .into(), - ), - ) - .into()])), - } + let stmts = + match cursor.peek(0, interner).or_abrupt()?.kind() { + TokenKind::Punctuator(Punctuator::OpenBlock) => { + cursor.advance(interner); + let body = FunctionBody::new(false, false).parse(cursor, interner)?; + cursor.expect(Punctuator::CloseBlock, "arrow function", interner)?; + body + } + _ => ast::function::FunctionBody::new(StatementList::from(vec![ + ast::Statement::Return(Return::new( + ExpressionBody::new(self.allow_in, false) + .parse(cursor, interner)? + .into(), + )) + .into(), + ])), + }; + + Ok(stmts) } } diff --git a/boa_parser/src/parser/expression/assignment/async_arrow_function.rs b/boa_parser/src/parser/expression/assignment/async_arrow_function.rs index 8f48c029d8..62deaa9d7e 100644 --- a/boa_parser/src/parser/expression/assignment/async_arrow_function.rs +++ b/boa_parser/src/parser/expression/assignment/async_arrow_function.rs @@ -18,7 +18,7 @@ use crate::{ }, }; use ast::{ - operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, + operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, Keyword, }; use boa_ast::{ @@ -145,7 +145,7 @@ where // also occurs in the LexicallyDeclaredNames of AsyncConciseBody. name_in_lexically_declared_names( &bound_names(¶ms), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; @@ -178,24 +178,27 @@ impl TokenParser for AsyncConciseBody where R: Read, { - type Output = StatementList; + type Output = ast::function::FunctionBody; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - match cursor.peek(0, interner).or_abrupt()?.kind() { - TokenKind::Punctuator(Punctuator::OpenBlock) => { - cursor.advance(interner); - let body = FunctionBody::new(false, true).parse(cursor, interner)?; - cursor.expect(Punctuator::CloseBlock, "async arrow function", interner)?; - Ok(body) - } - _ => Ok(StatementList::from(vec![ast::Statement::Return( - Return::new( - ExpressionBody::new(self.allow_in, true) - .parse(cursor, interner)? - .into(), - ), - ) - .into()])), - } + let body = + match cursor.peek(0, interner).or_abrupt()?.kind() { + TokenKind::Punctuator(Punctuator::OpenBlock) => { + cursor.advance(interner); + let body = FunctionBody::new(false, true).parse(cursor, interner)?; + cursor.expect(Punctuator::CloseBlock, "async arrow function", interner)?; + body + } + _ => ast::function::FunctionBody::new(StatementList::from(vec![ + ast::Statement::Return(Return::new( + ExpressionBody::new(self.allow_in, true) + .parse(cursor, interner)? + .into(), + )) + .into(), + ])), + }; + + Ok(body) } } diff --git a/boa_parser/src/parser/expression/assignment/mod.rs b/boa_parser/src/parser/expression/assignment/mod.rs index 33ea651efc..ed66cff608 100644 --- a/boa_parser/src/parser/expression/assignment/mod.rs +++ b/boa_parser/src/parser/expression/assignment/mod.rs @@ -32,7 +32,7 @@ use boa_ast::{ operator::assign::{Assign, AssignOp, AssignTarget}, Identifier, }, - operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, + operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, Expression, Keyword, Punctuator, }; use boa_interner::Interner; @@ -225,7 +225,7 @@ where // https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors name_in_lexically_declared_names( &bound_names(¶meters), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), position, interner, )?; diff --git a/boa_parser/src/parser/expression/left_hand_side/call.rs b/boa_parser/src/parser/expression/left_hand_side/call.rs index ea2ef19530..cac1d4a9d0 100644 --- a/boa_parser/src/parser/expression/left_hand_side/call.rs +++ b/boa_parser/src/parser/expression/left_hand_side/call.rs @@ -72,7 +72,7 @@ where let token = cursor.peek(0, interner).or_abrupt()?; - let mut lhs = if token.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) { + 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() @@ -86,6 +86,42 @@ where )); }; + CallExpressionTail::new(self.allow_yield, self.allow_await, lhs).parse(cursor, interner) + } +} + +/// Parses the tail parts of a call expression (property access, sucessive call, array access). +#[derive(Debug)] +pub(super) struct CallExpressionTail { + allow_yield: AllowYield, + allow_await: AllowAwait, + call: ast::Expression, +} + +impl CallExpressionTail { + /// Creates a new `CallExpressionTail` parser. + pub(super) fn new(allow_yield: Y, allow_await: A, call: ast::Expression) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + call, + } + } +} + +impl TokenParser for CallExpressionTail +where + R: Read, +{ + type Output = ast::Expression; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + let mut lhs = self.call; + while let Some(tok) = cursor.peek(0, interner)? { let token = tok.clone(); match token.kind() { @@ -147,6 +183,7 @@ where _ => break, } } + Ok(lhs) } } diff --git a/boa_parser/src/parser/expression/left_hand_side/mod.rs b/boa_parser/src/parser/expression/left_hand_side/mod.rs index 191c7a35a5..6b6c95c3ee 100644 --- a/boa_parser/src/parser/expression/left_hand_side/mod.rs +++ b/boa_parser/src/parser/expression/left_hand_side/mod.rs @@ -19,15 +19,21 @@ mod template; use crate::{ lexer::{InputElement, TokenKind}, parser::{ - expression::left_hand_side::{ - arguments::Arguments, call::CallExpression, member::MemberExpression, - optional::OptionalExpression, + expression::{ + left_hand_side::{ + arguments::Arguments, + call::{CallExpression, CallExpressionTail}, + member::MemberExpression, + optional::OptionalExpression, + }, + AssignmentExpression, }, - AllowAwait, AllowYield, Cursor, ParseResult, TokenParser, + AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser, }, + Error, }; use boa_ast::{ - expression::{Identifier, SuperCall}, + expression::{Identifier, ImportCall, SuperCall}, Expression, Keyword, Punctuator, }; use boa_interner::Interner; @@ -96,6 +102,7 @@ where } Ok(false) } + let _timer = Profiler::global().start_event("LeftHandSideExpression", "Parsing"); cursor.set_goal(InputElement::TemplateTail); @@ -106,15 +113,48 @@ where Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; SuperCall::new(args).into() } else { - let mut member = MemberExpression::new(self.name, self.allow_yield, self.allow_await) - .parse(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) + let next = cursor.peek(0, interner).or_abrupt()?; + if let TokenKind::Keyword((Keyword::Import, escaped)) = next.kind() { + if *escaped { + return Err(Error::general( + "keyword `import` must not contain escaped characters", + next.span().start(), + )); + } + cursor.advance(interner); + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenParen), + "import call", + interner, + )?; + + let arg = AssignmentExpression::new(None, 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)? + } else { + let mut member = + MemberExpression::new(self.name, self.allow_yield, self.allow_await) .parse(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)?; + } } + member } - member }; if let Some(tok) = cursor.peek(0, interner)? { diff --git a/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs b/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs index 74ec2de4bd..7b41a95e19 100644 --- a/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs @@ -13,7 +13,7 @@ use crate::{ use boa_ast::{ expression::Identifier, function::AsyncFunction, - operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, + operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, Keyword, Punctuator, }; use boa_interner::{Interner, Sym}; @@ -140,7 +140,7 @@ where // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors name_in_lexically_declared_names( &bound_names(¶ms), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; diff --git a/boa_parser/src/parser/expression/primary/async_function_expression/tests.rs b/boa_parser/src/parser/expression/primary/async_function_expression/tests.rs index 3664061e14..259dc62acc 100644 --- a/boa_parser/src/parser/expression/primary/async_function_expression/tests.rs +++ b/boa_parser/src/parser/expression/primary/async_function_expression/tests.rs @@ -2,7 +2,7 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ declaration::{Declaration, LexicalDeclaration, Variable}, expression::literal::Literal, - function::{AsyncFunction, FormalParameterList}, + function::{AsyncFunction, FormalParameterList, FunctionBody}, statement::Return, Statement, StatementListItem, }; @@ -26,10 +26,12 @@ fn check_async_expression() { AsyncFunction::new( Some(add.into()), FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some(Literal::from(1).into())), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some(Literal::from(1).into())), + ))] + .into(), + ), false, ) .into(), @@ -62,28 +64,32 @@ fn check_nested_async_expression() { AsyncFunction::new( Some(a.into()), FormalParameterList::default(), - vec![Declaration::Lexical(LexicalDeclaration::Const( - vec![Variable::from_identifier( - b.into(), - Some( - AsyncFunction::new( - Some(b.into()), - FormalParameterList::default(), - vec![Statement::Return(Return::new(Some( - Literal::from(1).into(), - ))) - .into()] + FunctionBody::new( + vec![Declaration::Lexical(LexicalDeclaration::Const( + vec![Variable::from_identifier( + b.into(), + Some( + AsyncFunction::new( + Some(b.into()), + FormalParameterList::default(), + FunctionBody::new( + vec![Statement::Return(Return::new(Some( + Literal::from(1).into(), + ))) + .into()] + .into(), + ), + false, + ) .into(), - false, - ) - .into(), - ), - )] - .try_into() - .unwrap(), - )) - .into()] - .into(), + ), + )] + .try_into() + .unwrap(), + )) + .into()] + .into(), + ), false, ) .into(), diff --git a/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs b/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs index 58edaf6b4f..20f5dfb2a2 100644 --- a/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs @@ -22,7 +22,7 @@ use crate::{ use boa_ast::{ expression::Identifier, function::AsyncGenerator, - operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, + operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, Keyword, Punctuator, }; use boa_interner::{Interner, Sym}; @@ -177,7 +177,7 @@ where // also occurs in the LexicallyDeclaredNames of FunctionBody. name_in_lexically_declared_names( &bound_names(¶ms), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; diff --git a/boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs b/boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs index 2db94ede44..df04d67d08 100644 --- a/boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs +++ b/boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs @@ -2,7 +2,7 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ declaration::{LexicalDeclaration, Variable}, expression::literal::Literal, - function::{AsyncGenerator, FormalParameterList}, + function::{AsyncGenerator, FormalParameterList, FunctionBody}, statement::Return, Declaration, Statement, StatementListItem, }; @@ -27,10 +27,12 @@ fn check_async_generator_expr() { AsyncGenerator::new( Some(add.into()), FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some(Literal::from(1).into())), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some(Literal::from(1).into())), + ))] + .into(), + ), false, ) .into(), @@ -63,27 +65,33 @@ fn check_nested_async_generator_expr() { AsyncGenerator::new( Some(a.into()), FormalParameterList::default(), - vec![Declaration::Lexical(LexicalDeclaration::Const( - vec![Variable::from_identifier( - b.into(), - Some( - AsyncGenerator::new( - Some(b.into()), - FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some(Literal::from(1).into())), - ))] + FunctionBody::new( + vec![Declaration::Lexical(LexicalDeclaration::Const( + vec![Variable::from_identifier( + b.into(), + Some( + AsyncGenerator::new( + Some(b.into()), + FormalParameterList::default(), + FunctionBody::new( + vec![StatementListItem::Statement( + Statement::Return(Return::new(Some( + Literal::from(1).into(), + ))), + )] + .into(), + ), + false, + ) .into(), - false, - ) - .into(), - ), - )] - .try_into() - .unwrap(), - )) - .into()] - .into(), + ), + )] + .try_into() + .unwrap(), + )) + .into()] + .into(), + ), false, ) .into(), diff --git a/boa_parser/src/parser/expression/primary/function_expression/mod.rs b/boa_parser/src/parser/expression/primary/function_expression/mod.rs index 99c08fa816..e0fb1192a7 100644 --- a/boa_parser/src/parser/expression/primary/function_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/function_expression/mod.rs @@ -22,7 +22,7 @@ use crate::{ use boa_ast::{ expression::Identifier, function::Function, - operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, + operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, Keyword, Punctuator, }; use boa_interner::{Interner, Sym}; @@ -135,7 +135,7 @@ where // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors name_in_lexically_declared_names( &bound_names(¶ms), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; diff --git a/boa_parser/src/parser/expression/primary/function_expression/tests.rs b/boa_parser/src/parser/expression/primary/function_expression/tests.rs index 400802b387..40da9ca130 100644 --- a/boa_parser/src/parser/expression/primary/function_expression/tests.rs +++ b/boa_parser/src/parser/expression/primary/function_expression/tests.rs @@ -2,7 +2,7 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ declaration::{LexicalDeclaration, Variable}, expression::literal::Literal, - function::{FormalParameterList, Function}, + function::{FormalParameterList, Function, FunctionBody}, statement::Return, Declaration, Statement, StatementListItem, }; @@ -26,10 +26,12 @@ fn check_function_expression() { Function::new( Some(add.into()), FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some(Literal::from(1).into())), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some(Literal::from(1).into())), + ))] + .into(), + ), ) .into(), ), @@ -61,26 +63,32 @@ fn check_nested_function_expression() { Function::new( Some(a.into()), FormalParameterList::default(), - vec![Declaration::Lexical(LexicalDeclaration::Const( - vec![Variable::from_identifier( - b.into(), - Some( - Function::new( - Some(b.into()), - FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some(Literal::from(1).into())), - ))] + FunctionBody::new( + vec![Declaration::Lexical(LexicalDeclaration::Const( + vec![Variable::from_identifier( + b.into(), + Some( + Function::new( + Some(b.into()), + FormalParameterList::default(), + FunctionBody::new( + vec![StatementListItem::Statement( + Statement::Return(Return::new(Some( + Literal::from(1).into(), + ))), + )] + .into(), + ), + ) .into(), - ) - .into(), - ), - )] - .try_into() - .unwrap(), - )) - .into()] - .into(), + ), + )] + .try_into() + .unwrap(), + )) + .into()] + .into(), + ), ) .into(), ), @@ -104,7 +112,15 @@ fn check_function_non_reserved_keyword() { Function::new_with_binding_identifier( Some($interner.get_or_intern_static($keyword, utf16!($keyword)).into()), FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Return(Return::new(Some(Literal::from(1).into()))))].into(), + FunctionBody::new( + vec![StatementListItem::Statement( + Statement::Return( + Return::new( + Some(Literal::from(1).into()) + ) + ) + )].into() + ), true, ) .into(), diff --git a/boa_parser/src/parser/expression/primary/generator_expression/mod.rs b/boa_parser/src/parser/expression/primary/generator_expression/mod.rs index 2b49a8eaa5..913fc81305 100644 --- a/boa_parser/src/parser/expression/primary/generator_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/generator_expression/mod.rs @@ -22,7 +22,7 @@ use crate::{ use boa_ast::{ expression::Identifier, function::Generator, - operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, + operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, Keyword, Punctuator, }; use boa_interner::{Interner, Sym}; @@ -143,7 +143,7 @@ where // https://tc39.es/ecma262/#sec-generator-function-definitions-static-semantics-early-errors name_in_lexically_declared_names( &bound_names(¶ms), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; diff --git a/boa_parser/src/parser/expression/primary/generator_expression/tests.rs b/boa_parser/src/parser/expression/primary/generator_expression/tests.rs index 3e7d6b0cc8..3e80cfb274 100644 --- a/boa_parser/src/parser/expression/primary/generator_expression/tests.rs +++ b/boa_parser/src/parser/expression/primary/generator_expression/tests.rs @@ -2,7 +2,7 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ declaration::{LexicalDeclaration, Variable}, expression::{literal::Literal, Yield}, - function::{FormalParameterList, Generator}, + function::{FormalParameterList, FunctionBody, Generator}, Declaration, Expression, Statement, StatementListItem, }; use boa_interner::Interner; @@ -24,10 +24,12 @@ fn check_generator_function_expression() { Generator::new( Some(gen.into()), FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Expression( - Expression::from(Yield::new(Some(Literal::from(1).into()), false)), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Expression( + Expression::from(Yield::new(Some(Literal::from(1).into()), false)), + ))] + .into(), + ), false, ) .into(), @@ -57,10 +59,12 @@ fn check_generator_function_delegate_yield_expression() { Generator::new( Some(gen.into()), FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Expression( - Expression::from(Yield::new(Some(Literal::from(1).into()), true)), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Expression( + Expression::from(Yield::new(Some(Literal::from(1).into()), true)), + ))] + .into(), + ), false, ) .into(), diff --git a/boa_parser/src/parser/expression/primary/object_initializer/mod.rs b/boa_parser/src/parser/expression/primary/object_initializer/mod.rs index 278911751e..58d77373fe 100644 --- a/boa_parser/src/parser/expression/primary/object_initializer/mod.rs +++ b/boa_parser/src/parser/expression/primary/object_initializer/mod.rs @@ -32,7 +32,7 @@ use boa_ast::{ AsyncFunction, AsyncGenerator, FormalParameterList, Function, Generator, PrivateName, }, operations::{ - bound_names, contains, has_direct_super, top_level_lexically_declared_names, ContainsSymbol, + bound_names, contains, has_direct_super, lexically_declared_names, ContainsSymbol, }, property::{self, MethodDefinition}, Expression, Keyword, Punctuator, @@ -442,7 +442,7 @@ where // https://tc39.es/ecma262/#sec-method-definitions-static-semantics-early-errors name_in_lexically_declared_names( &bound_names(¶meters), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; @@ -518,7 +518,7 @@ where // LexicallyDeclaredNames of FunctionBody. name_in_lexically_declared_names( &bound_names(¶ms), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; @@ -801,7 +801,7 @@ where // occurs in the LexicallyDeclaredNames of GeneratorBody. name_in_lexically_declared_names( &bound_names(¶ms), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; @@ -917,7 +917,7 @@ where // occurs in the LexicallyDeclaredNames of GeneratorBody. name_in_lexically_declared_names( &bound_names(¶ms), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; @@ -1010,7 +1010,7 @@ where // occurs in the LexicallyDeclaredNames of GeneratorBody. name_in_lexically_declared_names( &bound_names(¶ms), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; diff --git a/boa_parser/src/parser/expression/primary/object_initializer/tests.rs b/boa_parser/src/parser/expression/primary/object_initializer/tests.rs index af54d60f07..98c33ec24f 100644 --- a/boa_parser/src/parser/expression/primary/object_initializer/tests.rs +++ b/boa_parser/src/parser/expression/primary/object_initializer/tests.rs @@ -7,10 +7,10 @@ use boa_ast::{ }, function::{ AsyncFunction, AsyncGenerator, FormalParameter, FormalParameterList, - FormalParameterListFlags, Function, + FormalParameterListFlags, Function, FunctionBody, }, property::{MethodDefinition, PropertyDefinition, PropertyName}, - Declaration, StatementList, + Declaration, }; use boa_interner::{Interner, Sym}; use boa_macros::utf16; @@ -65,7 +65,7 @@ fn check_object_short_function() { MethodDefinition::Ordinary(Function::new( Some(interner.get_or_intern_static("b", utf16!("b")).into()), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), )), ), ]; @@ -115,7 +115,7 @@ fn check_object_short_function_arguments() { MethodDefinition::Ordinary(Function::new( Some(interner.get_or_intern_static("b", utf16!("b")).into()), parameters, - StatementList::default(), + FunctionBody::default(), )), ), ]; @@ -157,7 +157,7 @@ fn check_object_getter() { .into(), ), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), )), ), ]; @@ -210,7 +210,7 @@ fn check_object_setter() { .into(), ), params, - StatementList::default(), + FunctionBody::default(), )), ), ]; @@ -243,7 +243,7 @@ fn check_object_short_function_get() { MethodDefinition::Ordinary(Function::new( Some(interner.get_or_intern_static("get", utf16!("get")).into()), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), )), )]; @@ -274,7 +274,7 @@ fn check_object_short_function_set() { MethodDefinition::Ordinary(Function::new( Some(interner.get_or_intern_static("set", utf16!("set")).into()), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), )), )]; @@ -422,7 +422,7 @@ fn check_async_method() { MethodDefinition::Async(AsyncFunction::new( Some(interner.get_or_intern_static("dive", utf16!("dive")).into()), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), false, )), )]; @@ -460,7 +460,7 @@ fn check_async_generator_method() { .into(), ), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), false, )), )]; @@ -518,7 +518,7 @@ fn check_async_ordinary_method() { .into(), ), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), )), )]; diff --git a/boa_parser/src/parser/function/mod.rs b/boa_parser/src/parser/function/mod.rs index 59f5edb12d..70a1677d21 100644 --- a/boa_parser/src/parser/function/mod.rs +++ b/boa_parser/src/parser/function/mod.rs @@ -449,7 +449,7 @@ impl TokenParser for FunctionStatementList where R: Read, { - type Output = ast::StatementList; + type Output = ast::function::FunctionBody; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("FunctionStatementList", "Parsing"); @@ -478,6 +478,6 @@ where ))); } - Ok(statement_list) + Ok(ast::function::FunctionBody::new(statement_list)) } } diff --git a/boa_parser/src/parser/function/tests.rs b/boa_parser/src/parser/function/tests.rs index 0c4140c7e7..b74f564369 100644 --- a/boa_parser/src/parser/function/tests.rs +++ b/boa_parser/src/parser/function/tests.rs @@ -7,9 +7,10 @@ use boa_ast::{ }, function::{ ArrowFunction, FormalParameter, FormalParameterList, FormalParameterListFlags, Function, + FunctionBody, }, statement::Return, - Declaration, Expression, Statement, StatementList, StatementListItem, + Declaration, Expression, Statement, StatementListItem, }; use boa_interner::Interner; use boa_macros::utf16; @@ -30,12 +31,14 @@ fn check_basic() { vec![Declaration::Function(Function::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), - )), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), + )), + ))] + .into(), + ), )) .into()], interner, @@ -66,12 +69,14 @@ fn check_duplicates_strict_off() { vec![Declaration::Function(Function::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), - )), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), + )), + ))] + .into(), + ), )) .into()], interner, @@ -100,12 +105,14 @@ fn check_basic_semicolon_insertion() { vec![Declaration::Function(Function::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), - )), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), + )), + ))] + .into(), + ), )) .into()], interner, @@ -127,10 +134,12 @@ fn check_empty_return() { vec![Declaration::Function(Function::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(None), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(None), + ))] + .into(), + ), )) .into()], interner, @@ -152,10 +161,12 @@ fn check_empty_return_semicolon_insertion() { vec![Declaration::Function(Function::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(None), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(None), + ))] + .into(), + ), )) .into()], interner, @@ -186,7 +197,7 @@ fn check_rest_operator() { vec![Declaration::Function(Function::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - StatementList::default(), + FunctionBody::default(), )) .into()], interner, @@ -211,7 +222,7 @@ fn check_arrow_only_rest() { vec![Statement::Expression(Expression::from(ArrowFunction::new( None, params, - StatementList::default(), + FunctionBody::default(), ))) .into()], interner, @@ -246,7 +257,7 @@ fn check_arrow_rest() { vec![Statement::Expression(Expression::from(ArrowFunction::new( None, params, - StatementList::default(), + FunctionBody::default(), ))) .into()], interner, @@ -274,17 +285,19 @@ fn check_arrow() { vec![Statement::Expression(Expression::from(ArrowFunction::new( None, params, - vec![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(), - )), - ))] - .into(), + FunctionBody::new( + vec![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(), + )), + ))] + .into(), + ), ))) .into()], interner, @@ -310,17 +323,19 @@ fn check_arrow_semicolon_insertion() { vec![Statement::Expression(Expression::from(ArrowFunction::new( None, params, - vec![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(), - )), - ))] - .into(), + FunctionBody::new( + vec![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(), + )), + ))] + .into(), + ), ))) .into()], interner, @@ -346,10 +361,12 @@ fn check_arrow_epty_return() { vec![Statement::Expression(Expression::from(ArrowFunction::new( None, params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(None), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(None), + ))] + .into(), + ), ))) .into()], interner, @@ -375,10 +392,12 @@ fn check_arrow_empty_return_semicolon_insertion() { vec![Statement::Expression(Expression::from(ArrowFunction::new( None, params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(None), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(None), + ))] + .into(), + ), ))) .into()], interner, @@ -403,13 +422,17 @@ fn check_arrow_assignment() { ArrowFunction::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::new( + interner.get_or_intern_static("a", utf16!("a")), + ) .into(), - )), - ))] - .into(), + )), + ))] + .into(), + ), ) .into(), ), @@ -440,13 +463,17 @@ fn check_arrow_assignment_nobrackets() { ArrowFunction::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::new( + interner.get_or_intern_static("a", utf16!("a")), + ) .into(), - )), - ))] - .into(), + )), + ))] + .into(), + ), ) .into(), ), @@ -477,13 +504,17 @@ fn check_arrow_assignment_noparenthesis() { ArrowFunction::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::new( + interner.get_or_intern_static("a", utf16!("a")), + ) .into(), - )), - ))] - .into(), + )), + ))] + .into(), + ), ) .into(), ), @@ -514,13 +545,17 @@ fn check_arrow_assignment_noparenthesis_nobrackets() { ArrowFunction::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::new( + interner.get_or_intern_static("a", utf16!("a")), + ) .into(), - )), - ))] - .into(), + )), + ))] + .into(), + ), ) .into(), ), @@ -557,13 +592,17 @@ fn check_arrow_assignment_2arg() { ArrowFunction::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::new( + interner.get_or_intern_static("a", utf16!("a")), + ) .into(), - )), - ))] - .into(), + )), + ))] + .into(), + ), ) .into(), ), @@ -600,13 +639,17 @@ fn check_arrow_assignment_2arg_nobrackets() { ArrowFunction::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::new( + interner.get_or_intern_static("a", utf16!("a")), + ) .into(), - )), - ))] - .into(), + )), + ))] + .into(), + ), ) .into(), ), @@ -647,13 +690,17 @@ fn check_arrow_assignment_3arg() { ArrowFunction::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::new( + interner.get_or_intern_static("a", utf16!("a")), + ) .into(), - )), - ))] - .into(), + )), + ))] + .into(), + ), ) .into(), ), @@ -694,13 +741,17 @@ fn check_arrow_assignment_3arg_nobrackets() { ArrowFunction::new( Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some( - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some( + Identifier::new( + interner.get_or_intern_static("a", utf16!("a")), + ) .into(), - )), - ))] - .into(), + )), + ))] + .into(), + ), ) .into(), ), diff --git a/boa_parser/src/parser/mod.rs b/boa_parser/src/parser/mod.rs index 8ca8ea9485..3bf65abdb3 100644 --- a/boa_parser/src/parser/mod.rs +++ b/boa_parser/src/parser/mod.rs @@ -20,18 +20,19 @@ use crate::{ }; use boa_ast::{ expression::Identifier, - function::FormalParameterList, + function::{FormalParameterList, FunctionBody}, operations::{ all_private_identifiers_valid, check_labels, contains, contains_invalid_object_literal, - lexically_declared_names, top_level_lexically_declared_names, top_level_var_declared_names, - var_declared_names, ContainsSymbol, + lexically_declared_names, var_declared_names, ContainsSymbol, }, - ModuleItemList, Position, StatementList, + Position, StatementList, }; use boa_interner::Interner; use rustc_hash::FxHashSet; use std::{io::Read, path::Path}; +use self::statement::ModuleItemList; + /// Trait implemented by parsers. /// /// This makes it possible to abstract over the underlying implementation of a parser. @@ -126,15 +127,15 @@ impl<'a, R: Read> Parser<'a, R> { } /// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation. - /// The resulting `StatementList` can be compiled into boa bytecode and executed in the boa vm. + /// The resulting `Script` can be compiled into boa bytecode and executed in the boa vm. /// /// # Errors /// /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. /// /// [spec]: https://tc39.es/ecma262/#prod-Script - pub fn parse_script(&mut self, interner: &mut Interner) -> ParseResult { - Script::new(false).parse(&mut self.cursor, interner) + pub fn parse_script(&mut self, interner: &mut Interner) -> ParseResult { + ScriptParser::new(false).parse(&mut self.cursor, interner) } /// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation. @@ -145,11 +146,11 @@ impl<'a, R: Read> Parser<'a, R> { /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. /// /// [spec]: https://tc39.es/ecma262/#prod-Module - pub fn parse_module(&mut self, interner: &mut Interner) -> ParseResult + pub fn parse_module(&mut self, interner: &mut Interner) -> ParseResult where R: Read, { - Module.parse(&mut self.cursor, interner) + ModuleParser.parse(&mut self.cursor, interner) } /// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec] @@ -165,8 +166,8 @@ impl<'a, R: Read> Parser<'a, R> { &mut self, direct: bool, interner: &mut Interner, - ) -> ParseResult { - Script::new(direct).parse(&mut self.cursor, interner) + ) -> ParseResult { + ScriptParser::new(direct).parse(&mut self.cursor, interner) } /// Parses the full input as an [ECMAScript `FunctionBody`][spec] into the boa AST representation. @@ -181,7 +182,7 @@ impl<'a, R: Read> Parser<'a, R> { interner: &mut Interner, allow_yield: bool, allow_await: bool, - ) -> ParseResult { + ) -> ParseResult { FunctionStatementList::new(allow_yield, allow_await).parse(&mut self.cursor, interner) } @@ -235,11 +236,11 @@ impl Parser<'_, R> { /// /// [spec]: https://tc39.es/ecma262/#prod-Script #[derive(Debug, Clone, Copy)] -pub struct Script { +pub struct ScriptParser { direct_eval: bool, } -impl Script { +impl ScriptParser { /// Create a new `Script` parser. #[inline] const fn new(direct_eval: bool) -> Self { @@ -247,19 +248,20 @@ impl Script { } } -impl TokenParser for Script +impl TokenParser for ScriptParser where R: Read, { - type Output = StatementList; + type Output = boa_ast::Script; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - let statement_list = - ScriptBody::new(true, cursor.strict(), self.direct_eval).parse(cursor, interner)?; + let script = boa_ast::Script::new( + ScriptBody::new(true, cursor.strict(), self.direct_eval).parse(cursor, interner)?, + ); // It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries. let mut lexical_names = FxHashSet::default(); - for name in top_level_lexically_declared_names(&statement_list) { + for name in lexically_declared_names(&script) { if !lexical_names.insert(name) { return Err(Error::general( "lexical name declared multiple times", @@ -269,7 +271,7 @@ where } // It is a Syntax Error if any element of the LexicallyDeclaredNames of ScriptBody also occurs in the VarDeclaredNames of ScriptBody. - for name in top_level_var_declared_names(&statement_list) { + for name in var_declared_names(&script) { if lexical_names.contains(&name) { return Err(Error::general( "lexical name declared multiple times", @@ -278,7 +280,7 @@ where } } - Ok(statement_list) + Ok(script) } } @@ -370,39 +372,6 @@ where } } -/// Helper to check if any parameter names are declared in the given list. -fn name_in_lexically_declared_names( - bound_names: &[Identifier], - lexical_names: &[Identifier], - position: Position, - interner: &Interner, -) -> ParseResult<()> { - for name in bound_names { - if lexical_names.contains(name) { - return Err(Error::general( - format!( - "formal parameter `{}` declared in lexically declared names", - interner.resolve_expect(name.sym()) - ), - position, - )); - } - } - Ok(()) -} - -/// 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. - fn or_abrupt(self) -> ParseResult; -} - -impl OrAbrupt for ParseResult> { - fn or_abrupt(self) -> ParseResult { - self?.ok_or(Error::AbruptEnd) - } -} - /// Parses a full module. /// /// More information: @@ -410,26 +379,22 @@ impl OrAbrupt for ParseResult> { /// /// [spec]: https://tc39.es/ecma262/#prod-Module #[derive(Debug, Clone, Copy)] -struct Module; +struct ModuleParser; -impl TokenParser for Module +impl TokenParser for ModuleParser where R: Read, { - type Output = ModuleItemList; + type Output = boa_ast::Module; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { cursor.set_module(); - let items = if cursor.peek(0, interner)?.is_some() { - self::statement::ModuleItemList.parse(cursor, interner)? - } else { - return Ok(Vec::new().into()); - }; + let module = boa_ast::Module::new(ModuleItemList.parse(cursor, interner)?); // It is a Syntax Error if the LexicallyDeclaredNames of ModuleItemList contains any duplicate entries. let mut bindings = FxHashSet::default(); - for name in lexically_declared_names(&items) { + for name in lexically_declared_names(&module) { if !bindings.insert(name) { return Err(Error::general( format!( @@ -443,7 +408,7 @@ where // It is a Syntax Error if any element of the LexicallyDeclaredNames of ModuleItemList also occurs in the // VarDeclaredNames of ModuleItemList. - for name in var_declared_names(&items) { + for name in var_declared_names(&module) { if !bindings.insert(name) { return Err(Error::general( format!( @@ -458,7 +423,7 @@ where // It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. { let mut exported_names = FxHashSet::default(); - for name in items.exported_names() { + for name in module.items().exported_names() { if !exported_names.insert(name) { return Err(Error::general( format!( @@ -473,7 +438,7 @@ where // It is a Syntax Error if any element of the ExportedBindings of ModuleItemList does not also occur in either // the VarDeclaredNames of ModuleItemList, or the LexicallyDeclaredNames of ModuleItemList. - for name in items.exported_bindings() { + for name in module.items().exported_bindings() { if !bindings.contains(&name) { return Err(Error::general( format!( @@ -486,7 +451,7 @@ where } // It is a Syntax Error if ModuleItemList Contains super. - if contains(&items, ContainsSymbol::Super) { + if contains(&module, ContainsSymbol::Super) { return Err(Error::general( "module cannot contain `super` on the top-level", Position::new(1, 1), @@ -494,19 +459,64 @@ where } // It is a Syntax Error if ModuleItemList Contains NewTarget. - if contains(&items, ContainsSymbol::NewTarget) { + if contains(&module, ContainsSymbol::NewTarget) { return Err(Error::general( "module cannot contain `new.target` on the top-level", Position::new(1, 1), )); } - // TODO: // It is a Syntax Error if ContainsDuplicateLabels of ModuleItemList with argument « » is true. // It is a Syntax Error if ContainsUndefinedBreakTarget of ModuleItemList with argument « » is true. // It is a Syntax Error if ContainsUndefinedContinueTarget of ModuleItemList with arguments « » and « » is true. + check_labels(&module).map_err(|error| { + Error::lex(LexError::Syntax( + error.message(interner).into(), + Position::new(1, 1), + )) + })?; + // It is a Syntax Error if AllPrivateIdentifiersValid of ModuleItemList with argument « » is false. + if !all_private_identifiers_valid(&module, Vec::new()) { + return Err(Error::general( + "invalid private identifier usage", + Position::new(1, 1), + )); + } + + Ok(module) + } +} - Ok(items) +/// Helper to check if any parameter names are declared in the given list. +fn name_in_lexically_declared_names( + bound_names: &[Identifier], + lexical_names: &[Identifier], + position: Position, + interner: &Interner, +) -> ParseResult<()> { + for name in bound_names { + if lexical_names.contains(name) { + return Err(Error::general( + format!( + "formal parameter `{}` declared in lexically declared names", + interner.resolve_expect(name.sym()) + ), + position, + )); + } + } + Ok(()) +} + +/// 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. + fn or_abrupt(self) -> ParseResult; +} + +impl OrAbrupt for ParseResult> { + fn or_abrupt(self) -> ParseResult { + self?.ok_or(Error::AbruptEnd) } } diff --git a/boa_parser/src/parser/statement/block/tests.rs b/boa_parser/src/parser/statement/block/tests.rs index 55eae0e203..4015c29eeb 100644 --- a/boa_parser/src/parser/statement/block/tests.rs +++ b/boa_parser/src/parser/statement/block/tests.rs @@ -12,7 +12,7 @@ use boa_ast::{ }, Call, Identifier, }, - function::{FormalParameterList, Function}, + function::{FormalParameterList, Function, FunctionBody}, statement::{Block, Return}, Declaration, Expression, Statement, StatementListItem, }; @@ -81,10 +81,12 @@ fn non_empty() { Declaration::Function(Function::new( Some(hello.into()), FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some(Literal::from(10).into())), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some(Literal::from(10).into())), + ))] + .into(), + ), )) .into(), Statement::Var(VarDeclaration( @@ -136,10 +138,12 @@ fn hoisting() { Declaration::Function(Function::new( Some(hello.into()), FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some(Literal::from(10).into())), - ))] - .into(), + FunctionBody::new( + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some(Literal::from(10).into())), + ))] + .into(), + ), )) .into(), ], diff --git a/boa_parser/src/parser/statement/declaration/export.rs b/boa_parser/src/parser/statement/declaration/export.rs index 9d30090697..54814b33b7 100644 --- a/boa_parser/src/parser/statement/declaration/export.rs +++ b/boa_parser/src/parser/statement/declaration/export.rs @@ -59,7 +59,7 @@ where let next = cursor.peek(0, interner).or_abrupt()?; - match next.kind() { + let export = match next.kind() { TokenKind::IdentifierName((Sym::AS, _)) => { cursor.advance(interner); let tok = cursor.next(interner).or_abrupt()?; @@ -67,9 +67,10 @@ where let alias = match tok.kind() { TokenKind::StringLiteral((export_name, _)) | TokenKind::IdentifierName((export_name, _)) => *export_name, + TokenKind::Keyword((kw, _)) => kw.to_sym(), _ => { return Err(Error::expected( - ["identifier".to_owned(), "string literal".to_owned()], + ["identifier name".to_owned(), "string literal".to_owned()], tok.to_string(interner), tok.span(), "export declaration", @@ -102,7 +103,11 @@ where "export declaration", )) } - } + }; + + cursor.expect_semicolon("star re-export", interner)?; + + export } TokenKind::Punctuator(Punctuator::OpenBlock) => { let names = NamedExports.parse(cursor, interner)?; @@ -115,11 +120,16 @@ where ) { let specifier = FromClause::new("export declaration").parse(cursor, interner)?; + + cursor.expect_semicolon("named re-exports", interner)?; + AstExportDeclaration::ReExport { kind: ReExportKind::Named { names }, specifier, } } else { + cursor.expect_semicolon("named exports", interner)?; + AstExportDeclaration::List(names) } } @@ -165,10 +175,14 @@ where ClassDeclaration::new(false, true, true).parse(cursor, interner)?, ) } - _ => AstExportDeclaration::DefaultAssignmentExpression( - AssignmentExpression::new(None, true, false, true) - .parse(cursor, interner)?, - ), + _ => { + let expr = AssignmentExpression::new(None, true, false, true) + .parse(cursor, interner)?; + + cursor.expect_semicolon("default expression export", interner)?; + + AstExportDeclaration::DefaultAssignmentExpression(expr) + } } } _ => AstExportDeclaration::Declaration( @@ -222,7 +236,9 @@ where } cursor.advance(interner); } - TokenKind::StringLiteral(_) | TokenKind::IdentifierName(_) => { + TokenKind::StringLiteral(_) + | TokenKind::IdentifierName(_) + | TokenKind::Keyword(_) => { list.push(ExportSpecifier.parse(cursor, interner)?); } _ => { @@ -272,6 +288,7 @@ where Ok(*ident) } TokenKind::IdentifierName((ident, _)) => Ok(*ident), + TokenKind::Keyword((kw, _)) => Ok(kw.to_sym()), _ => Err(Error::expected( ["identifier".to_owned(), "string literal".to_owned()], tok.to_string(interner), diff --git a/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs b/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs index 2cc0f15f03..6984959468 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs @@ -1,7 +1,7 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ - function::{AsyncFunction, FormalParameterList}, - Declaration, StatementList, + function::{AsyncFunction, FormalParameterList, FunctionBody}, + Declaration, }; use boa_interner::{Interner, Sym}; use boa_macros::utf16; @@ -19,7 +19,7 @@ fn async_function_declaration() { .into(), ), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), false, )) .into()], @@ -36,7 +36,7 @@ fn async_function_declaration_keywords() { vec![Declaration::AsyncFunction(AsyncFunction::new( Some(Sym::YIELD.into()), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), false, )) .into()], @@ -49,7 +49,7 @@ fn async_function_declaration_keywords() { vec![Declaration::AsyncFunction(AsyncFunction::new( Some(Sym::AWAIT.into()), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), false, )) .into()], diff --git a/boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/tests.rs b/boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/tests.rs index 865806c0a6..aea08f6a0b 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/tests.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/tests.rs @@ -1,7 +1,7 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ - function::{AsyncGenerator, FormalParameterList}, - Declaration, StatementList, + function::{AsyncGenerator, FormalParameterList, FunctionBody}, + Declaration, }; use boa_interner::Interner; use boa_macros::utf16; @@ -14,7 +14,7 @@ fn async_generator_function_declaration() { vec![Declaration::AsyncGenerator(AsyncGenerator::new( Some(interner.get_or_intern_static("gen", utf16!("gen")).into()), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), false, )) .into()], diff --git a/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs b/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs index 4c3fdb871d..d9fc304939 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -696,7 +696,9 @@ where cursor.set_strict(strict); statement_list }; - function::ClassElement::StaticBlock(statement_list) + function::ClassElement::StaticBlock(ast::function::FunctionBody::new( + statement_list, + )) } TokenKind::Punctuator(Punctuator::Mul) => { let token = cursor.peek(1, interner).or_abrupt()?; @@ -1346,7 +1348,7 @@ where } // ClassStaticBlockBody : ClassStaticBlockStatementList function::ClassElement::StaticBlock(block) => { - for node in block.statements() { + for node in &**block.statements() { // It is a Syntax Error if ContainsArguments of ClassStaticBlockStatementList is true. if contains_arguments(node) { return Err(Error::general( diff --git a/boa_parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs b/boa_parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs index 68b5d97521..e5f3424650 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs @@ -6,7 +6,7 @@ use boa_ast::{ literal::Literal, Call, Identifier, }, - function::{Class, ClassElement, FormalParameterList, Function}, + function::{Class, ClassElement, FormalParameterList, Function, FunctionBody}, property::{MethodDefinition, PropertyName}, Declaration, Expression, Statement, StatementList, StatementListItem, }; @@ -22,7 +22,7 @@ fn check_async_ordinary_method() { MethodDefinition::Ordinary(Function::new( None, FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), )), )]; @@ -120,7 +120,10 @@ fn check_new_target_with_property_access() { let constructor = Function::new( Some(interner.get_or_intern_static("A", utf16!("A")).into()), FormalParameterList::default(), - StatementList::new([Statement::Expression(console).into()], false), + FunctionBody::new(StatementList::new( + [Statement::Expression(console).into()], + false, + )), ); let class = Class::new( diff --git a/boa_parser/src/parser/statement/declaration/hoistable/function_decl/tests.rs b/boa_parser/src/parser/statement/declaration/hoistable/function_decl/tests.rs index 67d9ea376b..5bf2fbdf7f 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/function_decl/tests.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/function_decl/tests.rs @@ -1,7 +1,7 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ - function::{FormalParameterList, Function}, - Declaration, StatementList, + function::{FormalParameterList, Function, FunctionBody}, + Declaration, }; use boa_interner::Interner; use boa_macros::utf16; @@ -19,7 +19,7 @@ fn function_declaration() { .into(), ), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), )) .into()], interner, @@ -38,7 +38,7 @@ fn function_declaration_keywords() { .into(), ), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), )) .into()] }; diff --git a/boa_parser/src/parser/statement/declaration/hoistable/generator_decl/tests.rs b/boa_parser/src/parser/statement/declaration/hoistable/generator_decl/tests.rs index b0047d9ac4..215d3f8ba2 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/generator_decl/tests.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/generator_decl/tests.rs @@ -1,7 +1,7 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ - function::{FormalParameterList, Generator}, - Declaration, StatementList, + function::{FormalParameterList, FunctionBody, Generator}, + Declaration, }; use boa_interner::Interner; use boa_macros::utf16; @@ -14,7 +14,7 @@ fn generator_function_declaration() { vec![Declaration::Generator(Generator::new( Some(interner.get_or_intern_static("gen", utf16!("gen")).into()), FormalParameterList::default(), - StatementList::default(), + FunctionBody::default(), false, )) .into()], diff --git a/boa_parser/src/parser/statement/declaration/hoistable/mod.rs b/boa_parser/src/parser/statement/declaration/hoistable/mod.rs index 7cf3474aef..0c2a50a469 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/mod.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/mod.rs @@ -27,10 +27,11 @@ use crate::{ Error, }; use boa_ast::{ + self as ast, expression::Identifier, function::FormalParameterList, - operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, - Declaration, Keyword, Punctuator, StatementList, + operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, + Declaration, Keyword, Punctuator, }; use boa_interner::{Interner, Sym}; use boa_profiler::Profiler; @@ -148,7 +149,7 @@ fn parse_callable_declaration( c: &C, cursor: &mut Cursor, interner: &mut Interner, -) -> ParseResult<(Identifier, FormalParameterList, StatementList)> { +) -> ParseResult<(Identifier, FormalParameterList, ast::function::FunctionBody)> { let token = cursor.peek(0, interner).or_abrupt()?; let name_span = token.span(); let name = match token.kind() { @@ -222,7 +223,7 @@ fn parse_callable_declaration( // also occurs in the LexicallyDeclaredNames of FunctionBody. name_in_lexically_declared_names( &bound_names(¶ms), - &top_level_lexically_declared_names(&body), + &lexically_declared_names(&body), params_start_position, interner, )?; diff --git a/boa_parser/src/parser/statement/declaration/import.rs b/boa_parser/src/parser/statement/declaration/import.rs index 0f4f426c0c..755b605171 100644 --- a/boa_parser/src/parser/statement/declaration/import.rs +++ b/boa_parser/src/parser/statement/declaration/import.rs @@ -38,6 +38,39 @@ use std::io::Read; #[derive(Debug, Clone, Copy)] pub(in crate::parser) struct ImportDeclaration; +impl ImportDeclaration { + /// Tests if the next node is an `ImportDeclaration`. + pub(in crate::parser) fn test( + cursor: &mut Cursor, + interner: &mut Interner, + ) -> ParseResult { + if let Some(token) = cursor.peek(0, interner)? { + if let TokenKind::Keyword((Keyword::Import, escaped)) = token.kind() { + if *escaped { + return Err(Error::general( + "keyword `import` must not contain escaped characters", + token.span().start(), + )); + } + + if let Some(token) = cursor.peek(1, interner)? { + match token.kind() { + TokenKind::StringLiteral(_) + | TokenKind::Punctuator(Punctuator::OpenBlock | Punctuator::Mul) + | TokenKind::IdentifierName(_) + | TokenKind::Keyword((Keyword::Await | Keyword::Yield, _)) => { + return Ok(true) + } + _ => {} + } + } + } + } + + Ok(false) + } +} + impl TokenParser for ImportDeclaration where R: Read, diff --git a/boa_parser/src/parser/statement/mod.rs b/boa_parser/src/parser/statement/mod.rs index cbe56a471f..d55d85da6a 100644 --- a/boa_parser/src/parser/statement/mod.rs +++ b/boa_parser/src/parser/statement/mod.rs @@ -961,12 +961,20 @@ where let tok = cursor.peek(0, interner).or_abrupt()?; match tok.kind() { - TokenKind::Keyword((Keyword::Import, false)) => ImportDeclaration - .parse(cursor, interner) - .map(Self::Output::ImportDeclaration), TokenKind::Keyword((Keyword::Export, false)) => ExportDeclaration .parse(cursor, interner) .map(Self::Output::ExportDeclaration), + TokenKind::Keyword((Keyword::Import, false)) => { + if ImportDeclaration::test(cursor, interner)? { + ImportDeclaration + .parse(cursor, interner) + .map(Self::Output::ImportDeclaration) + } else { + StatementListItem::new(false, true, false) + .parse(cursor, interner) + .map(Self::Output::StatementListItem) + } + } _ => StatementListItem::new(false, true, false) .parse(cursor, interner) .map(Self::Output::StatementListItem), diff --git a/boa_parser/src/parser/tests/mod.rs b/boa_parser/src/parser/tests/mod.rs index 28fda835bf..f1c8a70d5b 100644 --- a/boa_parser/src/parser/tests/mod.rs +++ b/boa_parser/src/parser/tests/mod.rs @@ -20,10 +20,11 @@ use boa_ast::{ }, function::{ ArrowFunction, FormalParameter, FormalParameterList, FormalParameterListFlags, Function, + FunctionBody, }, property::PropertyDefinition, statement::{If, Return}, - Expression, Statement, StatementList, StatementListItem, + Expression, Script, Statement, StatementList, StatementListItem, }; use boa_interner::Interner; use boa_macros::utf16; @@ -39,7 +40,7 @@ where Parser::new(Source::from_bytes(js)) .parse_script(interner) .expect("failed to parse"), - StatementList::from(expr.into()) + Script::new(StatementList::from(expr.into())) ); } @@ -126,7 +127,10 @@ fn hoisting() { Declaration::Function(Function::new( Some(hello.into()), FormalParameterList::default(), - vec![Statement::Return(Return::new(Some(Literal::from(10).into()))).into()].into(), + FunctionBody::new( + vec![Statement::Return(Return::new(Some(Literal::from(10).into()))).into()] + .into(), + ), )) .into(), ], @@ -505,7 +509,9 @@ fn spread_in_arrow_function() { vec![Statement::Expression(Expression::from(ArrowFunction::new( None, params, - vec![Statement::Expression(Expression::from(Identifier::from(b))).into()].into(), + FunctionBody::new( + vec![Statement::Expression(Expression::from(Identifier::from(b))).into()].into(), + ), ))) .into()], interner, diff --git a/boa_runtime/src/lib.rs b/boa_runtime/src/lib.rs index bb7029054f..7c9caacf09 100644 --- a/boa_runtime/src/lib.rs +++ b/boa_runtime/src/lib.rs @@ -24,7 +24,7 @@ //! let js_code = "console.log('Hello World from a JS code string!')"; //! //! // Parse the source code -//! match context.eval_script(Source::from_bytes(js_code)) { +//! match context.eval(Source::from_bytes(js_code)) { //! Ok(res) => { //! println!( //! "{}", @@ -186,7 +186,7 @@ pub(crate) mod test { ) { #[track_caller] fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult { - context.eval_script(Source::from_bytes(source)) + context.eval(Source::from_bytes(source)) } #[track_caller] diff --git a/boa_tester/src/edition.rs b/boa_tester/src/edition.rs index aafb18eb4c..0deb013538 100644 --- a/boa_tester/src/edition.rs +++ b/boa_tester/src/edition.rs @@ -91,10 +91,6 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // https://github.com/tc39/proposal-duplicate-named-capturing-groups "regexp-duplicate-named-groups" => SpecEdition::ESNext, - // Symbols as WeakMap keys - // https://github.com/tc39/proposal-symbols-as-weakmap-keys - "symbols-as-weakmap-keys" => SpecEdition::ESNext, - // Array.prototype.toReversed, Array.prototype.toSorted, Array.prototype.toSpliced, // Array.prototype.with and the equivalent TypedArray methods. // https://github.com/tc39/proposal-change-array-by-copy/ @@ -116,6 +112,7 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { "Intl.DateTimeFormat-extend-timezonename" => SpecEdition::ESNext, "Intl.DisplayNames-v2" => SpecEdition::ESNext, "Intl.Segmenter" => SpecEdition::ESNext, + "symbols-as-weakmap-keys" => SpecEdition::ESNext, // Standard language features diff --git a/boa_tester/src/exec/js262.rs b/boa_tester/src/exec/js262.rs index ae43ca7bba..13e81a6830 100644 --- a/boa_tester/src/exec/js262.rs +++ b/boa_tester/src/exec/js262.rs @@ -92,17 +92,7 @@ fn detach_array_buffer(_: &JsValue, args: &[JsValue], _: &mut Context<'_>) -> Js fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { args.get(0).and_then(JsValue::as_string).map_or_else( || Ok(JsValue::undefined()), - |source_text| match context - .parse_script(Source::from_bytes(&source_text.to_std_string_escaped())) - { - // TODO: check strict - Err(e) => Err(JsNativeError::typ() - .with_message(format!("Uncaught Syntax Error: {e}")) - .into()), - // Calling eval here parses the code a second time. - // TODO: We can fix this after we have have defined the public api for the vm executer. - Ok(_) => context.eval_script(Source::from_bytes(&source_text.to_std_string_escaped())), - }, + |source_text| context.eval(Source::from_bytes(&source_text.to_std_string_escaped())), ) } diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index 1b06526716..3d3dd4fb56 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -13,12 +13,13 @@ use boa_engine::{ object::FunctionObjectBuilder, optimizer::OptimizerOptions, property::Attribute, + script::Script, Context, JsArgs, JsError, JsNativeErrorKind, JsValue, Source, }; use colored::Colorize; use fxhash::FxHashSet; use rayon::prelude::*; -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, eprintln, rc::Rc}; impl TestSuite { /// Runs the test suite. @@ -264,12 +265,26 @@ impl Test { } PromiseState::Fulfilled(v) => v, PromiseState::Rejected(err) => { - return (false, format!("Uncaught {}", err.display())) + let output = JsError::from_opaque(err.clone()) + .try_native(context) + .map_or_else( + |_| format!("Uncaught {}", err.display()), + |err| { + format!( + "Uncaught {err}{}", + err.cause().map_or_else(String::new, |cause| format!( + "\n caused by {cause}" + )) + ) + }, + ); + + return (false, output); } } } else { context.strict(strict); - match context.eval_script(source) { + match context.eval(source) { Ok(v) => v, Err(err) => return (false, format!("Uncaught {err}")), } @@ -312,7 +327,7 @@ impl Test { } } else { context.strict(strict); - match context.parse_script(source) { + match Script::parse(source, None, context) { Ok(_) => (false, "StatementList parsing should fail".to_owned()), Err(e) => (true, format!("Uncaught {e}")), } @@ -431,6 +446,8 @@ impl Test { let promise = module.evaluate(context); + context.run_jobs(); + match promise .state() .expect("tester can only use builtin promises") @@ -443,15 +460,12 @@ impl Test { } } else { context.strict(strict); - let script = match context.parse_script(source) { + let script = match Script::parse(source, None, context) { Ok(code) => code, Err(e) => return (false, format!("Uncaught {e}")), }; - match context - .compile_script(&script) - .and_then(|code| context.execute(code)) - { + match script.evaluate(context) { Ok(_) => return (false, "Script execution should fail".to_owned()), Err(e) => e, } @@ -545,10 +559,10 @@ impl Test { let sta = Source::from_reader(harness.sta.content.as_bytes(), Some(&harness.sta.path)); context - .eval_script(assert) + .eval(assert) .map_err(|e| format!("could not run assert.js:\n{e}"))?; context - .eval_script(sta) + .eval(sta) .map_err(|e| format!("could not run sta.js:\n{e}"))?; if self.flags.contains(TestFlags::ASYNC) { @@ -557,7 +571,7 @@ impl Test { Some(&harness.doneprint_handle.path), ); context - .eval_script(dph) + .eval(dph) .map_err(|e| format!("could not run doneprintHandle.js:\n{e}"))?; } @@ -567,7 +581,7 @@ impl Test { .get(include_name) .ok_or_else(|| format!("could not find the {include_name} include file."))?; let source = Source::from_reader(include.content.as_bytes(), Some(&include.path)); - context.eval_script(source).map_err(|e| { + context.eval(source).map_err(|e| { format!("could not run the harness `{include_name}`:\nUncaught {e}",) })?; } diff --git a/boa_wasm/src/lib.rs b/boa_wasm/src/lib.rs index fac3ab35ca..07ebe66a49 100644 --- a/boa_wasm/src/lib.rs +++ b/boa_wasm/src/lib.rs @@ -68,7 +68,7 @@ use wasm_bindgen::prelude::*; pub fn evaluate(src: &str) -> Result { // Setup executor Context::default() - .eval_script(Source::from_bytes(src)) + .eval(Source::from_bytes(src)) .map_err(|e| JsValue::from(format!("Uncaught {e}"))) .map(|v| v.display().to_string()) } diff --git a/test_ignore.toml b/test_ignore.toml index ceed610a55..b40150231a 100644 --- a/test_ignore.toml +++ b/test_ignore.toml @@ -10,11 +10,11 @@ features = [ "ShadowRealm", "FinalizationRegistry", "Atomics", - "dynamic_import", "decorators", "array-grouping", "IsHTMLDDA", "legacy-regexp", + "symbols-as-weakmap-keys", # Non-implemented Intl features "intl-normative-optional", @@ -24,8 +24,6 @@ features = [ # Stage 3 proposals - # https://github.com/tc39/proposal-symbols-as-weakmap-keys - "symbols-as-weakmap-keys", # https://github.com/tc39/proposal-intl-locale-info "Intl.Locale-info", # https://github.com/tc39/proposal-intl-enumeration