Browse Source

Implement dynamic imports (#2932)

* WIP: 76d7eceed6 Implement dynamic imports

* Expand `ActiveRunnable` to missing places

* Fix memory leak

* Fix docs

* Parse `import` as call expression

* Fix regressions

* Fix copypasted doc

* clippy fix

* Adjust ignored features

* Migrate away from `top_level_*` operations

* Fix more module tests

* Fix doc link
pull/2958/head
José Julián Espina 1 year ago committed by GitHub
parent
commit
09658b02bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      boa_ast/src/declaration/export.rs
  2. 11
      boa_ast/src/declaration/import.rs
  3. 4
      boa_ast/src/declaration/mod.rs
  4. 65
      boa_ast/src/expression/call.rs
  5. 24
      boa_ast/src/expression/literal/object.rs
  6. 9
      boa_ast/src/expression/mod.rs
  7. 14
      boa_ast/src/function/arrow_function.rs
  8. 14
      boa_ast/src/function/async_arrow_function.rs
  9. 14
      boa_ast/src/function/async_function.rs
  10. 16
      boa_ast/src/function/async_generator.rs
  11. 53
      boa_ast/src/function/class.rs
  12. 16
      boa_ast/src/function/generator.rs
  13. 29
      boa_ast/src/function/mod.rs
  14. 4
      boa_ast/src/lib.rs
  15. 6
      boa_ast/src/module_item_list/mod.rs
  16. 402
      boa_ast/src/operations.rs
  17. 111
      boa_ast/src/source.rs
  18. 9
      boa_ast/src/statement_list.rs
  19. 19
      boa_ast/src/visitor.rs
  20. 93
      boa_cli/src/main.rs
  21. 32
      boa_engine/benches/full.rs
  22. 19
      boa_engine/src/builtins/eval/mod.rs
  23. 40
      boa_engine/src/builtins/function/mod.rs
  24. 15
      boa_engine/src/builtins/json/mod.rs
  25. 4
      boa_engine/src/builtins/promise/mod.rs
  26. 8
      boa_engine/src/bytecompiler/class.rs
  27. 60
      boa_engine/src/bytecompiler/declarations.rs
  28. 7
      boa_engine/src/bytecompiler/expression/mod.rs
  29. 6
      boa_engine/src/bytecompiler/function.rs
  30. 4
      boa_engine/src/bytecompiler/mod.rs
  31. 2
      boa_engine/src/context/hooks.rs
  32. 82
      boa_engine/src/context/mod.rs
  33. 39
      boa_engine/src/job.rs
  34. 7
      boa_engine/src/lib.rs
  35. 19
      boa_engine/src/module/mod.rs
  36. 113
      boa_engine/src/module/source.rs
  37. 2
      boa_engine/src/object/builtins/jspromise.rs
  38. 163
      boa_engine/src/script.rs
  39. 34
      boa_engine/src/vm/code_block.rs
  40. 5
      boa_engine/src/vm/flowgraph/mod.rs
  41. 23
      boa_engine/src/vm/mod.rs
  42. 223
      boa_engine/src/vm/opcode/call/mod.rs
  43. 7
      boa_engine/src/vm/opcode/mod.rs
  44. 2
      boa_examples/src/bin/classes.rs
  45. 13
      boa_examples/src/bin/closures.rs
  46. 6
      boa_examples/src/bin/commuter_visitor.rs
  47. 2
      boa_examples/src/bin/derive.rs
  48. 2
      boa_examples/src/bin/futures.rs
  49. 2
      boa_examples/src/bin/loadfile.rs
  50. 2
      boa_examples/src/bin/loadstring.rs
  51. 6
      boa_examples/src/bin/modulehandler.rs
  52. 16
      boa_examples/src/bin/runtime_limits.rs
  53. 4
      boa_examples/src/bin/symbol_visitor.rs
  54. 23
      boa_parser/src/parser/expression/assignment/arrow_function.rs
  55. 23
      boa_parser/src/parser/expression/assignment/async_arrow_function.rs
  56. 4
      boa_parser/src/parser/expression/assignment/mod.rs
  57. 39
      boa_parser/src/parser/expression/left_hand_side/call.rs
  58. 50
      boa_parser/src/parser/expression/left_hand_side/mod.rs
  59. 4
      boa_parser/src/parser/expression/primary/async_function_expression/mod.rs
  60. 8
      boa_parser/src/parser/expression/primary/async_function_expression/tests.rs
  61. 4
      boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs
  62. 16
      boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs
  63. 4
      boa_parser/src/parser/expression/primary/function_expression/mod.rs
  64. 26
      boa_parser/src/parser/expression/primary/function_expression/tests.rs
  65. 4
      boa_parser/src/parser/expression/primary/generator_expression/mod.rs
  66. 6
      boa_parser/src/parser/expression/primary/generator_expression/tests.rs
  67. 12
      boa_parser/src/parser/expression/primary/object_initializer/mod.rs
  68. 22
      boa_parser/src/parser/expression/primary/object_initializer/tests.rs
  69. 4
      boa_parser/src/parser/function/mod.rs
  70. 75
      boa_parser/src/parser/function/tests.rs
  71. 150
      boa_parser/src/parser/mod.rs
  72. 6
      boa_parser/src/parser/statement/block/tests.rs
  73. 33
      boa_parser/src/parser/statement/declaration/export.rs
  74. 10
      boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs
  75. 6
      boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/tests.rs
  76. 6
      boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs
  77. 9
      boa_parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs
  78. 8
      boa_parser/src/parser/statement/declaration/hoistable/function_decl/tests.rs
  79. 6
      boa_parser/src/parser/statement/declaration/hoistable/generator_decl/tests.rs
  80. 9
      boa_parser/src/parser/statement/declaration/hoistable/mod.rs
  81. 33
      boa_parser/src/parser/statement/declaration/import.rs
  82. 14
      boa_parser/src/parser/statement/mod.rs
  83. 12
      boa_parser/src/parser/tests/mod.rs
  84. 4
      boa_runtime/src/lib.rs
  85. 5
      boa_tester/src/edition.rs
  86. 12
      boa_tester/src/exec/js262.rs
  87. 40
      boa_tester/src/exec/mod.rs
  88. 2
      boa_wasm/src/lib.rs
  89. 4
      test_ignore.toml

11
boa_ast/src/declaration/export.rs

@ -22,7 +22,9 @@ use crate::{
use boa_interner::Sym; use boa_interner::Sym;
/// The kind of re-export in an [`ExportDeclaration`]. /// 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 { pub enum ReExportKind {
/// Namespaced Re-export (`export * as name from "module-name"`). /// Namespaced Re-export (`export * as name from "module-name"`).
Namespaced { Namespaced {
@ -76,7 +78,8 @@ impl VisitWith for ReExportKind {
/// - [ECMAScript specification][spec] /// - [ECMAScript specification][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-ExportDeclaration /// [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 { pub enum ExportDeclaration {
/// Re-export. /// Re-export.
ReExport { ReExport {
@ -165,7 +168,9 @@ impl VisitWith for ExportDeclaration {
/// - [ECMAScript specification][spec] /// - [ECMAScript specification][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-ExportSpecifier /// [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 { pub struct ExportSpecifier {
alias: Sym, alias: Sym,
private_name: Sym, private_name: Sym,

11
boa_ast/src/declaration/import.rs

@ -21,7 +21,9 @@ use boa_interner::Sym;
use super::ModuleSpecifier; use super::ModuleSpecifier;
/// The kind of import in an [`ImportDeclaration`]. /// 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 { pub enum ImportKind {
/// Default (`import defaultName from "module-name"`) or unnamed (`import "module-name"). /// Default (`import defaultName from "module-name"`) or unnamed (`import "module-name").
DefaultOrUnnamed, DefaultOrUnnamed,
@ -77,7 +79,8 @@ impl VisitWith for ImportKind {
/// - [ECMAScript specification][spec] /// - [ECMAScript specification][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-ImportDeclaration /// [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 { pub struct ImportDeclaration {
/// Binding for the default export of `specifier`. /// Binding for the default export of `specifier`.
default: Option<Identifier>, default: Option<Identifier>,
@ -155,7 +158,9 @@ impl VisitWith for ImportDeclaration {
/// - [ECMAScript specification][spec] /// - [ECMAScript specification][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-ImportSpecifier /// [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 { pub struct ImportSpecifier {
binding: Identifier, binding: Identifier,
export_name: Sym, export_name: Sym,

4
boa_ast/src/declaration/mod.rs

@ -105,7 +105,9 @@ impl VisitWith for Declaration {
/// This is equivalent to the [`ModuleSpecifier`] production. /// This is equivalent to the [`ModuleSpecifier`] production.
/// ///
/// [`FromClause`]: https://tc39.es/ecma262/#prod-ModuleSpecifier /// [`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 { pub struct ModuleSpecifier {
module: Sym, module: Sym,
} }

65
boa_ast/src/expression/call.rs

@ -162,3 +162,68 @@ impl VisitWith for SuperCall {
ControlFlow::Continue(()) 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<Expression>,
}
impl ImportCall {
/// Creates a new `ImportCall` AST node.
pub fn new<A>(arg: A) -> Self
where
A: Into<Expression>,
{
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<ImportCall> 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<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.arg)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.arg)
}
}

24
boa_ast/src/expression/literal/object.rs

@ -256,16 +256,32 @@ impl ToIndentedString for ObjectLiteral {
MethodDefinition::Get(expression) MethodDefinition::Get(expression)
| MethodDefinition::Set(expression) | MethodDefinition::Set(expression)
| MethodDefinition::Ordinary(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) => { 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) => { 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) => { MethodDefinition::Async(expression) => {
block_to_string(expression.body(), interner, indent_n + 1) block_to_string(
expression.body().statements(),
interner,
indent_n + 1,
)
} }
}, },
) )

9
boa_ast/src/expression/mod.rs

@ -33,7 +33,7 @@ mod tagged_template;
mod r#yield; mod r#yield;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; 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 identifier::{Identifier, RESERVED_IDENTIFIERS_STRICT};
pub use new::New; pub use new::New;
pub use optional::{Optional, OptionalOperation, OptionalOperationKind}; pub use optional::{Optional, OptionalOperation, OptionalOperationKind};
@ -121,10 +121,12 @@ pub enum Expression {
/// See [`SuperCall`]. /// See [`SuperCall`].
SuperCall(SuperCall), SuperCall(SuperCall),
/// See [`ImportCall`].
ImportCall(ImportCall),
/// See [`Optional`]. /// See [`Optional`].
Optional(Optional), Optional(Optional),
// TODO: Import calls
/// See [`TaggedTemplate`]. /// See [`TaggedTemplate`].
TaggedTemplate(TaggedTemplate), TaggedTemplate(TaggedTemplate),
@ -192,6 +194,7 @@ impl Expression {
Self::New(new) => new.to_interned_string(interner), Self::New(new) => new.to_interned_string(interner),
Self::Call(call) => call.to_interned_string(interner), Self::Call(call) => call.to_interned_string(interner),
Self::SuperCall(supc) => supc.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::Optional(opt) => opt.to_interned_string(interner),
Self::NewTarget => "new.target".to_owned(), Self::NewTarget => "new.target".to_owned(),
Self::TaggedTemplate(tag) => tag.to_interned_string(interner), Self::TaggedTemplate(tag) => tag.to_interned_string(interner),
@ -300,6 +303,7 @@ impl VisitWith for Expression {
Self::New(n) => visitor.visit_new(n), Self::New(n) => visitor.visit_new(n),
Self::Call(c) => visitor.visit_call(c), Self::Call(c) => visitor.visit_call(c),
Self::SuperCall(sc) => visitor.visit_super_call(sc), Self::SuperCall(sc) => visitor.visit_super_call(sc),
Self::ImportCall(ic) => visitor.visit_import_call(ic),
Self::Optional(opt) => visitor.visit_optional(opt), Self::Optional(opt) => visitor.visit_optional(opt),
Self::TaggedTemplate(tt) => visitor.visit_tagged_template(tt), Self::TaggedTemplate(tt) => visitor.visit_tagged_template(tt),
Self::Assign(a) => visitor.visit_assign(a), Self::Assign(a) => visitor.visit_assign(a),
@ -341,6 +345,7 @@ impl VisitWith for Expression {
Self::New(n) => visitor.visit_new_mut(n), Self::New(n) => visitor.visit_new_mut(n),
Self::Call(c) => visitor.visit_call_mut(c), Self::Call(c) => visitor.visit_call_mut(c),
Self::SuperCall(sc) => visitor.visit_super_call_mut(sc), 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::Optional(opt) => visitor.visit_optional_mut(opt),
Self::TaggedTemplate(tt) => visitor.visit_tagged_template_mut(tt), Self::TaggedTemplate(tt) => visitor.visit_tagged_template_mut(tt),
Self::Assign(a) => visitor.visit_assign_mut(a), Self::Assign(a) => visitor.visit_assign_mut(a),

14
boa_ast/src/function/arrow_function.rs

@ -2,12 +2,12 @@ use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
expression::{Expression, Identifier}, expression::{Expression, Identifier},
join_nodes, StatementList, join_nodes,
}; };
use boa_interner::{Interner, ToIndentedString}; use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use super::FormalParameterList; use super::{FormalParameterList, FunctionBody};
/// An arrow function expression, as defined by the [spec]. /// An arrow function expression, as defined by the [spec].
/// ///
@ -24,7 +24,7 @@ use super::FormalParameterList;
pub struct ArrowFunction { pub struct ArrowFunction {
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
} }
impl ArrowFunction { impl ArrowFunction {
@ -34,7 +34,7 @@ impl ArrowFunction {
pub const fn new( pub const fn new(
name: Option<Identifier>, name: Option<Identifier>,
params: FormalParameterList, params: FormalParameterList,
body: StatementList, body: FunctionBody,
) -> Self { ) -> Self {
Self { Self {
name, name,
@ -66,7 +66,7 @@ impl ArrowFunction {
/// Gets the body of the arrow function. /// Gets the body of the arrow function.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn body(&self) -> &StatementList { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
} }
@ -102,7 +102,7 @@ impl VisitWith for ArrowFunction {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); 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<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -113,6 +113,6 @@ impl VisitWith for ArrowFunction {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); 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)
} }
} }

14
boa_ast/src/function/async_arrow_function.rs

@ -1,11 +1,11 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use super::FormalParameterList; use super::{FormalParameterList, FunctionBody};
use crate::try_break; use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
expression::{Expression, Identifier}, expression::{Expression, Identifier},
join_nodes, StatementList, join_nodes,
}; };
use boa_interner::{Interner, ToIndentedString}; use boa_interner::{Interner, ToIndentedString};
@ -24,7 +24,7 @@ use boa_interner::{Interner, ToIndentedString};
pub struct AsyncArrowFunction { pub struct AsyncArrowFunction {
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
} }
impl AsyncArrowFunction { impl AsyncArrowFunction {
@ -34,7 +34,7 @@ impl AsyncArrowFunction {
pub const fn new( pub const fn new(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
) -> Self { ) -> Self {
Self { Self {
name, name,
@ -66,7 +66,7 @@ impl AsyncArrowFunction {
/// Gets the body of the arrow function. /// Gets the body of the arrow function.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn body(&self) -> &StatementList { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
} }
@ -102,7 +102,7 @@ impl VisitWith for AsyncArrowFunction {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); 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<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -113,6 +113,6 @@ impl VisitWith for AsyncArrowFunction {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); 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)
} }
} }

14
boa_ast/src/function/async_function.rs

@ -4,12 +4,12 @@ use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
expression::{Expression, Identifier}, expression::{Expression, Identifier},
join_nodes, Declaration, StatementList, join_nodes, Declaration,
}; };
use boa_interner::{Interner, ToIndentedString}; use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use super::FormalParameterList; use super::{FormalParameterList, FunctionBody};
/// An async function definition, as defined by the [spec]. /// An async function definition, as defined by the [spec].
/// ///
@ -25,7 +25,7 @@ use super::FormalParameterList;
pub struct AsyncFunction { pub struct AsyncFunction {
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
} }
@ -36,7 +36,7 @@ impl AsyncFunction {
pub const fn new( pub const fn new(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
) -> Self { ) -> Self {
Self { Self {
@ -64,7 +64,7 @@ impl AsyncFunction {
/// Gets the body of the function declaration. /// Gets the body of the function declaration.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn body(&self) -> &StatementList { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
@ -122,7 +122,7 @@ impl VisitWith for AsyncFunction {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); 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<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -133,6 +133,6 @@ impl VisitWith for AsyncFunction {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); 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)
} }
} }

16
boa_ast/src/function/async_generator.rs

@ -4,12 +4,12 @@ use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
block_to_string, block_to_string,
expression::{Expression, Identifier}, expression::{Expression, Identifier},
join_nodes, Declaration, StatementList, join_nodes, Declaration,
}; };
use boa_interner::{Interner, ToIndentedString}; use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use super::FormalParameterList; use super::{FormalParameterList, FunctionBody};
/// An async generator definition, as defined by the [spec]. /// An async generator definition, as defined by the [spec].
/// ///
@ -24,7 +24,7 @@ use super::FormalParameterList;
pub struct AsyncGenerator { pub struct AsyncGenerator {
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
} }
@ -35,7 +35,7 @@ impl AsyncGenerator {
pub const fn new( pub const fn new(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
) -> Self { ) -> Self {
Self { Self {
@ -63,7 +63,7 @@ impl AsyncGenerator {
/// Gets the body of the async generator expression /// Gets the body of the async generator expression
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn body(&self) -> &StatementList { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
@ -84,7 +84,7 @@ impl ToIndentedString for AsyncGenerator {
buf.push_str(&format!( buf.push_str(&format!(
"({}) {}", "({}) {}",
join_nodes(interner, self.parameters.as_ref()), join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation) block_to_string(self.body.statements(), interner, indentation)
)); ));
buf buf
@ -114,7 +114,7 @@ impl VisitWith for AsyncGenerator {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); 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<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -125,6 +125,6 @@ impl VisitWith for AsyncGenerator {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); 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)
} }
} }

53
boa_ast/src/function/class.rs

@ -6,7 +6,7 @@ use crate::{
property::{MethodDefinition, PropertyName}, property::{MethodDefinition, PropertyName},
try_break, try_break,
visitor::{VisitWith, Visitor, VisitorMut}, visitor::{VisitWith, Visitor, VisitorMut},
Declaration, StatementList, ToStringEscaped, Declaration, ToStringEscaped,
}; };
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString}; use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow; use core::ops::ControlFlow;
@ -121,7 +121,7 @@ impl ToIndentedString for Class {
buf.push_str(&format!( buf.push_str(&format!(
"{indentation}constructor({}) {}\n", "{indentation}constructor({}) {}\n",
join_nodes(interner, expr.parameters().as_ref()), 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() { for element in self.elements.iter() {
@ -155,16 +155,16 @@ impl ToIndentedString for Class {
MethodDefinition::Get(expr) MethodDefinition::Get(expr)
| MethodDefinition::Set(expr) | MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(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) => { 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) => { 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) => { 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::Get(expr)
| MethodDefinition::Set(expr) | MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(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) => { 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) => { 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) => { 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::Get(expr)
| MethodDefinition::Set(expr) | MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(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) => { 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) => { 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) => { 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::Get(expr)
| MethodDefinition::Set(expr) | MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(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) => { 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) => { 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) => { 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!( format!(
"{indentation}static {}\n", "{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]. /// An element that can be within a [`Class`], as defined by the [spec].
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-ClassElement /// [spec]: https://tc39.es/ecma262/#prod-ClassElement
@ -454,7 +461,7 @@ pub enum ClassElement {
PrivateStaticFieldDefinition(PrivateName, Option<Expression>), PrivateStaticFieldDefinition(PrivateName, Option<Expression>),
/// A static block, where a class can have initialization logic for its static fields. /// A static block, where a class can have initialization logic for its static fields.
StaticBlock(StatementList), StaticBlock(StaticBlockBody),
} }
impl VisitWith for ClassElement { impl VisitWith for ClassElement {
@ -489,7 +496,7 @@ impl VisitWith for ClassElement {
ControlFlow::Continue(()) 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(()) ControlFlow::Continue(())
} }
} }
Self::StaticBlock(sl) => visitor.visit_statement_list_mut(sl), Self::StaticBlock(sl) => visitor.visit_script_mut(sl),
} }
} }
} }

16
boa_ast/src/function/generator.rs

@ -1,7 +1,7 @@
use crate::{ use crate::{
block_to_string, block_to_string,
expression::{Expression, Identifier}, expression::{Expression, Identifier},
join_nodes, Declaration, StatementList, join_nodes, Declaration,
}; };
use core::ops::ControlFlow; use core::ops::ControlFlow;
@ -9,7 +9,7 @@ use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString}; use boa_interner::{Interner, ToIndentedString};
use super::FormalParameterList; use super::{FormalParameterList, FunctionBody};
/// A generator definition, as defined by the [spec]. /// A generator definition, as defined by the [spec].
/// ///
@ -26,7 +26,7 @@ use super::FormalParameterList;
pub struct Generator { pub struct Generator {
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
} }
@ -37,7 +37,7 @@ impl Generator {
pub const fn new( pub const fn new(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
) -> Self { ) -> Self {
Self { Self {
@ -65,7 +65,7 @@ impl Generator {
/// Gets the body of the generator declaration. /// Gets the body of the generator declaration.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn body(&self) -> &StatementList { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
@ -86,7 +86,7 @@ impl ToIndentedString for Generator {
buf.push_str(&format!( buf.push_str(&format!(
"({}) {}", "({}) {}",
join_nodes(interner, self.parameters.as_ref()), join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation) block_to_string(self.body.statements(), interner, indentation)
)); ));
buf buf
@ -116,7 +116,7 @@ impl VisitWith for Generator {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); 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<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -127,6 +127,6 @@ impl VisitWith for Generator {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); 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)
} }
} }

29
boa_ast/src/function/mod.rs

@ -38,9 +38,9 @@ use core::ops::ControlFlow;
pub use generator::Generator; pub use generator::Generator;
pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags}; pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; 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 boa_interner::{Interner, ToIndentedString};
use super::expression::{Expression, Identifier}; use super::expression::{Expression, Identifier};
@ -62,7 +62,7 @@ use super::Declaration;
pub struct Function { pub struct Function {
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
} }
@ -73,7 +73,7 @@ impl Function {
pub const fn new( pub const fn new(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
) -> Self { ) -> Self {
Self { Self {
name, name,
@ -89,7 +89,7 @@ impl Function {
pub const fn new_with_binding_identifier( pub const fn new_with_binding_identifier(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: StatementList, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
) -> Self { ) -> Self {
Self { Self {
@ -117,7 +117,7 @@ impl Function {
/// Gets the body of the function declaration. /// Gets the body of the function declaration.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn body(&self) -> &StatementList { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
@ -138,7 +138,7 @@ impl ToIndentedString for Function {
buf.push_str(&format!( buf.push_str(&format!(
"({}) {}", "({}) {}",
join_nodes(interner, self.parameters.as_ref()), join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation) block_to_string(self.body.statements(), interner, indentation)
)); ));
buf buf
@ -168,7 +168,7 @@ impl VisitWith for Function {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); 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<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -179,6 +179,17 @@ impl VisitWith for Function {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); 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;

4
boa_ast/src/lib.rs

@ -75,15 +75,16 @@
clippy::option_if_let_else clippy::option_if_let_else
)] )]
mod module_item_list;
mod position; mod position;
mod punctuator; mod punctuator;
mod source;
mod statement_list; mod statement_list;
pub mod declaration; pub mod declaration;
pub mod expression; pub mod expression;
pub mod function; pub mod function;
pub mod keyword; pub mod keyword;
pub mod module_item_list;
pub mod operations; pub mod operations;
pub mod pattern; pub mod pattern;
pub mod property; pub mod property;
@ -99,6 +100,7 @@ pub use self::{
module_item_list::{ModuleItem, ModuleItemList}, module_item_list::{ModuleItem, ModuleItemList},
position::{Position, Span}, position::{Position, Span},
punctuator::Punctuator, punctuator::Punctuator,
source::{Module, Script},
statement::Statement, statement::Statement,
statement_list::{StatementList, StatementListItem}, statement_list::{StatementList, StatementListItem},
}; };

6
boa_ast/src/module_item_list/mod.rs

@ -31,7 +31,8 @@ use crate::{
/// - [ECMAScript specification][spec] /// - [ECMAScript specification][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-ModuleItemList /// [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 { pub struct ModuleItemList {
items: Box<[ModuleItem]>, items: Box<[ModuleItem]>,
} }
@ -462,7 +463,8 @@ impl VisitWith for ModuleItemList {
/// - [ECMAScript specification][spec] /// - [ECMAScript specification][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-ModuleItem /// [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 { pub enum ModuleItem {
/// See [`ImportDeclaration`]. /// See [`ImportDeclaration`].
ImportDeclaration(ImportDeclaration), ImportDeclaration(ImportDeclaration),

402
boa_ast/src/operations.rs

@ -9,7 +9,9 @@ use boa_interner::{Interner, Sym};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use crate::{ use crate::{
declaration::{Binding, ExportDeclaration, ImportDeclaration, VarDeclaration, Variable}, declaration::{
Binding, ExportDeclaration, ImportDeclaration, LexicalDeclaration, VarDeclaration, Variable,
},
expression::{ expression::{
access::{PrivatePropertyAccess, SuperPropertyAccess}, access::{PrivatePropertyAccess, SuperPropertyAccess},
operator::BinaryInPrivate, operator::BinaryInPrivate,
@ -26,7 +28,7 @@ use crate::{
}, },
try_break, try_break,
visitor::{NodeRef, VisitWith, Visitor}, 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. /// 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> { impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T> {
type BreakTy = Infallible; type BreakTy = Infallible;
fn visit_script(&mut self, node: &'ast Script) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.statements(), self.0);
ControlFlow::Continue(())
}
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> { fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> { fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
if let Statement::Labelled(labelled) = node { if let Statement::Labelled(labelled) = node {
return self.visit_labelled(labelled); return self.visit_labelled(labelled);
} }
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_declaration(&mut self, node: &'ast Declaration) -> ControlFlow<Self::BreakTy> { fn visit_declaration(&mut self, node: &'ast Declaration) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_declaration(node) BoundNamesVisitor(self.0).visit_declaration(node)
} }
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> { fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node { match node {
LabelledItem::Function(f) => BoundNamesVisitor(self.0).visit_function(f), LabelledItem::Function(f) => BoundNamesVisitor(self.0).visit_function(f),
LabelledItem::Statement(_) => ControlFlow::Continue(()), LabelledItem::Statement(_) => ControlFlow::Continue(()),
} }
} }
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> { fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0); self.visit_script(node.body())
ControlFlow::Continue(())
} }
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> { fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0); self.visit_script(node.body())
ControlFlow::Continue(())
} }
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> { fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0); self.visit_script(node.body())
ControlFlow::Continue(())
} }
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> { fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0); self.visit_script(node.body())
ControlFlow::Continue(())
} }
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> { fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0); self.visit_script(node.body())
ControlFlow::Continue(())
} }
fn visit_async_arrow_function( fn visit_async_arrow_function(
&mut self, &mut self,
node: &'ast AsyncArrowFunction, node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0); self.visit_script(node.body())
ControlFlow::Continue(())
} }
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> { fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(stmts) = node { if let ClassElement::StaticBlock(body) = node {
top_level_lexicals(stmts, self.0); self.visit_script(body);
} }
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_import_declaration( fn visit_import_declaration(
&mut self, &mut self,
node: &'ast ImportDeclaration, node: &'ast ImportDeclaration,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_import_declaration(node) BoundNamesVisitor(self.0).visit_import_declaration(node)
} }
fn visit_export_declaration( fn visit_export_declaration(
&mut self, &mut self,
node: &'ast ExportDeclaration, node: &'ast ExportDeclaration,
@ -517,10 +557,6 @@ impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T>
} }
BoundNamesVisitor(self.0).visit_export_declaration(node) 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. /// Returns a list with the lexical bindings of a node, which may contain duplicates.
@ -562,6 +598,34 @@ struct VarDeclaredNamesVisitor<'a>(&'a mut FxHashSet<Identifier>);
impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
type BreakTy = Infallible; type BreakTy = Infallible;
fn visit_script(&mut self, node: &'ast Script) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.statements(), self.0);
ControlFlow::Continue(())
}
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> { fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
match node { match node {
Statement::Empty Statement::Empty
@ -682,28 +746,24 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
} }
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> { fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0); self.visit_script(node.body())
ControlFlow::Continue(())
} }
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> { fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0); self.visit_script(node.body())
ControlFlow::Continue(())
} }
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> { fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0); self.visit_script(node.body())
ControlFlow::Continue(())
} }
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> { fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0); self.visit_script(node.body())
ControlFlow::Continue(())
} }
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> { fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(stmts) = node { if let ClassElement::StaticBlock(body) = node {
top_level_vars(stmts, self.0); self.visit_script(body);
} }
node.visit_with(self) node.visit_with(self)
} }
@ -726,10 +786,6 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
_ => ControlFlow::Continue(()), _ => 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. /// 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`. /// 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<T: IdentList>(stmts: &StatementList, names: &mut T) { fn top_level_lexicals<T: IdentList>(stmts: &StatementList, names: &mut T) {
for stmt in stmts.statements() { for stmt in stmts.statements() {
if let StatementListItem::Declaration(decl) = stmt { if let StatementListItem::Declaration(decl) = stmt {
@ -770,20 +830,11 @@ fn top_level_lexicals<T: IdentList>(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 /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvardeclarednames
#[must_use]
#[inline]
pub fn top_level_lexically_declared_names(stmts: &StatementList) -> Vec<Identifier> {
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`.
fn top_level_vars(stmts: &StatementList, names: &mut FxHashSet<Identifier>) { fn top_level_vars(stmts: &StatementList, names: &mut FxHashSet<Identifier>) {
for stmt in stmts.statements() { for stmt in stmts.statements() {
match stmt { match stmt {
@ -826,19 +877,6 @@ fn top_level_vars(stmts: &StatementList, names: &mut FxHashSet<Identifier>) {
} }
} }
/// 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<Identifier> {
let mut names = FxHashSet::default();
top_level_vars(stmts, &mut names);
names
}
/// Returns `true` if all private identifiers in a node are valid. /// Returns `true` if all private identifiers in a node are valid.
/// ///
/// This is equivalent to the [`AllPrivateIdentifiersValid`][spec] syntax operation in the spec. /// This is equivalent to the [`AllPrivateIdentifiersValid`][spec] syntax operation in the spec.
@ -1319,13 +1357,69 @@ where
node.visit_with(&mut visitor).is_break() 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<Identifier> {
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<Declaration> 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. /// Returns a list of lexically scoped declarations of the given node.
/// ///
/// This is equivalent to the [`LexicallyScopedDeclarations`][spec] syntax operation in the spec. /// This is equivalent to the [`LexicallyScopedDeclarations`][spec] syntax operation in the spec.
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallyscopeddeclarations /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallyscopeddeclarations
#[must_use] #[must_use]
pub fn lexically_scoped_declarations<'a, N>(node: &'a N) -> Vec<Declaration> pub fn lexically_scoped_declarations<'a, N>(node: &'a N) -> Vec<LexicallyScopedDeclaration>
where where
&'a N: Into<NodeRef<'a>>, &'a N: Into<NodeRef<'a>>,
{ {
@ -1336,22 +1430,91 @@ where
/// The [`Visitor`] used to obtain the lexically scoped declarations of a node. /// The [`Visitor`] used to obtain the lexically scoped declarations of a node.
#[derive(Debug)] #[derive(Debug)]
struct LexicallyScopedDeclarationsVisitor<'a>(&'a mut Vec<Declaration>); struct LexicallyScopedDeclarationsVisitor<'a>(&'a mut Vec<LexicallyScopedDeclaration>);
impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_> { impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_> {
type BreakTy = Infallible; type BreakTy = Infallible;
// ScriptBody : StatementList
fn visit_script(&mut self, node: &'ast Script) -> ControlFlow<Self::BreakTy> {
// 1. Return TopLevelLexicallyScopedDeclarations of StatementList.
TopLevelLexicallyScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements())
}
fn visit_export_declaration(
&mut self,
node: &'ast ExportDeclaration,
) -> ControlFlow<Self::BreakTy> {
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( fn visit_statement_list_item(
&mut self, &mut self,
node: &'ast StatementListItem, node: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
match node { match node {
// StatementListItem : Statement
StatementListItem::Statement(Statement::Labelled(labelled)) => { StatementListItem::Statement(Statement::Labelled(labelled)) => {
// 1. If Statement is Statement : LabelledStatement , return LexicallyScopedDeclarations of LabelledStatement.
self.visit_labelled(labelled) self.visit_labelled(labelled)
} }
StatementListItem::Statement(_) => ControlFlow::Continue(()), StatementListItem::Statement(_) => {
// 2. Return a new empty List.
ControlFlow::Continue(())
}
// StatementListItem : Declaration
StatementListItem::Declaration(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(()) ControlFlow::Continue(())
} }
} }
@ -1359,46 +1522,77 @@ impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_> {
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> { fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node { match node {
// LabelledItem : FunctionDeclaration
LabelledItem::Function(f) => { LabelledItem::Function(f) => {
self.0.push(f.clone().into()); // 1. Return « FunctionDeclaration ».
ControlFlow::Continue(()) self.0.push(LexicallyScopedDeclaration::Function(f.clone()));
} }
LabelledItem::Statement(_) => ControlFlow::Continue(()),
// LabelledItem : Statement
LabelledItem::Statement(_) => {
// 1. Return a new empty List.
} }
} }
ControlFlow::Continue(())
}
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> { fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
match node { match node {
ModuleItem::StatementListItem(item) => self.visit_statement_list_item(item), ModuleItem::StatementListItem(item) => self.visit_statement_list_item(item),
ModuleItem::ExportDeclaration(ExportDeclaration::Declaration(declaration)) => { ModuleItem::ExportDeclaration(export) => self.visit_export_declaration(export),
self.0.push(declaration.clone());
// ModuleItem : ImportDeclaration
ModuleItem::ImportDeclaration(_) => {
// 1. Return a new empty List.
ControlFlow::Continue(()) 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)) => { /// The [`Visitor`] used to obtain the top level lexically scoped declarations of a node.
self.0.push(f.clone().into()); ///
ControlFlow::Continue(()) /// 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<LexicallyScopedDeclaration>);
impl<'ast> Visitor<'ast> for TopLevelLexicallyScopedDeclarationsVisitor<'_> {
type BreakTy = Infallible;
fn visit_statement_list_item(
&mut self,
node: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> {
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.
} }
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultClassDeclaration(f)) => {
self.0.push(f.clone().into()); // 2. Return « Declaration ».
ControlFlow::Continue(()) Declaration::Class(cl) => {
self.0.push(LexicallyScopedDeclaration::Class(cl.clone()));
} }
ModuleItem::ImportDeclaration(_) | ModuleItem::ExportDeclaration(_) => { Declaration::Lexical(lex) => {
ControlFlow::Continue(()) self.0
.push(LexicallyScopedDeclaration::LexicalDeclaration(lex.clone()));
} }
},
// StatementListItem : Statement
StatementListItem::Statement(_) => {
// 1. Return a new empty List.
} }
} }
ControlFlow::Continue(())
}
} }
/// The type of a var scoped declaration. /// The type of a var scoped declaration.
@ -1456,6 +1650,12 @@ struct VarScopedDeclarationsVisitor<'a>(&'a mut Vec<VarScopedDeclaration>);
impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> { impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> {
type BreakTy = Infallible; type BreakTy = Infallible;
// ScriptBody : StatementList
fn visit_script(&mut self, node: &'ast Script) -> ControlFlow<Self::BreakTy> {
// 1. Return TopLevelVarScopedDeclarations of StatementList.
TopLevelVarScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements())
}
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> { fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
match node { match node {
Statement::Block(s) => self.visit(s), 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<Self::BreakTy> { fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
match node { match node {
ModuleItem::ExportDeclaration(ExportDeclaration::VarStatement(var)) => self.visit(var), // ModuleItem : ExportDeclaration
ModuleItem::StatementListItem(item) => self.visit(item), ModuleItem::ExportDeclaration(decl) => {
_ => ControlFlow::Continue(()), 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. /// This is equivalent to the [`TopLevelVarScopedDeclarations`][spec] syntax operation in the spec.
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvarscopeddeclarations /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvarscopeddeclarations
#[must_use]
pub fn top_level_var_scoped_declarations<'a, N>(node: &'a N) -> Vec<VarScopedDeclaration>
where
&'a N: Into<NodeRef<'a>>,
{
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)] #[derive(Debug)]
struct TopLevelVarScopedDeclarationsVisitor<'a>(&'a mut Vec<VarScopedDeclaration>); struct TopLevelVarScopedDeclarationsVisitor<'a>(&'a mut Vec<VarScopedDeclaration>);

111
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<V::BreakTy>
where
V: Visitor<'a>,
{
self.statements.visit_with(visitor)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
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<V::BreakTy>
where
V: Visitor<'a>,
{
self.items.visit_with(visitor)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
self.items.visit_with_mut(visitor)
}
}

9
boa_ast/src/statement_list.rs

@ -8,6 +8,7 @@ use crate::{
}; };
use boa_interner::{Interner, ToIndentedString}; use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use std::ops::Deref;
/// An item inside a [`StatementList`] Parse Node, as defined by the [spec]. /// An item inside a [`StatementList`] Parse Node, as defined by the [spec].
/// ///
@ -149,6 +150,14 @@ impl From<Vec<StatementListItem>> for StatementList {
} }
} }
impl Deref for StatementList {
type Target = [StatementListItem];
fn deref(&self) -> &Self::Target {
&self.statements
}
}
impl ToIndentedString for StatementList { impl ToIndentedString for StatementList {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = String::new(); let mut buf = String::new();

19
boa_ast/src/visitor.rs

@ -21,7 +21,7 @@ use crate::{
assign::{Assign, AssignTarget}, assign::{Assign, AssignTarget},
Binary, BinaryInPrivate, Conditional, Unary, Update, 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, OptionalOperationKind, Parenthesized, Spread, SuperCall, TaggedTemplate, Yield,
}, },
function::{ function::{
@ -38,7 +38,7 @@ use crate::{
Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw, Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw,
Try, With, Try, With,
}, },
ModuleItem, ModuleItemList, StatementList, StatementListItem, Module, ModuleItem, ModuleItemList, Script, StatementList, StatementListItem,
}; };
use boa_interner::Sym; use boa_interner::Sym;
@ -118,6 +118,8 @@ macro_rules! node_ref {
} }
node_ref! { node_ref! {
Script,
Module,
StatementList, StatementList,
StatementListItem, StatementListItem,
Statement, Statement,
@ -164,6 +166,7 @@ node_ref! {
New, New,
Call, Call,
SuperCall, SuperCall,
ImportCall,
Optional, Optional,
TaggedTemplate, TaggedTemplate,
Assign, Assign,
@ -217,6 +220,8 @@ pub trait Visitor<'ast>: Sized {
/// Type which will be propagated from the visitor if completing early. /// Type which will be propagated from the visitor if completing early.
type BreakTy; type BreakTy;
define_visit!(visit_script, Script);
define_visit!(visit_module, Module);
define_visit!(visit_statement_list, StatementList); define_visit!(visit_statement_list, StatementList);
define_visit!(visit_statement_list_item, StatementListItem); define_visit!(visit_statement_list_item, StatementListItem);
define_visit!(visit_statement, Statement); define_visit!(visit_statement, Statement);
@ -263,6 +268,7 @@ pub trait Visitor<'ast>: Sized {
define_visit!(visit_new, New); define_visit!(visit_new, New);
define_visit!(visit_call, Call); define_visit!(visit_call, Call);
define_visit!(visit_super_call, SuperCall); define_visit!(visit_super_call, SuperCall);
define_visit!(visit_import_call, ImportCall);
define_visit!(visit_optional, Optional); define_visit!(visit_optional, Optional);
define_visit!(visit_tagged_template, TaggedTemplate); define_visit!(visit_tagged_template, TaggedTemplate);
define_visit!(visit_assign, Assign); define_visit!(visit_assign, Assign);
@ -313,6 +319,8 @@ pub trait Visitor<'ast>: Sized {
fn visit<N: Into<NodeRef<'ast>>>(&mut self, node: N) -> ControlFlow<Self::BreakTy> { fn visit<N: Into<NodeRef<'ast>>>(&mut self, node: N) -> ControlFlow<Self::BreakTy> {
let node = node.into(); let node = node.into();
match node { 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::StatementList(n) => self.visit_statement_list(n),
NodeRef::StatementListItem(n) => self.visit_statement_list_item(n), NodeRef::StatementListItem(n) => self.visit_statement_list_item(n),
NodeRef::Statement(n) => self.visit_statement(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::New(n) => self.visit_new(n),
NodeRef::Call(n) => self.visit_call(n), NodeRef::Call(n) => self.visit_call(n),
NodeRef::SuperCall(n) => self.visit_super_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::Optional(n) => self.visit_optional(n),
NodeRef::TaggedTemplate(n) => self.visit_tagged_template(n), NodeRef::TaggedTemplate(n) => self.visit_tagged_template(n),
NodeRef::Assign(n) => self.visit_assign(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 which will be propagated from the visitor if completing early.
type BreakTy; 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_mut, StatementList);
define_visit_mut!(visit_statement_list_item_mut, StatementListItem); define_visit_mut!(visit_statement_list_item_mut, StatementListItem);
define_visit_mut!(visit_statement_mut, Statement); 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_new_mut, New);
define_visit_mut!(visit_call_mut, Call); define_visit_mut!(visit_call_mut, Call);
define_visit_mut!(visit_super_call_mut, SuperCall); 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_optional_mut, Optional);
define_visit_mut!(visit_tagged_template_mut, TaggedTemplate); define_visit_mut!(visit_tagged_template_mut, TaggedTemplate);
define_visit_mut!(visit_assign_mut, Assign); define_visit_mut!(visit_assign_mut, Assign);
@ -510,6 +522,8 @@ pub trait VisitorMut<'ast>: Sized {
fn visit<N: Into<NodeRefMut<'ast>>>(&mut self, node: N) -> ControlFlow<Self::BreakTy> { fn visit<N: Into<NodeRefMut<'ast>>>(&mut self, node: N) -> ControlFlow<Self::BreakTy> {
let node = node.into(); let node = node.into();
match node { 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::StatementList(n) => self.visit_statement_list_mut(n),
NodeRefMut::StatementListItem(n) => self.visit_statement_list_item_mut(n), NodeRefMut::StatementListItem(n) => self.visit_statement_list_item_mut(n),
NodeRefMut::Statement(n) => self.visit_statement_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::New(n) => self.visit_new_mut(n),
NodeRefMut::Call(n) => self.visit_call_mut(n), NodeRefMut::Call(n) => self.visit_call_mut(n),
NodeRefMut::SuperCall(n) => self.visit_super_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::Optional(n) => self.visit_optional_mut(n),
NodeRefMut::TaggedTemplate(n) => self.visit_tagged_template_mut(n), NodeRefMut::TaggedTemplate(n) => self.visit_tagged_template_mut(n),
NodeRefMut::Assign(n) => self.visit_assign_mut(n), NodeRefMut::Assign(n) => self.visit_assign_mut(n),

93
boa_cli/src/main.rs

@ -59,10 +59,11 @@
)] )]
#![allow(clippy::option_if_let_else, clippy::redundant_pub_crate)] #![allow(clippy::option_if_let_else, clippy::redundant_pub_crate)]
use boa_ast as _;
mod debug; mod debug;
mod helper; mod helper;
use boa_ast::StatementList;
use boa_engine::{ use boa_engine::{
builtins::promise::PromiseState, builtins::promise::PromiseState,
context::ContextBuilder, context::ContextBuilder,
@ -70,15 +71,19 @@ use boa_engine::{
module::{Module, ModuleLoader, SimpleModuleLoader}, module::{Module, ModuleLoader, SimpleModuleLoader},
optimizer::OptimizerOptions, optimizer::OptimizerOptions,
property::Attribute, property::Attribute,
script::Script,
vm::flowgraph::{Direction, Graph}, vm::flowgraph::{Direction, Graph},
Context, JsNativeError, JsResult, Source, Context, JsError, JsNativeError, JsResult, Source,
}; };
use boa_runtime::Console; use boa_runtime::Console;
use clap::{Parser, ValueEnum, ValueHint}; use clap::{Parser, ValueEnum, ValueHint};
use colored::{Color, Colorize}; use colored::{Color, Colorize};
use debug::init_boa_debug_object; use debug::init_boa_debug_object;
use rustyline::{config::Config, error::ReadlineError, EditMode, Editor}; 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(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
#[cfg_attr( #[cfg_attr(
@ -174,7 +179,7 @@ impl Opt {
} }
} }
#[derive(Debug, Clone, ValueEnum)] #[derive(Debug, Copy, Clone, Default, ValueEnum)]
enum DumpFormat { enum DumpFormat {
/// The different types of format available for dumping. /// The different types of format available for dumping.
// NOTE: This can easily support other formats just by // NOTE: This can easily support other formats just by
@ -185,6 +190,7 @@ enum DumpFormat {
// arg_enum! macro does not support it. // arg_enum! macro does not support it.
// This is the default format that you get from std::fmt::Debug. // This is the default format that you get from std::fmt::Debug.
#[default]
Debug, Debug,
// This is a minified json format. // This is a minified json format.
@ -212,19 +218,6 @@ enum FlowgraphDirection {
RightToLeft, 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<S>(src: &S, context: &mut Context<'_>) -> Result<StatementList, String>
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. /// Dumps the AST to stdout with format controlled by the given arguments.
/// ///
/// Returns a error of type String with a error message, /// Returns a error of type String with a error message,
@ -233,25 +226,41 @@ fn dump<S>(src: &S, args: &Opt, context: &mut Context<'_>) -> Result<(), String>
where where
S: AsRef<[u8]> + ?Sized, S: AsRef<[u8]> + ?Sized,
{ {
if let Some(ref arg) = args.dump_ast { if let Some(arg) = args.dump_ast {
let mut ast = parse_tokens(src, context)?; 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 { if args.optimize {
context.optimize_statement_list(&mut ast); context.optimize_statement_list(script.statements_mut());
} }
match arg { match arg {
Some(DumpFormat::Json) => println!( DumpFormat::Json => serde_json::to_string(&script)
"{}", .expect("could not convert AST to a JSON string"),
serde_json::to_string(&ast).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"),
Some(DumpFormat::JsonPretty) => println!( DumpFormat::Debug => format!("{script:#?}"),
"{}",
serde_json::to_string_pretty(&ast)
.expect("could not convert AST to a pretty JSON string")
),
Some(DumpFormat::Debug) | None => println!("{ast:#?}"),
} }
};
println!("{dump}");
} }
Ok(()) Ok(())
@ -263,8 +272,8 @@ fn generate_flowgraph(
format: FlowgraphFormat, format: FlowgraphFormat,
direction: Option<FlowgraphDirection>, direction: Option<FlowgraphDirection>,
) -> JsResult<String> { ) -> JsResult<String> {
let ast = context.parse_script(Source::from_bytes(src))?; let script = Script::parse(Source::from_bytes(src), None, context)?;
let code = context.compile_script(&ast)?; let code = script.codeblock(context)?;
let direction = match direction { let direction = match direction {
Some(FlowgraphDirection::TopToBottom) | None => Direction::TopToBottom, Some(FlowgraphDirection::TopToBottom) | None => Direction::TopToBottom,
@ -327,11 +336,25 @@ fn evaluate_files(
Ok(PromiseState::Fulfilled(_)) => {} Ok(PromiseState::Fulfilled(_)) => {}
Ok(PromiseState::Rejected(err)) => { Ok(PromiseState::Rejected(err)) => {
eprintln!("Uncaught {}", err.display()); 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 { } else {
match context.eval_script(Source::from_bytes(&buffer)) { match context.eval(Source::from_bytes(&buffer)) {
Ok(v) => println!("{}", v.display()), Ok(v) => println!("{}", v.display()),
Err(v) => eprintln!("Uncaught {v}"), Err(v) => eprintln!("Uncaught {v}"),
} }
@ -425,7 +448,7 @@ fn main() -> Result<(), io::Error> {
Err(v) => eprintln!("Uncaught {v}"), Err(v) => eprintln!("Uncaught {v}"),
} }
} else { } else {
match context.eval_script(Source::from_bytes(line.trim_end())) { match context.eval(Source::from_bytes(line.trim_end())) {
Ok(v) => { Ok(v) => {
println!("{}", v.display()); println!("{}", v.display());
} }

32
boa_engine/benches/full.rs

@ -2,7 +2,7 @@
use boa_engine::{ use boa_engine::{
context::DefaultHooks, object::shape::RootShape, optimizer::OptimizerOptions, realm::Realm, context::DefaultHooks, object::shape::RootShape, optimizer::OptimizerOptions, realm::Realm,
Context, Source, script::Script, Context, Source,
}; };
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use std::hint::black_box; use std::hint::black_box;
@ -33,7 +33,13 @@ macro_rules! full_benchmarks {
context.set_optimizer_options(OptimizerOptions::empty()); context.set_optimizer_options(OptimizerOptions::empty());
c.bench_function(concat!($id, " (Parser)"), move |b| { 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")); static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default(); let context = &mut Context::default();
// Disable optimizations // Disable optimizations
context.set_optimizer_options(OptimizerOptions::empty()); 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| { c.bench_function(concat!($id, " (Compiler)"), move |b| {
b.iter(|| { 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")); static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default(); let context = &mut Context::default();
// Disable optimizations // Disable optimizations
context.set_optimizer_options(OptimizerOptions::empty()); context.set_optimizer_options(OptimizerOptions::empty());
let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed"); let script = Script::parse(
let code_block = context.compile_script(&statement_list).unwrap(); black_box(Source::from_bytes(CODE)),
None,
context,
).unwrap();
script.codeblock(context).unwrap();
c.bench_function(concat!($id, " (Execution)"), move |b| { c.bench_function(concat!($id, " (Execution)"), move |b| {
b.iter(|| { b.iter(|| {
context.execute(black_box(code_block.clone())).unwrap() script.evaluate(context).unwrap();
}) })
}); });
} }

19
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 //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
use crate::{ use crate::{
builtins::BuiltInObject, bytecompiler::ByteCompiler, context::intrinsics::Intrinsics, builtins::BuiltInObject,
environments::Environment, error::JsNativeError, object::JsObject, realm::Realm, vm::Opcode, bytecompiler::ByteCompiler,
context::intrinsics::Intrinsics,
environments::Environment,
error::JsNativeError,
object::JsObject,
realm::Realm,
vm::{CallFrame, Opcode},
Context, JsArgs, JsResult, JsString, JsValue, Context, JsArgs, JsResult, JsString, JsValue,
}; };
use boa_ast::operations::{contains, contains_arguments, ContainsSymbol}; 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); let push_env = compiler.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
compiler.eval_declaration_instantiation(&body, strict)?; 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(); let env_info = compiler.pop_compile_environment();
compiler.patch_jump_with_target(push_env.0, env_info.num_bindings); 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.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()
} }
} }

40
boa_engine/src/builtins/function/mod.rs

@ -26,16 +26,15 @@ use crate::{
string::utf16, string::utf16,
symbol::JsSymbol, symbol::JsSymbol,
value::IntegerOrInfinity, value::IntegerOrInfinity,
vm::CodeBlock, vm::{ActiveRunnable, CodeBlock},
Context, JsArgs, JsResult, JsString, JsValue, Context, JsArgs, JsResult, JsString, JsValue,
}; };
use boa_ast::{ use boa_ast::{
function::FormalParameterList, function::{FormalParameterList, FunctionBody},
operations::{ operations::{
all_private_identifiers_valid, bound_names, contains, lexically_declared_names, all_private_identifiers_valid, bound_names, contains, lexically_declared_names,
ContainsSymbol, ContainsSymbol,
}, },
StatementList,
}; };
use boa_gc::{self, custom_trace, Finalize, Gc, Trace}; use boa_gc::{self, custom_trace, Finalize, Gc, Trace};
use boa_interner::Sym; use boa_interner::Sym;
@ -177,6 +176,9 @@ pub(crate) enum FunctionKind {
/// The class object that this function is associated with. /// The class object that this function is associated with.
class_object: Option<JsObject>, class_object: Option<JsObject>,
/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
}, },
/// A bytecode async function. /// A bytecode async function.
@ -192,6 +194,9 @@ pub(crate) enum FunctionKind {
/// The class object that this function is associated with. /// The class object that this function is associated with.
class_object: Option<JsObject>, class_object: Option<JsObject>,
/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
}, },
/// A bytecode generator function. /// A bytecode generator function.
@ -207,6 +212,9 @@ pub(crate) enum FunctionKind {
/// The class object that this function is associated with. /// The class object that this function is associated with.
class_object: Option<JsObject>, class_object: Option<JsObject>,
/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
}, },
/// A bytecode async generator function. /// A bytecode async generator function.
@ -222,6 +230,9 @@ pub(crate) enum FunctionKind {
/// The class object that this function is associated with. /// The class object that this function is associated with.
class_object: Option<JsObject>, class_object: Option<JsObject>,
/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
}, },
} }
@ -256,7 +267,16 @@ unsafe impl Trace for FunctionKind {
custom_trace! {this, { custom_trace! {this, {
match this { match this {
Self::Native { function, .. } => {mark(function)} 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(code);
mark(environments); mark(environments);
mark(home_object); mark(home_object);
@ -267,14 +287,16 @@ unsafe impl Trace for FunctionKind {
mark(elem); mark(elem);
} }
mark(class_object); mark(class_object);
mark(script_or_module);
} }
Self::Async { 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} | Self::Generator { code, environments, home_object, class_object, script_or_module}
| Self::AsyncGenerator { code, environments, home_object, class_object} => { | Self::AsyncGenerator { code, environments, home_object, class_object, script_or_module} => {
mark(code); mark(code);
mark(environments); mark(environments);
mark(home_object); mark(home_object);
mark(class_object); mark(class_object);
mark(script_or_module);
} }
} }
}} }}
@ -753,7 +775,7 @@ impl BuiltInFunctionObject {
.generator(true) .generator(true)
.compile( .compile(
&FormalParameterList::default(), &FormalParameterList::default(),
&StatementList::default(), &FunctionBody::default(),
context.realm().environment().compile_env(), context.realm().environment().compile_env(),
context, context,
); );
@ -771,7 +793,7 @@ impl BuiltInFunctionObject {
} else { } else {
let code = FunctionCompiler::new().name(Sym::ANONYMOUS).compile( let code = FunctionCompiler::new().name(Sym::ANONYMOUS).compile(
&FormalParameterList::default(), &FormalParameterList::default(),
&StatementList::default(), &FunctionBody::default(),
context.realm().environment().compile_env(), context.realm().environment().compile_env(),
context, context,
); );

15
boa_engine/src/builtins/json/mod.rs

@ -29,6 +29,7 @@ use crate::{
string::{utf16, CodePoint}, string::{utf16, CodePoint},
symbol::JsSymbol, symbol::JsSymbol,
value::IntegerOrInfinity, value::IntegerOrInfinity,
vm::CallFrame,
Context, JsArgs, JsResult, JsString, JsValue, Context, JsArgs, JsResult, JsString, JsValue,
}; };
use boa_gc::Gc; 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. // 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)); let mut parser = Parser::new(Source::from_bytes(&script_string));
parser.set_json_parse(); 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 code_block = {
let mut compiler = ByteCompiler::new( let mut compiler = ByteCompiler::new(
Sym::MAIN, Sym::MAIN,
statement_list.strict(), script.strict(),
true, true,
context.realm().environment().compile_env(), context.realm().environment().compile_env(),
context, context,
); );
compiler.compile_statement_list(&statement_list, true, false); compiler.compile_statement_list(script.statements(), true, false);
Gc::new(compiler.finish()) 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 // 11. If IsCallable(reviver) is true, then
if let Some(obj) = args.get_or_undefined(1).as_callable() { if let Some(obj) = args.get_or_undefined(1).as_callable() {

4
boa_engine/src/builtins/promise/mod.rs

@ -2333,7 +2333,7 @@ fn new_promise_reaction_job(
}; };
// 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }. // 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }.
NativeJob::with_realm(job, realm) NativeJob::with_realm(job, realm, context)
} }
/// More information: /// More information:
@ -2387,5 +2387,5 @@ fn new_promise_resolve_thenable_job(
}; };
// 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }. // 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }.
NativeJob::with_realm(job, realm) NativeJob::with_realm(job, realm, context)
} }

8
boa_engine/src/bytecompiler/class.rs

@ -47,7 +47,7 @@ impl ByteCompiler<'_, '_> {
false, 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(); let env_info = compiler.pop_compile_environment();
@ -370,7 +370,7 @@ impl ByteCompiler<'_, '_> {
let index = self.get_or_insert_private_name(*name); let index = self.get_or_insert_private_name(*name);
self.emit(Opcode::DefinePrivateField, &[index]); self.emit(Opcode::DefinePrivateField, &[index]);
} }
ClassElement::StaticBlock(statement_list) => { ClassElement::StaticBlock(body) => {
self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup);
let mut compiler = ByteCompiler::new( let mut compiler = ByteCompiler::new(
Sym::EMPTY_STRING, Sym::EMPTY_STRING,
@ -384,14 +384,14 @@ impl ByteCompiler<'_, '_> {
compiler.push_compile_environment(true); compiler.push_compile_environment(true);
compiler.function_declaration_instantiation( compiler.function_declaration_instantiation(
statement_list, body,
&FormalParameterList::default(), &FormalParameterList::default(),
false, false,
true, true,
false, false,
); );
compiler.compile_statement_list(statement_list, false, false); compiler.compile_statement_list(body.statements(), false, false);
let env_info = compiler.pop_compile_environment(); let env_info = compiler.pop_compile_environment();
compiler.pop_compile_environment(); compiler.pop_compile_environment();
compiler.num_bindings = env_info.num_bindings; compiler.num_bindings = env_info.num_bindings;

60
boa_engine/src/bytecompiler/declarations.rs

@ -5,14 +5,14 @@ use crate::{
}; };
use boa_ast::{ use boa_ast::{
declaration::{Binding, LexicalDeclaration, VariableList}, declaration::{Binding, LexicalDeclaration, VariableList},
function::FormalParameterList, function::{FormalParameterList, FunctionBody},
operations::{ operations::{
all_private_identifiers_valid, bound_names, lexically_scoped_declarations, all_private_identifiers_valid, bound_names, lexically_declared_names,
top_level_lexically_declared_names, top_level_var_declared_names, lexically_scoped_declarations, var_declared_names, var_scoped_declarations,
top_level_var_scoped_declarations, VarScopedDeclaration, LexicallyScopedDeclaration, VarScopedDeclaration,
}, },
visitor::NodeRef, visitor::NodeRef,
Declaration, StatementList, StatementListItem, Declaration, Script, StatementListItem,
}; };
use boa_interner::Sym; use boa_interner::Sym;
@ -26,15 +26,12 @@ impl ByteCompiler<'_, '_> {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation /// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
pub(crate) fn global_declaration_instantiation( pub(crate) fn global_declaration_instantiation(&mut self, script: &Script) -> JsResult<()> {
&mut self,
script: &StatementList,
) -> JsResult<()> {
// 1. Let lexNames be the LexicallyDeclaredNames of script. // 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. // 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 // 3. For each element name of lexNames, do
for name in lex_names { for name in lex_names {
@ -71,7 +68,7 @@ impl ByteCompiler<'_, '_> {
// 5. Let varDeclarations be the VarScopedDeclarations of script. // 5. Let varDeclarations be the VarScopedDeclarations of script.
// Note: VarScopedDeclarations for a Script node is TopLevelVarScopedDeclarations. // 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. // 6. Let functionsToInitialize be a new empty List.
let mut functions_to_initialize = Vec::new(); let mut functions_to_initialize = Vec::new();
@ -162,7 +159,7 @@ impl ByteCompiler<'_, '_> {
// b. If strict is false, then // b. If strict is false, then
#[cfg(feature = "annex-b")] #[cfg(feature = "annex-b")]
if !script.strict() { 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. // 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, // 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. // 13. Let lexDeclarations be the LexicallyScopedDeclarations of script.
// 14. Let privateEnv be null. // 14. Let privateEnv be null.
// 15. For each element d of lexDeclarations, do // 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. // a. NOTE: Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do // b. For each element dn of the BoundNames of d, do
// i. If IsConstantDeclaration of d is true, then // i. If IsConstantDeclaration of d is true, then
@ -315,7 +312,8 @@ impl ByteCompiler<'_, '_> {
// 3. For each element d of declarations, do // 3. For each element d of declarations, do
for d in &declarations { for d in &declarations {
// i. If IsConstantDeclaration of d is true, then // 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 // a. For each element dn of the BoundNames of d, do
for dn in bound_names::<'_, VariableList>(d) { for dn in bound_names::<'_, VariableList>(d) {
// 1. Perform ! env.CreateImmutableBinding(dn, true). // 1. Perform ! env.CreateImmutableBinding(dn, true).
@ -325,7 +323,7 @@ impl ByteCompiler<'_, '_> {
// ii. Else, // ii. Else,
else { else {
// a. For each element dn of the BoundNames of d, do // 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"))] #[cfg(not(feature = "annex-b"))]
// 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6. // 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6.
env.create_mutable_binding(dn, false); env.create_mutable_binding(dn, false);
@ -352,16 +350,16 @@ impl ByteCompiler<'_, '_> {
// TODO: Support B.3.2.6. // TODO: Support B.3.2.6.
for d in &declarations { for d in &declarations {
match d { match d {
Declaration::Function(function) => { LexicallyScopedDeclaration::Function(function) => {
self.function(function.into(), NodeKind::Declaration, false); self.function(function.into(), NodeKind::Declaration, false);
} }
Declaration::Generator(function) => { LexicallyScopedDeclaration::Generator(function) => {
self.function(function.into(), NodeKind::Declaration, false); self.function(function.into(), NodeKind::Declaration, false);
} }
Declaration::AsyncFunction(function) => { LexicallyScopedDeclaration::AsyncFunction(function) => {
self.function(function.into(), NodeKind::Declaration, false); self.function(function.into(), NodeKind::Declaration, false);
} }
Declaration::AsyncGenerator(function) => { LexicallyScopedDeclaration::AsyncGenerator(function) => {
self.function(function.into(), NodeKind::Declaration, false); self.function(function.into(), NodeKind::Declaration, false);
} }
_ => {} _ => {}
@ -379,7 +377,7 @@ impl ByteCompiler<'_, '_> {
/// [spec]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation /// [spec]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
pub(crate) fn eval_declaration_instantiation( pub(crate) fn eval_declaration_instantiation(
&mut self, &mut self,
body: &StatementList, body: &Script,
strict: bool, strict: bool,
) -> JsResult<()> { ) -> JsResult<()> {
let var_environment_is_global = self let var_environment_is_global = self
@ -390,12 +388,12 @@ impl ByteCompiler<'_, '_> {
&& !strict; && !strict;
// 2. Let varDeclarations be the VarScopedDeclarations of body. // 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 // 3. If strict is false, then
if !strict { if !strict {
// 1. Let varNames be the VarDeclaredNames of body. // 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 // a. If varEnv is a Global Environment Record, then
// i. For each element name of varNames, do // i. For each element name of varNames, do
@ -494,7 +492,7 @@ impl ByteCompiler<'_, '_> {
// 11. If strict is false, then // 11. If strict is false, then
#[cfg(feature = "annex-b")] #[cfg(feature = "annex-b")]
if !strict { 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. // a. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames.
// b. For each FunctionDeclaration f that is directly contained in the StatementList // 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. // 15. Let lexDeclarations be the LexicallyScopedDeclarations of body.
// 16. For each element d of lexDeclarations, do // 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. // a. NOTE: Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do // b. For each element dn of the BoundNames of d, do
// i. If IsConstantDeclaration of d is true, then // i. If IsConstantDeclaration of d is true, then
@ -774,7 +772,7 @@ impl ByteCompiler<'_, '_> {
/// [spec]: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation /// [spec]: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
pub(crate) fn function_declaration_instantiation( pub(crate) fn function_declaration_instantiation(
&mut self, &mut self,
code: &StatementList, body: &FunctionBody,
formals: &FormalParameterList, formals: &FormalParameterList,
arrow: bool, arrow: bool,
strict: bool, strict: bool,
@ -801,13 +799,13 @@ impl ByteCompiler<'_, '_> {
let has_parameter_expressions = formals.has_expressions(); let has_parameter_expressions = formals.has_expressions();
// 9. Let varNames be the VarDeclaredNames of code. // 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. // 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. // 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. // 12. Let functionNames be a new empty List.
let mut function_names = Vec::new(); let mut function_names = Vec::new();
@ -1064,7 +1062,7 @@ impl ByteCompiler<'_, '_> {
if !strict { if !strict {
// a. For each FunctionDeclaration f that is directly contained in the StatementList // a. For each FunctionDeclaration f that is directly contained in the StatementList
// of a Block, CaseClause, or DefaultClause, do // 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. // i. Let F be StringValue of the BindingIdentifier of f.
// ii. If replacing the FunctionDeclaration f with a VariableStatement that has F // ii. If replacing the FunctionDeclaration f with a VariableStatement that has F
// as a BindingIdentifier would not produce any Early Errors // as a BindingIdentifier would not produce any Early Errors
@ -1120,7 +1118,7 @@ impl ByteCompiler<'_, '_> {
// 1. Perform ! lexEnv.CreateImmutableBinding(dn, true). // 1. Perform ! lexEnv.CreateImmutableBinding(dn, true).
// ii. Else, // ii. Else,
// 1. Perform ! lexEnv.CreateMutableBinding(dn, false). // 1. Perform ! lexEnv.CreateMutableBinding(dn, false).
for statement in code.statements() { for statement in &**body.statements() {
if let StatementListItem::Declaration(declaration) = statement { if let StatementListItem::Declaration(declaration) = statement {
match declaration { match declaration {
Declaration::Class(class) => { Declaration::Class(class) => {

7
boa_engine/src/bytecompiler/expression/mod.rs

@ -320,6 +320,13 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::Pop); 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 => { Expression::NewTarget => {
if use_expr { if use_expr {
self.emit_opcode(Opcode::PushNewTarget); self.emit_opcode(Opcode::PushNewTarget);

6
boa_engine/src/bytecompiler/function.rs

@ -5,7 +5,7 @@ use crate::{
vm::{CodeBlock, Opcode}, vm::{CodeBlock, Opcode},
Context, Context,
}; };
use boa_ast::{function::FormalParameterList, StatementList}; use boa_ast::function::{FormalParameterList, FunctionBody};
use boa_gc::{Gc, GcRefCell}; use boa_gc::{Gc, GcRefCell};
use boa_interner::Sym; use boa_interner::Sym;
@ -87,7 +87,7 @@ impl FunctionCompiler {
pub(crate) fn compile( pub(crate) fn compile(
mut self, mut self,
parameters: &FormalParameterList, parameters: &FormalParameterList,
body: &StatementList, body: &FunctionBody,
outer_env: Gc<GcRefCell<CompileTimeEnvironment>>, outer_env: Gc<GcRefCell<CompileTimeEnvironment>>,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> Gc<CodeBlock> { ) -> Gc<CodeBlock> {
@ -125,7 +125,7 @@ impl FunctionCompiler {
self.generator, self.generator,
); );
compiler.compile_statement_list(body, false, false); compiler.compile_statement_list(body.statements(), false, false);
if let Some(env_labels) = env_labels { if let Some(env_labels) = env_labels {
let env_info = compiler.pop_compile_environment(); let env_info = compiler.pop_compile_environment();

4
boa_engine/src/bytecompiler/mod.rs

@ -27,7 +27,7 @@ use boa_ast::{
}, },
function::{ function::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class,
FormalParameterList, Function, Generator, PrivateName, FormalParameterList, Function, FunctionBody, Generator, PrivateName,
}, },
pattern::Pattern, pattern::Pattern,
Declaration, Expression, Statement, StatementList, StatementListItem, Declaration, Expression, Statement, StatementList, StatementListItem,
@ -64,7 +64,7 @@ pub(crate) struct FunctionSpec<'a> {
kind: FunctionKind, kind: FunctionKind,
pub(crate) name: Option<Identifier>, pub(crate) name: Option<Identifier>,
parameters: &'a FormalParameterList, parameters: &'a FormalParameterList,
body: &'a StatementList, body: &'a FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
} }

2
boa_engine/src/context/hooks.rs

@ -41,7 +41,7 @@ use super::intrinsics::Intrinsics;
/// } /// }
/// let hooks: &dyn HostHooks = &Hooks; // Can have additional state. /// let hooks: &dyn HostHooks = &Hooks; // Can have additional state.
/// let context = &mut ContextBuilder::new().host_hooks(hooks).build().unwrap(); /// 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"); /// assert_eq!(result.unwrap_err().to_string(), "TypeError: eval calls not available");
/// ``` /// ```
/// ///

82
boa_engine/src/context/mod.rs

@ -18,7 +18,6 @@ use std::{io::Read, path::Path, rc::Rc};
use crate::{ use crate::{
builtins, builtins,
bytecompiler::ByteCompiler,
class::{Class, ClassBuilder}, class::{Class, ClassBuilder},
job::{JobQueue, NativeJob, SimpleJobQueue}, job::{JobQueue, NativeJob, SimpleJobQueue},
module::{ModuleLoader, SimpleModuleLoader}, module::{ModuleLoader, SimpleModuleLoader},
@ -27,13 +26,12 @@ use crate::{
optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics}, optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm, realm::Realm,
vm::{CallFrame, CodeBlock, Vm}, script::Script,
vm::{CallFrame, Vm},
JsResult, JsValue, Source, JsResult, JsValue, Source,
}; };
use boa_ast::{expression::Identifier, StatementList}; use boa_ast::{expression::Identifier, StatementList};
use boa_gc::Gc; use boa_interner::Interner;
use boa_interner::{Interner, Sym};
use boa_parser::Parser;
use boa_profiler::Profiler; use boa_profiler::Profiler;
use crate::vm::RuntimeLimits; use crate::vm::RuntimeLimits;
@ -68,7 +66,7 @@ use crate::vm::RuntimeLimits;
/// let mut context = Context::default(); /// let mut context = Context::default();
/// ///
/// // Populate the script definition to the context. /// // 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. /// // Create an object that can be used in eval calls.
/// let arg = ObjectInitializer::new(&mut context) /// let arg = ObjectInitializer::new(&mut context)
@ -76,7 +74,7 @@ use crate::vm::RuntimeLimits;
/// .build(); /// .build();
/// context.register_global_property("arg", arg, Attribute::all()); /// 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)) /// assert_eq!(value.as_number(), Some(12.0))
/// ``` /// ```
@ -153,7 +151,7 @@ impl<'host> Context<'host> {
ContextBuilder::default() 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. /// bytecode into a value.
/// ///
/// # Examples /// # Examples
@ -162,7 +160,7 @@ impl<'host> Context<'host> {
/// let mut context = Context::default(); /// let mut context = Context::default();
/// ///
/// let source = Source::from_bytes("1 + 3"); /// 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!(value.is_number());
/// assert_eq!(value.as_number().unwrap(), 4.0); /// 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`] /// 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. /// on the context or [`JobQueue::run_jobs`] on the provided queue to run them.
#[allow(clippy::unit_arg, clippy::drop_copy)] #[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval_script<R: Read>(&mut self, src: Source<'_, R>) -> JsResult<JsValue> { pub fn eval<R: Read>(&mut self, src: Source<'_, R>) -> JsResult<JsValue> {
let main_timer = Profiler::global().start_event("Script evaluation", "Main"); let main_timer = Profiler::global().start_event("Script evaluation", "Main");
let script = self.parse_script(src)?; let result = Script::parse(src, None, self)?.evaluate(self);
let code_block = self.compile_script(&script)?;
let result = self.execute(code_block);
// The main_timer needs to be dropped before the Profiler is. // The main_timer needs to be dropped before the Profiler is.
drop(main_timer); drop(main_timer);
@ -194,61 +190,6 @@ impl<'host> Context<'host> {
optimizer.apply(statement_list) optimizer.apply(statement_list)
} }
/// Parse the given source script.
pub fn parse_script<R: Read>(&mut self, src: Source<'_, R>) -> JsResult<StatementList> {
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<Gc<CodeBlock>> {
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<CodeBlock>`, 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<CodeBlock>` 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<CodeBlock>) -> JsResult<JsValue> {
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. /// Register a global property.
/// ///
/// It will return an error if the property is already defined. /// It will return an error if the property is already defined.
@ -737,6 +678,11 @@ impl Context<'_> {
// 6. Return true. // 6. Return true.
Ok(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> { impl<'host> Context<'host> {

39
boa_engine/src/job.rs

@ -22,6 +22,7 @@ use std::{any::Any, cell::RefCell, collections::VecDeque, fmt::Debug, future::Fu
use crate::{ use crate::{
object::{JsFunction, NativeObject}, object::{JsFunction, NativeObject},
realm::Realm, realm::Realm,
vm::ActiveRunnable,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -68,6 +69,7 @@ pub struct NativeJob {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
f: Box<dyn FnOnce(&mut Context<'_>) -> JsResult<JsValue>>, f: Box<dyn FnOnce(&mut Context<'_>) -> JsResult<JsValue>>,
realm: Option<Realm>, realm: Option<Realm>,
active_runnable: Option<ActiveRunnable>,
} }
impl Debug for NativeJob { impl Debug for NativeJob {
@ -85,17 +87,19 @@ impl NativeJob {
Self { Self {
f: Box::new(f), f: Box::new(f),
realm: None, realm: None,
active_runnable: None,
} }
} }
/// Creates a new `NativeJob` from a closure and an execution realm. /// Creates a new `NativeJob` from a closure and an execution realm.
pub fn with_realm<F>(f: F, realm: Realm) -> Self pub fn with_realm<F>(f: F, realm: Realm, context: &mut Context<'_>) -> Self
where where
F: FnOnce(&mut Context<'_>) -> JsResult<JsValue> + 'static, F: FnOnce(&mut Context<'_>) -> JsResult<JsValue> + 'static,
{ {
Self { Self {
f: Box::new(f), f: Box::new(f),
realm: Some(realm), 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 /// 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. /// context to the realm's before calling the inner closure, and resets it after execution.
pub fn call(self, context: &mut Context<'_>) -> JsResult<JsValue> { pub fn call(mut self, context: &mut Context<'_>) -> JsResult<JsValue> {
// 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 { if let Some(realm) = self.realm {
let old_realm = context.enter_realm(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); let result = (self.f)(context);
context.enter_realm(old_realm); context.enter_realm(old_realm);
std::mem::swap(&mut context.vm.active_runnable, &mut self.active_runnable);
result result
} else { } else {
(self.f)(context) (self.f)(context)
@ -173,8 +190,22 @@ impl JobCallback {
pub trait JobQueue { pub trait JobQueue {
/// [`HostEnqueuePromiseJob ( job, realm )`][spec]. /// [`HostEnqueuePromiseJob ( job, realm )`][spec].
/// ///
/// Enqueues a [`NativeJob`] on the job queue. Note that host-defined [Jobs] need to satisfy /// Enqueues a [`NativeJob`] on the job queue.
/// a set of requirements for them to be spec-compliant. ///
/// # 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 /// [spec]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob
/// [Jobs]: https://tc39.es/ecma262/#sec-jobs /// [Jobs]: https://tc39.es/ecma262/#sec-jobs

7
boa_engine/src/lib.rs

@ -22,7 +22,7 @@
//! let mut context = Context::default(); //! let mut context = Context::default();
//! //!
//! // Parse the source code //! // Parse the source code
//! match context.eval_script(Source::from_bytes(js_code)) { //! match context.eval(Source::from_bytes(js_code)) {
//! Ok(res) => { //! Ok(res) => {
//! println!( //! println!(
//! "{}", //! "{}",
@ -137,6 +137,7 @@ pub mod object;
pub mod optimizer; pub mod optimizer;
pub mod property; pub mod property;
pub mod realm; pub mod realm;
pub mod script;
pub mod string; pub mod string;
pub mod symbol; pub mod symbol;
// pub(crate) mod tagged; // pub(crate) mod tagged;
@ -153,6 +154,7 @@ pub mod prelude {
module::Module, module::Module,
native_function::NativeFunction, native_function::NativeFunction,
object::JsObject, object::JsObject,
script::Script,
Context, JsBigInt, JsResult, JsString, JsValue, Context, JsBigInt, JsResult, JsString, JsValue,
}; };
pub use boa_parser::Source; pub use boa_parser::Source;
@ -169,6 +171,7 @@ pub use crate::{
module::Module, module::Module,
native_function::NativeFunction, native_function::NativeFunction,
object::JsObject, object::JsObject,
script::Script,
string::JsString, string::JsString,
symbol::JsSymbol, symbol::JsSymbol,
value::JsValue, value::JsValue,
@ -336,7 +339,7 @@ fn run_test_actions(actions: impl IntoIterator<Item = TestAction>) {
fn run_test_actions_with(actions: impl IntoIterator<Item = TestAction>, context: &mut Context<'_>) { fn run_test_actions_with(actions: impl IntoIterator<Item = TestAction>, context: &mut Context<'_>) {
#[track_caller] #[track_caller]
fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult<JsValue> { fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult<JsValue> {
context.eval_script(Source::from_bytes(source)) context.eval(Source::from_bytes(source))
} }
#[track_caller] #[track_caller]

19
boa_engine/src/module/mod.rs

@ -41,6 +41,8 @@ use boa_parser::{Parser, Source};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use crate::object::FunctionObjectBuilder; use crate::object::FunctionObjectBuilder;
use crate::script::Script;
use crate::vm::ActiveRunnable;
use crate::{ use crate::{
builtins::promise::{PromiseCapability, PromiseState}, builtins::promise::{PromiseCapability, PromiseState},
environments::DeclarativeEnvironment, environments::DeclarativeEnvironment,
@ -51,12 +53,23 @@ use crate::{
use crate::{js_string, JsNativeError, NativeFunction}; use crate::{js_string, JsNativeError, NativeFunction};
/// The referrer from which a load request of a module originates. /// The referrer from which a load request of a module originates.
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Referrer { pub enum Referrer {
/// A [**Source Text Module Record**](https://tc39.es/ecma262/#sec-source-text-module-records). /// A [**Source Text Module Record**](https://tc39.es/ecma262/#sec-source-text-module-records).
Module(Module), Module(Module),
/// A [**Realm**](https://tc39.es/ecma262/#sec-code-realms). /// 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<ActiveRunnable> 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. /// Module loading related host hooks.
@ -182,7 +195,7 @@ impl ModuleLoader for SimpleModuleLoader {
.with_cause(JsError::from_opaque(js_string!(err.to_string()).into())) .with_cause(JsError::from_opaque(js_string!(err.to_string()).into()))
})?; })?;
let module = Module::parse(source, None, context).map_err(|err| { 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_message(format!("could not parse module `{}`", short_path.display()))
.with_cause(err) .with_cause(err)
})?; })?;

113
boa_engine/src/module/source.rs

@ -7,9 +7,8 @@ use boa_ast::{
}, },
operations::{ operations::{
bound_names, contains, lexically_scoped_declarations, var_scoped_declarations, 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_gc::{custom_trace, empty_trace, Finalize, Gc, GcRefCell, Trace};
use boa_interner::Sym; use boa_interner::Sym;
@ -22,7 +21,7 @@ use crate::{
module::ModuleKind, module::ModuleKind,
object::{FunctionObjectBuilder, JsPromise, RecursionLimiter}, object::{FunctionObjectBuilder, JsPromise, RecursionLimiter},
realm::Realm, realm::Realm,
vm::{CallFrame, CodeBlock, CompletionRecord, Opcode}, vm::{ActiveRunnable, CallFrame, CodeBlock, CompletionRecord, Opcode},
Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsString, JsValue, NativeFunction, Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsString, JsValue, NativeFunction,
}; };
@ -275,7 +274,7 @@ struct Inner {
struct ModuleCode { struct ModuleCode {
has_tla: bool, has_tla: bool,
requested_modules: FxHashSet<Sym>, requested_modules: FxHashSet<Sym>,
node: ModuleItemList, source: boa_ast::Module,
import_entries: Vec<ImportEntry>, import_entries: Vec<ImportEntry>,
local_export_entries: Vec<LocalExportEntry>, local_export_entries: Vec<LocalExportEntry>,
indirect_export_entries: Vec<IndirectExportEntry>, indirect_export_entries: Vec<IndirectExportEntry>,
@ -297,16 +296,16 @@ impl SourceTextModule {
.expect("parent module must be initialized") .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]. /// Contains part of the abstract operation [`ParseModule`][parse].
/// ///
/// [parse]: https://tc39.es/ecma262/#sec-parsemodule /// [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. // 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. // 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). // 5. Let importedBoundNames be ImportedLocalNames(importEntries).
// Can be ignored because this is just a simple `Iter::map` // Can be ignored because this is just a simple `Iter::map`
@ -319,7 +318,7 @@ impl SourceTextModule {
let mut star_export_entries = Vec::new(); let mut star_export_entries = Vec::new();
// 10. For each ExportEntry Record ee of exportEntries, do // 10. For each ExportEntry Record ee of exportEntries, do
for ee in code.export_entries() { for ee in code.items().export_entries() {
match ee { match ee {
// a. If ee.[[ModuleRequest]] is null, then // a. If ee.[[ModuleRequest]] is null, then
ExportEntry::Ordinary(entry) => { ExportEntry::Ordinary(entry) => {
@ -389,7 +388,7 @@ impl SourceTextModule {
async_parent_modules: GcRefCell::default(), async_parent_modules: GcRefCell::default(),
import_meta: GcRefCell::default(), import_meta: GcRefCell::default(),
code: ModuleCode { code: ModuleCode {
node: code, source: code,
requested_modules, requested_modules,
has_tla, has_tla,
import_entries, import_entries,
@ -615,8 +614,8 @@ impl SourceTextModule {
// iii. Else, // iii. Else,
// 1. Assert: module imports a specific binding for this export. // 1. Assert: module imports a specific binding for this export.
// 2. Return importedModule.ResolveExport(e.[[ImportName]], resolveSet). // 2. Return importedModule.ResolveExport(e.[[ImportName]], resolveSet).
ReExportImportName::Name(_) => { ReExportImportName::Name(name) => {
imported_module.resolve_export(export_name, resolve_set) imported_module.resolve_export(name, resolve_set)
} }
}; };
} }
@ -1470,7 +1469,7 @@ impl SourceTextModule {
// 18. Let code be module.[[ECMAScriptCode]]. // 18. Let code be module.[[ECMAScriptCode]].
// 19. Let varDeclarations be the VarScopedDeclarations of code. // 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. // 20. Let declaredVarNames be a new empty List.
let mut declared_var_names = Vec::new(); let mut declared_var_names = Vec::new();
// 21. For each element d of varDeclarations, do // 21. For each element d of varDeclarations, do
@ -1494,80 +1493,60 @@ impl SourceTextModule {
// 22. Let lexDeclarations be the LexicallyScopedDeclarations of code. // 22. Let lexDeclarations be the LexicallyScopedDeclarations of code.
// 23. Let privateEnv be null. // 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 // 24. For each element d of lexDeclarations, do
for declaration in lex_declarations { for declaration in &lex_declarations {
match &declaration {
// i. If IsConstantDeclaration of d is true, then // i. If IsConstantDeclaration of d is true, then
Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { if let LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(
decl,
)) = declaration
{
// a. For each element dn of the BoundNames of d, do // a. For each element dn of the BoundNames of d, do
for name in bound_names(declaration) { for name in bound_names(decl) {
// 1. Perform ! env.CreateImmutableBinding(dn, true). // 1. Perform ! env.CreateImmutableBinding(dn, true).
compiler.create_immutable_binding(name, true); compiler.create_immutable_binding(name, true);
} }
} } else {
// ii. Else, // ii. Else,
Declaration::Lexical(LexicalDeclaration::Let(declaration)) => {
// a. For each element dn of the BoundNames of d, do // a. For each element dn of the BoundNames of d, do
for name in bound_names(declaration) { for name in declaration.bound_names() {
// 1. Perform ! env.CreateMutableBinding(dn, false). // 1. Perform ! env.CreateMutableBinding(dn, false).
compiler.create_mutable_binding(name, false); compiler.create_mutable_binding(name, false);
} }
} }
// iii. If d is either a FunctionDeclaration, a GeneratorDeclaration, an // iii. If d is either a FunctionDeclaration, a GeneratorDeclaration, an
// AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then // 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. // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
// 2. Perform ! env.InitializeBinding(dn, fo). // 2. Perform ! env.InitializeBinding(dn, fo).
for name in bound_names(function) { let spec = match declaration {
compiler.create_mutable_binding(name, false); LexicallyScopedDeclaration::Function(f) => f.into(),
} LexicallyScopedDeclaration::Generator(g) => g.into(),
compiler.function(function.into(), NodeKind::Declaration, false); LexicallyScopedDeclaration::AsyncFunction(af) => af.into(),
} LexicallyScopedDeclaration::AsyncGenerator(ag) => ag.into(),
Declaration::Class(class) => { LexicallyScopedDeclaration::Class(_)
// 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. | LexicallyScopedDeclaration::LexicalDeclaration(_)
// 2. Perform ! env.InitializeBinding(dn, fo). | LexicallyScopedDeclaration::AssignmentExpression(_) => continue,
for name in bound_names(class) { };
compiler.create_mutable_binding(name, false);
} 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()) Gc::new(compiler.finish())
}; };
// 8. Let moduleContext be a new ECMAScript code execution context. // 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); let mut envs = EnvironmentStack::new(global_env);
envs.push_module(module_compile_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]]. // 13. Set the VariableEnvironment of moduleContext to module.[[Environment]].
// 14. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. // 14. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
// 15. Set the PrivateEnvironment of moduleContext to null. // 15. Set the PrivateEnvironment of moduleContext to null.
@ -1623,6 +1602,7 @@ impl SourceTextModule {
std::mem::swap(&mut context.vm.environments, &mut envs); std::mem::swap(&mut context.vm.environments, &mut envs);
context.vm.stack = stack; context.vm.stack = stack;
context.vm.active_function = active_function; context.vm.active_function = active_function;
context.vm.active_runnable = active_runnable;
context.swap_realm(&mut realm); context.swap_realm(&mut realm);
debug_assert!(envs.current().as_declarative().is_some()); debug_assert!(envs.current().as_declarative().is_some());
@ -1674,6 +1654,11 @@ impl SourceTextModule {
callframe.promise_capability = capability; callframe.promise_capability = capability;
// 4. Set the ScriptOrModule of moduleContext to module. // 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. // 5. Assert: module has been linked and declarations in its module environment have been instantiated.
// 6. Set the VariableEnvironment of moduleContext to module.[[Environment]]. // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]].
// 7. Set the LexicalEnvironment 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); std::mem::swap(&mut context.vm.environments, &mut environments);
context.vm.stack = stack; context.vm.stack = stack;
context.vm.active_function = function; context.vm.active_function = function;
context.vm.active_runnable = active_runnable;
context.swap_realm(&mut realm); context.swap_realm(&mut realm);
context.vm.pop_frame(); context.vm.pop_frame();
@ -1712,6 +1698,11 @@ impl SourceTextModule {
Ok(()) Ok(())
} }
} }
/// Gets the loaded modules of this module.
pub(crate) fn loaded_modules(&self) -> &GcRefCell<FxHashMap<Sym, Module>> {
&self.inner.loaded_modules
}
} }
/// Abstract operation [`AsyncModuleExecutionFulfilled ( module )`][spec]. /// Abstract operation [`AsyncModuleExecutionFulfilled ( module )`][spec].

2
boa_engine/src/object/builtins/jspromise.rs

@ -229,7 +229,7 @@ impl JsPromise {
/// # fn main() -> Result<(), Box<dyn Error>> { /// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default(); /// 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 = promise.as_object().cloned().unwrap();
/// ///
/// let promise = JsPromise::from_object(promise)?; /// let promise = JsPromise::from_object(promise)?;

163
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<Inner>,
}
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<Option<Gc<CodeBlock>>>,
loaded_modules: GcRefCell<FxHashMap<JsString, Module>>,
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<FxHashMap<JsString, Module>> {
&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<R: Read>(
src: Source<'_, R>,
realm: Option<Realm>,
context: &mut Context<'_>,
) -> JsResult<Self> {
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<Gc<CodeBlock>> {
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<JsValue> {
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()
}
}

34
boa_engine/src/vm/code_block.rs

@ -511,6 +511,7 @@ impl CodeBlock {
| Opcode::IsObject | Opcode::IsObject
| Opcode::SetNameByLocator | Opcode::SetNameByLocator
| Opcode::PopPrivateEnvironment | Opcode::PopPrivateEnvironment
| Opcode::ImportCall
| Opcode::Nop => String::new(), | Opcode::Nop => String::new(),
} }
} }
@ -612,6 +613,8 @@ pub(crate) fn create_function_object(
let length: JsValue = code.length.into(); let length: JsValue = code.length.into();
let script_or_module = context.vm.active_runnable.clone();
let function = if r#async { let function = if r#async {
Function::new( Function::new(
FunctionKind::Async { FunctionKind::Async {
@ -619,6 +622,7 @@ pub(crate) fn create_function_object(
environments: context.vm.environments.clone(), environments: context.vm.environments.clone(),
home_object: None, home_object: None,
class_object: None, class_object: None,
script_or_module,
}, },
context.realm().clone(), context.realm().clone(),
) )
@ -632,6 +636,7 @@ pub(crate) fn create_function_object(
fields: ThinVec::new(), fields: ThinVec::new(),
private_methods: ThinVec::new(), private_methods: ThinVec::new(),
class_object: None, class_object: None,
script_or_module,
}, },
context.realm().clone(), context.realm().clone(),
) )
@ -693,12 +698,15 @@ pub(crate) fn create_function_object_fast(
let length: JsValue = code.length.into(); let length: JsValue = code.length.into();
let script_or_module = context.vm.active_runnable.clone();
let function = if r#async { let function = if r#async {
FunctionKind::Async { FunctionKind::Async {
code, code,
environments: context.vm.environments.clone(), environments: context.vm.environments.clone(),
home_object: None, home_object: None,
class_object: None, class_object: None,
script_or_module,
} }
} else { } else {
FunctionKind::Ordinary { FunctionKind::Ordinary {
@ -709,6 +717,7 @@ pub(crate) fn create_function_object_fast(
fields: ThinVec::new(), fields: ThinVec::new(),
private_methods: ThinVec::new(), private_methods: ThinVec::new(),
class_object: None, class_object: None,
script_or_module,
} }
}; };
@ -799,6 +808,8 @@ pub(crate) fn create_generator_function_object(
ObjectData::ordinary(), ObjectData::ordinary(),
); );
let script_or_module = context.vm.active_runnable.clone();
let constructor = if r#async { let constructor = if r#async {
let function = Function::new( let function = Function::new(
FunctionKind::AsyncGenerator { FunctionKind::AsyncGenerator {
@ -806,6 +817,7 @@ pub(crate) fn create_generator_function_object(
environments: context.vm.environments.clone(), environments: context.vm.environments.clone(),
home_object: None, home_object: None,
class_object: None, class_object: None,
script_or_module,
}, },
context.realm().clone(), context.realm().clone(),
); );
@ -821,6 +833,7 @@ pub(crate) fn create_generator_function_object(
environments: context.vm.environments.clone(), environments: context.vm.environments.clone(),
home_object: None, home_object: None,
class_object: None, class_object: None,
script_or_module,
}, },
context.realm().clone(), context.realm().clone(),
); );
@ -875,7 +888,8 @@ impl JsObject {
context.enter_realm(realm); context.enter_realm(realm);
context.vm.active_function = Some(active_function); context.vm.active_function = Some(active_function);
let (code, mut environments, class_object, async_, gen) = match function_object.kind() { let (code, mut environments, class_object, mut script_or_module, async_, gen) =
match function_object.kind() {
FunctionKind::Native { FunctionKind::Native {
function, function,
constructor, constructor,
@ -895,6 +909,7 @@ impl JsObject {
code, code,
environments, environments,
class_object, class_object,
script_or_module,
.. ..
} => { } => {
let code = code.clone(); let code = code.clone();
@ -908,6 +923,7 @@ impl JsObject {
code, code,
environments.clone(), environments.clone(),
class_object.clone(), class_object.clone(),
script_or_module.clone(),
false, false,
false, false,
) )
@ -916,11 +932,14 @@ impl JsObject {
code, code,
environments, environments,
class_object, class_object,
script_or_module,
.. ..
} => ( } => (
code.clone(), code.clone(),
environments.clone(), environments.clone(),
class_object.clone(), class_object.clone(),
script_or_module.clone(),
true, true,
false, false,
), ),
@ -928,11 +947,13 @@ impl JsObject {
code, code,
environments, environments,
class_object, class_object,
script_or_module,
.. ..
} => ( } => (
code.clone(), code.clone(),
environments.clone(), environments.clone(),
class_object.clone(), class_object.clone(),
script_or_module.clone(),
false, false,
true, true,
), ),
@ -940,11 +961,14 @@ impl JsObject {
code, code,
environments, environments,
class_object, class_object,
script_or_module,
.. ..
} => ( } => (
code.clone(), code.clone(),
environments.clone(), environments.clone(),
class_object.clone(), class_object.clone(),
script_or_module.clone(),
true, true,
true, true,
), ),
@ -1063,6 +1087,8 @@ impl JsObject {
.with_arg_count(arg_count); .with_arg_count(arg_count);
frame.promise_capability = promise_capability.clone(); frame.promise_capability = promise_capability.clone();
std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module);
context.vm.push_frame(frame); context.vm.push_frame(frame);
let result = context let result = context
@ -1073,6 +1099,7 @@ impl JsObject {
let call_frame = context.vm.pop_frame().expect("frame must exist"); let call_frame = context.vm.pop_frame().expect("frame must exist");
std::mem::swap(&mut environments, &mut context.vm.environments); std::mem::swap(&mut environments, &mut context.vm.environments);
std::mem::swap(&mut context.vm.stack, &mut stack); 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 { if let Some(promise_capability) = promise_capability {
Ok(promise_capability.promise().clone().into()) Ok(promise_capability.promise().clone().into())
@ -1208,10 +1235,12 @@ impl JsObject {
code, code,
environments, environments,
constructor_kind, constructor_kind,
script_or_module,
.. ..
} => { } => {
let code = code.clone(); let code = code.clone();
let mut environments = environments.clone(); let mut environments = environments.clone();
let mut script_or_module = script_or_module.clone();
let constructor_kind = *constructor_kind; let constructor_kind = *constructor_kind;
drop(object); drop(object);
@ -1317,6 +1346,8 @@ impl JsObject {
let param_count = code.params.as_ref().len(); let param_count = code.params.as_ref().len();
let has_binding_identifier = code.has_binding_identifier; let has_binding_identifier = code.has_binding_identifier;
std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module);
context.vm.push_frame( context.vm.push_frame(
CallFrame::new(code) CallFrame::new(code)
.with_param_count(param_count) .with_param_count(param_count)
@ -1328,6 +1359,7 @@ impl JsObject {
context.vm.pop_frame(); context.vm.pop_frame();
std::mem::swap(&mut environments, &mut context.vm.environments); 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 { let environment = if has_binding_identifier {
environments.truncate(environments_len + 2); environments.truncate(environments_len + 2);

5
boa_engine/src/vm/flowgraph/mod.rs

@ -593,9 +593,10 @@ impl CodeBlock {
| Opcode::SetPrototype | Opcode::SetPrototype
| Opcode::IsObject | Opcode::IsObject
| Opcode::SetNameByLocator | Opcode::SetNameByLocator
| Opcode::Nop
| Opcode::PushObjectEnvironment | Opcode::PushObjectEnvironment
| Opcode::PopPrivateEnvironment => { | Opcode::PopPrivateEnvironment
| Opcode::ImportCall
| Opcode::Nop => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
} }

23
boa_engine/src/vm/mod.rs

@ -9,11 +9,12 @@ use crate::JsNativeError;
use crate::{ use crate::{
builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, builtins::async_generator::{AsyncGenerator, AsyncGeneratorState},
environments::{DeclarativeEnvironment, EnvironmentStack}, environments::{DeclarativeEnvironment, EnvironmentStack},
script::Script,
vm::code_block::Readable, 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 boa_profiler::Profiler;
use std::{convert::TryInto, mem::size_of}; use std::{convert::TryInto, mem::size_of};
@ -58,6 +59,23 @@ pub struct Vm {
pub(crate) trace: bool, pub(crate) trace: bool,
pub(crate) runtime_limits: RuntimeLimits, pub(crate) runtime_limits: RuntimeLimits,
pub(crate) active_function: Option<JsObject>, pub(crate) active_function: Option<JsObject>,
pub(crate) active_runnable: Option<ActiveRunnable>,
}
/// 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 { impl Vm {
@ -72,6 +90,7 @@ impl Vm {
trace: false, trace: false,
runtime_limits: RuntimeLimits::default(), runtime_limits: RuntimeLimits::default(),
active_function: None, active_function: None,
active_runnable: None,
} }
} }

223
boa_engine/src/vm/opcode/call/mod.rs

@ -1,8 +1,10 @@
use crate::{ use crate::{
builtins::function::FunctionKind, builtins::{function::FunctionKind, promise::PromiseCapability, Promise},
error::JsNativeError, error::JsNativeError,
module::{ModuleKind, Referrer},
object::FunctionObjectBuilder,
vm::{opcode::Operation, CompletionType}, vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue, Context, JsResult, JsValue, NativeFunction,
}; };
/// `CallEval` implements the Opcode Operation for `Opcode::CallEval` /// `CallEval` implements the Opcode Operation for `Opcode::CallEval`
@ -250,3 +252,220 @@ impl Operation for CallSpread {
Ok(CompletionType::Normal) 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<CompletionType> {
// 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)
}
}

7
boa_engine/src/vm/opcode/mod.rs

@ -1230,6 +1230,13 @@ generate_impl! {
/// Stack: argument_1, ... argument_n **=>** /// Stack: argument_1, ... argument_n **=>**
SuperCallDerived, SuperCallDerived,
/// Dynamically import a module.
///
/// Operands:
///
/// Stack: specifier **=>** promise
ImportCall,
/// Pop the two values of the stack, strict equal compares the two values, /// 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. /// if true jumps to address, otherwise push the second pop'ed value.
/// ///

2
boa_examples/src/bin/classes.rs

@ -149,7 +149,7 @@ fn main() {
// Having done all of that, we can execute Javascript code with `eval`, // Having done all of that, we can execute Javascript code with `eval`,
// and access the `Person` class defined in Rust! // and access the `Person` class defined in Rust!
context context
.eval_script(Source::from_bytes( .eval(Source::from_bytes(
r" r"
let person = new Person('John', 19); let person = new Person('John', 19);
person.sayHello(); person.sayHello();

13
boa_examples/src/bin/closures.rs

@ -38,10 +38,7 @@ fn main() -> Result<(), JsError> {
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(context.eval(Source::from_bytes("closure()"))?, 255.into());
context.eval_script(Source::from_bytes("closure()"))?,
255.into()
);
// We have created a closure with moved variables and executed that closure // We have created a closure with moved variables and executed that closure
// inside Javascript! // inside Javascript!
@ -124,13 +121,13 @@ fn main() -> Result<(), JsError> {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
context.eval_script(Source::from_bytes("createMessage()"))?, context.eval(Source::from_bytes("createMessage()"))?,
"message from `Boa dev`: Hello!".into() "message from `Boa dev`: Hello!".into()
); );
// The data mutates between calls // The data mutates between calls
assert_eq!( 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() "message from `Boa dev`: Hello! Hello! Hello!".into()
); );
@ -174,7 +171,7 @@ fn main() -> Result<(), JsError> {
.unwrap(); .unwrap();
// First call should return the array `[0]`. // 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 let object = result
.as_object() .as_object()
.cloned() .cloned()
@ -185,7 +182,7 @@ fn main() -> Result<(), JsError> {
assert_eq!(array.get(1, &mut context)?, JsValue::undefined()); assert_eq!(array.get(1, &mut context)?, JsValue::undefined());
// First call should return the array `[0, 1]`. // 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 let object = result
.as_object() .as_object()
.cloned() .cloned()

6
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()); Parser::new(Source::from_filepath(Path::new("boa_examples/scripts/calc.js")).unwrap());
let mut ctx = Context::default(); 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(); let mut visitor = CommutorVisitor::default();
assert!(matches!( assert!(matches!(
visitor.visit_statement_list_mut(&mut statements), visitor.visit_statement_list_mut(script.statements_mut()),
ControlFlow::Continue(_) ControlFlow::Continue(_)
)); ));
println!("{}", statements.to_interned_string(ctx.interner())); println!("{}", script.to_interned_string(ctx.interner()));
} }

2
boa_examples/src/bin/derive.rs

@ -26,7 +26,7 @@ fn main() {
let js = Source::from_bytes(js_str); let js = Source::from_bytes(js_str);
let mut context = Context::default(); 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) let str = TestStruct::try_from_js(&res, &mut context)
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())

2
boa_examples/src/bin/futures.rs

@ -166,7 +166,7 @@ fn main() {
"#; "#;
let now = Instant::now(); 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. // Important to run this after evaluating, since this is what triggers to run the enqueued jobs.
context.run_jobs(); context.run_jobs();

2
boa_examples/src/bin/loadfile.rs

@ -13,7 +13,7 @@ fn main() {
// Instantiate the execution context // Instantiate the execution context
let mut context = Context::default(); let mut context = Context::default();
// Parse the source code // Parse the source code
match context.eval_script(src) { match context.eval(src) {
Ok(res) => { Ok(res) => {
println!( println!(
"{}", "{}",

2
boa_examples/src/bin/loadstring.rs

@ -9,7 +9,7 @@ fn main() {
let mut context = Context::default(); let mut context = Context::default();
// Parse the source code // Parse the source code
match context.eval_script(Source::from_bytes(js_code)) { match context.eval(Source::from_bytes(js_code)) {
Ok(res) => { Ok(res) => {
println!( println!(
"{}", "{}",

6
boa_examples/src/bin/modulehandler.rs

@ -33,8 +33,7 @@ fn main() {
// Instantiating the engine with the execution context // Instantiating the engine with the execution context
// Loading, parsing and executing the JS code from the source file // Loading, parsing and executing the JS code from the source file
ctx.eval_script(Source::from_bytes(&buffer.unwrap())) ctx.eval(Source::from_bytes(&buffer.unwrap())).unwrap();
.unwrap();
} }
// Custom implementation that mimics the 'require' module loader // Custom implementation that mimics the 'require' module loader
@ -55,8 +54,7 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult<JsV
Ok(JsValue::Rational(-1.0)) Ok(JsValue::Rational(-1.0))
} else { } else {
// Load and parse the module source // Load and parse the module source
ctx.eval_script(Source::from_bytes(&buffer.unwrap())) ctx.eval(Source::from_bytes(&buffer.unwrap())).unwrap();
.unwrap();
// Access module.exports and return as ResultValue // Access module.exports and return as ResultValue
let global_obj = ctx.global_object(); let global_obj = ctx.global_object();

16
boa_examples/src/bin/runtime_limits.rs

@ -12,7 +12,7 @@ fn main() {
context.runtime_limits_mut().set_loop_iteration_limit(10); context.runtime_limits_mut().set_loop_iteration_limit(10);
// The code below iterates 5 times, so no error is thrown. // The code below iterates 5 times, so no error is thrown.
let result = context.eval_script(Source::from_bytes( let result = context.eval(Source::from_bytes(
r" r"
for (let i = 0; i < 5; ++i) { } for (let i = 0; i < 5; ++i) { }
", ",
@ -22,7 +22,7 @@ fn main() {
// Here we exceed the limit by 1 iteration and a `RuntimeLimit` error is thrown. // Here we exceed the limit by 1 iteration and a `RuntimeLimit` error is thrown.
// //
// This error cannot be caught in JavaScript it propagates to rust caller. // This error cannot be caught in JavaScript it propagates to rust caller.
let result = context.eval_script(Source::from_bytes( let result = context.eval(Source::from_bytes(
r" r"
try { try {
for (let i = 0; i < 11; ++i) { } for (let i = 0; i < 11; ++i) { }
@ -34,7 +34,7 @@ fn main() {
assert!(result.is_err()); assert!(result.is_err());
// Preventing an infinity loops // Preventing an infinity loops
let result = context.eval_script(Source::from_bytes( let result = context.eval(Source::from_bytes(
r" r"
while (true) { } while (true) { }
", ",
@ -42,7 +42,7 @@ fn main() {
assert!(result.is_err()); assert!(result.is_err());
// The limit applies to all types of loops. // The limit applies to all types of loops.
let result = context.eval_script(Source::from_bytes( let result = context.eval(Source::from_bytes(
r" r"
for (let e of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) { } for (let e of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) { }
", ",
@ -54,7 +54,7 @@ fn main() {
// ----------------------------------------- // -----------------------------------------
// Create and register `factorial` function. // Create and register `factorial` function.
let result = context.eval_script(Source::from_bytes( let result = context.eval(Source::from_bytes(
r" r"
function factorial(n) { function factorial(n) {
if (n == 0) { if (n == 0) {
@ -68,17 +68,17 @@ fn main() {
assert!(result.is_ok()); assert!(result.is_ok());
// Run function before setting the limit and assert that it works. // Run function before setting the limit and assert that it works.
let result = context.eval_script(Source::from_bytes("factorial(11)")); let result = context.eval(Source::from_bytes("factorial(11)"));
assert_eq!(result, Ok(JsValue::new(39_916_800))); assert_eq!(result, Ok(JsValue::new(39_916_800)));
// Setting runtime limit for recustion to 10. // Setting runtime limit for recustion to 10.
context.runtime_limits_mut().set_recursion_limit(10); context.runtime_limits_mut().set_recursion_limit(10);
// Run without exceeding recursion limit and assert that it works. // Run without exceeding recursion limit and assert that it works.
let result = context.eval_script(Source::from_bytes("factorial(8)")); let result = context.eval(Source::from_bytes("factorial(8)"));
assert_eq!(result, Ok(JsValue::new(40_320))); assert_eq!(result, Ok(JsValue::new(40_320)));
// Run exceeding limit by 1 and assert that it fails. // Run exceeding limit by 1 and assert that it fails.
let result = context.eval_script(Source::from_bytes("factorial(11)")); let result = context.eval(Source::from_bytes("factorial(11)"));
assert!(result.is_err()); assert!(result.is_err());
} }

4
boa_examples/src/bin/symbol_visitor.rs

@ -28,12 +28,12 @@ fn main() {
Parser::new(Source::from_filepath(Path::new("boa_examples/scripts/calc.js")).unwrap()); Parser::new(Source::from_filepath(Path::new("boa_examples/scripts/calc.js")).unwrap());
let mut ctx = Context::default(); let mut ctx = Context::default();
let statements = parser.parse_script(ctx.interner_mut()).unwrap(); let script = parser.parse_script(ctx.interner_mut()).unwrap();
let mut visitor = SymbolVisitor::default(); let mut visitor = SymbolVisitor::default();
assert!(matches!( assert!(matches!(
visitor.visit_statement_list(&statements), visitor.visit_statement_list(script.statements()),
ControlFlow::Continue(_) ControlFlow::Continue(_)
)); ));

23
boa_parser/src/parser/expression/assignment/arrow_function.rs

@ -18,7 +18,7 @@ use crate::{
TokenParser, TokenParser,
}, },
}; };
use ast::operations::{bound_names, top_level_lexically_declared_names}; use ast::operations::{bound_names, lexically_declared_names};
use boa_ast::{ use boa_ast::{
self as ast, self as ast,
declaration::Variable, declaration::Variable,
@ -159,7 +159,7 @@ where
// https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors // https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;
@ -190,25 +190,28 @@ impl<R> TokenParser<R> for ConciseBody
where where
R: Read, R: Read,
{ {
type Output = StatementList; type Output = ast::function::FunctionBody;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let stmts =
match cursor.peek(0, interner).or_abrupt()?.kind() { match cursor.peek(0, interner).or_abrupt()?.kind() {
TokenKind::Punctuator(Punctuator::OpenBlock) => { TokenKind::Punctuator(Punctuator::OpenBlock) => {
cursor.advance(interner); cursor.advance(interner);
let body = FunctionBody::new(false, false).parse(cursor, interner)?; let body = FunctionBody::new(false, false).parse(cursor, interner)?;
cursor.expect(Punctuator::CloseBlock, "arrow function", interner)?; cursor.expect(Punctuator::CloseBlock, "arrow function", interner)?;
Ok(body) body
} }
_ => Ok(StatementList::from(vec![ast::Statement::Return( _ => ast::function::FunctionBody::new(StatementList::from(vec![
Return::new( ast::Statement::Return(Return::new(
ExpressionBody::new(self.allow_in, false) ExpressionBody::new(self.allow_in, false)
.parse(cursor, interner)? .parse(cursor, interner)?
.into(), .into(),
), ))
) .into(),
.into()])), ])),
} };
Ok(stmts)
} }
} }

23
boa_parser/src/parser/expression/assignment/async_arrow_function.rs

@ -18,7 +18,7 @@ use crate::{
}, },
}; };
use ast::{ use ast::{
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, operations::{bound_names, contains, lexically_declared_names, ContainsSymbol},
Keyword, Keyword,
}; };
use boa_ast::{ use boa_ast::{
@ -145,7 +145,7 @@ where
// also occurs in the LexicallyDeclaredNames of AsyncConciseBody. // also occurs in the LexicallyDeclaredNames of AsyncConciseBody.
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;
@ -178,24 +178,27 @@ impl<R> TokenParser<R> for AsyncConciseBody
where where
R: Read, R: Read,
{ {
type Output = StatementList; type Output = ast::function::FunctionBody;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let body =
match cursor.peek(0, interner).or_abrupt()?.kind() { match cursor.peek(0, interner).or_abrupt()?.kind() {
TokenKind::Punctuator(Punctuator::OpenBlock) => { TokenKind::Punctuator(Punctuator::OpenBlock) => {
cursor.advance(interner); cursor.advance(interner);
let body = FunctionBody::new(false, true).parse(cursor, interner)?; let body = FunctionBody::new(false, true).parse(cursor, interner)?;
cursor.expect(Punctuator::CloseBlock, "async arrow function", interner)?; cursor.expect(Punctuator::CloseBlock, "async arrow function", interner)?;
Ok(body) body
} }
_ => Ok(StatementList::from(vec![ast::Statement::Return( _ => ast::function::FunctionBody::new(StatementList::from(vec![
Return::new( ast::Statement::Return(Return::new(
ExpressionBody::new(self.allow_in, true) ExpressionBody::new(self.allow_in, true)
.parse(cursor, interner)? .parse(cursor, interner)?
.into(), .into(),
), ))
) .into(),
.into()])), ])),
} };
Ok(body)
} }
} }

4
boa_parser/src/parser/expression/assignment/mod.rs

@ -32,7 +32,7 @@ use boa_ast::{
operator::assign::{Assign, AssignOp, AssignTarget}, operator::assign::{Assign, AssignOp, AssignTarget},
Identifier, Identifier,
}, },
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, operations::{bound_names, contains, lexically_declared_names, ContainsSymbol},
Expression, Keyword, Punctuator, Expression, Keyword, Punctuator,
}; };
use boa_interner::Interner; use boa_interner::Interner;
@ -225,7 +225,7 @@ where
// https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors // https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&parameters), &bound_names(&parameters),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
position, position,
interner, interner,
)?; )?;

39
boa_parser/src/parser/expression/left_hand_side/call.rs

@ -72,7 +72,7 @@ where
let token = cursor.peek(0, interner).or_abrupt()?; 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 = let args =
Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
Call::new(self.first_member_expr, args).into() 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<Y, A>(allow_yield: Y, allow_await: A, call: ast::Expression) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
call,
}
}
}
impl<R> TokenParser<R> for CallExpressionTail
where
R: Read,
{
type Output = ast::Expression;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let mut lhs = self.call;
while let Some(tok) = cursor.peek(0, interner)? { while let Some(tok) = cursor.peek(0, interner)? {
let token = tok.clone(); let token = tok.clone();
match token.kind() { match token.kind() {
@ -147,6 +183,7 @@ where
_ => break, _ => break,
} }
} }
Ok(lhs) Ok(lhs)
} }
} }

50
boa_parser/src/parser/expression/left_hand_side/mod.rs

@ -19,15 +19,21 @@ mod template;
use crate::{ use crate::{
lexer::{InputElement, TokenKind}, lexer::{InputElement, TokenKind},
parser::{ parser::{
expression::left_hand_side::{ expression::{
arguments::Arguments, call::CallExpression, member::MemberExpression, left_hand_side::{
arguments::Arguments,
call::{CallExpression, CallExpressionTail},
member::MemberExpression,
optional::OptionalExpression, optional::OptionalExpression,
}, },
AllowAwait, AllowYield, Cursor, ParseResult, TokenParser, AssignmentExpression,
}, },
AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser,
},
Error,
}; };
use boa_ast::{ use boa_ast::{
expression::{Identifier, SuperCall}, expression::{Identifier, ImportCall, SuperCall},
Expression, Keyword, Punctuator, Expression, Keyword, Punctuator,
}; };
use boa_interner::Interner; use boa_interner::Interner;
@ -96,6 +102,7 @@ where
} }
Ok(false) Ok(false)
} }
let _timer = Profiler::global().start_event("LeftHandSideExpression", "Parsing"); let _timer = Profiler::global().start_event("LeftHandSideExpression", "Parsing");
cursor.set_goal(InputElement::TemplateTail); cursor.set_goal(InputElement::TemplateTail);
@ -106,7 +113,39 @@ where
Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
SuperCall::new(args).into() SuperCall::new(args).into()
} else { } else {
let mut member = MemberExpression::new(self.name, self.allow_yield, self.allow_await) 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)?; .parse(cursor, interner)?;
if let Some(tok) = cursor.peek(0, interner)? { if let Some(tok) = cursor.peek(0, interner)? {
if tok.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) { if tok.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) {
@ -115,6 +154,7 @@ where
} }
} }
member member
}
}; };
if let Some(tok) = cursor.peek(0, interner)? { if let Some(tok) = cursor.peek(0, interner)? {

4
boa_parser/src/parser/expression/primary/async_function_expression/mod.rs

@ -13,7 +13,7 @@ use crate::{
use boa_ast::{ use boa_ast::{
expression::Identifier, expression::Identifier,
function::AsyncFunction, function::AsyncFunction,
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, operations::{bound_names, contains, lexically_declared_names, ContainsSymbol},
Keyword, Punctuator, Keyword, Punctuator,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
@ -140,7 +140,7 @@ where
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;

8
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::{ use boa_ast::{
declaration::{Declaration, LexicalDeclaration, Variable}, declaration::{Declaration, LexicalDeclaration, Variable},
expression::literal::Literal, expression::literal::Literal,
function::{AsyncFunction, FormalParameterList}, function::{AsyncFunction, FormalParameterList, FunctionBody},
statement::Return, statement::Return,
Statement, StatementListItem, Statement, StatementListItem,
}; };
@ -26,10 +26,12 @@ fn check_async_expression() {
AsyncFunction::new( AsyncFunction::new(
Some(add.into()), Some(add.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())), Return::new(Some(Literal::from(1).into())),
))] ))]
.into(), .into(),
),
false, false,
) )
.into(), .into(),
@ -62,6 +64,7 @@ fn check_nested_async_expression() {
AsyncFunction::new( AsyncFunction::new(
Some(a.into()), Some(a.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![Declaration::Lexical(LexicalDeclaration::Const( vec![Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier( vec![Variable::from_identifier(
b.into(), b.into(),
@ -69,11 +72,13 @@ fn check_nested_async_expression() {
AsyncFunction::new( AsyncFunction::new(
Some(b.into()), Some(b.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![Statement::Return(Return::new(Some( vec![Statement::Return(Return::new(Some(
Literal::from(1).into(), Literal::from(1).into(),
))) )))
.into()] .into()]
.into(), .into(),
),
false, false,
) )
.into(), .into(),
@ -84,6 +89,7 @@ fn check_nested_async_expression() {
)) ))
.into()] .into()]
.into(), .into(),
),
false, false,
) )
.into(), .into(),

4
boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs

@ -22,7 +22,7 @@ use crate::{
use boa_ast::{ use boa_ast::{
expression::Identifier, expression::Identifier,
function::AsyncGenerator, function::AsyncGenerator,
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, operations::{bound_names, contains, lexically_declared_names, ContainsSymbol},
Keyword, Punctuator, Keyword, Punctuator,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
@ -177,7 +177,7 @@ where
// also occurs in the LexicallyDeclaredNames of FunctionBody. // also occurs in the LexicallyDeclaredNames of FunctionBody.
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;

16
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::{ use boa_ast::{
declaration::{LexicalDeclaration, Variable}, declaration::{LexicalDeclaration, Variable},
expression::literal::Literal, expression::literal::Literal,
function::{AsyncGenerator, FormalParameterList}, function::{AsyncGenerator, FormalParameterList, FunctionBody},
statement::Return, statement::Return,
Declaration, Statement, StatementListItem, Declaration, Statement, StatementListItem,
}; };
@ -27,10 +27,12 @@ fn check_async_generator_expr() {
AsyncGenerator::new( AsyncGenerator::new(
Some(add.into()), Some(add.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())), Return::new(Some(Literal::from(1).into())),
))] ))]
.into(), .into(),
),
false, false,
) )
.into(), .into(),
@ -63,6 +65,7 @@ fn check_nested_async_generator_expr() {
AsyncGenerator::new( AsyncGenerator::new(
Some(a.into()), Some(a.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![Declaration::Lexical(LexicalDeclaration::Const( vec![Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier( vec![Variable::from_identifier(
b.into(), b.into(),
@ -70,10 +73,14 @@ fn check_nested_async_generator_expr() {
AsyncGenerator::new( AsyncGenerator::new(
Some(b.into()), Some(b.into()),
FormalParameterList::default(), FormalParameterList::default(),
vec![StatementListItem::Statement(Statement::Return( FunctionBody::new(
Return::new(Some(Literal::from(1).into())), vec![StatementListItem::Statement(
))] Statement::Return(Return::new(Some(
Literal::from(1).into(),
))),
)]
.into(), .into(),
),
false, false,
) )
.into(), .into(),
@ -84,6 +91,7 @@ fn check_nested_async_generator_expr() {
)) ))
.into()] .into()]
.into(), .into(),
),
false, false,
) )
.into(), .into(),

4
boa_parser/src/parser/expression/primary/function_expression/mod.rs

@ -22,7 +22,7 @@ use crate::{
use boa_ast::{ use boa_ast::{
expression::Identifier, expression::Identifier,
function::Function, function::Function,
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, operations::{bound_names, contains, lexically_declared_names, ContainsSymbol},
Keyword, Punctuator, Keyword, Punctuator,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
@ -135,7 +135,7 @@ where
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;

26
boa_parser/src/parser/expression/primary/function_expression/tests.rs

@ -2,7 +2,7 @@ use crate::parser::tests::check_script_parser;
use boa_ast::{ use boa_ast::{
declaration::{LexicalDeclaration, Variable}, declaration::{LexicalDeclaration, Variable},
expression::literal::Literal, expression::literal::Literal,
function::{FormalParameterList, Function}, function::{FormalParameterList, Function, FunctionBody},
statement::Return, statement::Return,
Declaration, Statement, StatementListItem, Declaration, Statement, StatementListItem,
}; };
@ -26,10 +26,12 @@ fn check_function_expression() {
Function::new( Function::new(
Some(add.into()), Some(add.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())), Return::new(Some(Literal::from(1).into())),
))] ))]
.into(), .into(),
),
) )
.into(), .into(),
), ),
@ -61,6 +63,7 @@ fn check_nested_function_expression() {
Function::new( Function::new(
Some(a.into()), Some(a.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![Declaration::Lexical(LexicalDeclaration::Const( vec![Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier( vec![Variable::from_identifier(
b.into(), b.into(),
@ -68,10 +71,14 @@ fn check_nested_function_expression() {
Function::new( Function::new(
Some(b.into()), Some(b.into()),
FormalParameterList::default(), FormalParameterList::default(),
vec![StatementListItem::Statement(Statement::Return( FunctionBody::new(
Return::new(Some(Literal::from(1).into())), vec![StatementListItem::Statement(
))] Statement::Return(Return::new(Some(
Literal::from(1).into(),
))),
)]
.into(), .into(),
),
) )
.into(), .into(),
), ),
@ -81,6 +88,7 @@ fn check_nested_function_expression() {
)) ))
.into()] .into()]
.into(), .into(),
),
) )
.into(), .into(),
), ),
@ -104,7 +112,15 @@ fn check_function_non_reserved_keyword() {
Function::new_with_binding_identifier( Function::new_with_binding_identifier(
Some($interner.get_or_intern_static($keyword, utf16!($keyword)).into()), Some($interner.get_or_intern_static($keyword, utf16!($keyword)).into()),
FormalParameterList::default(), 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, true,
) )
.into(), .into(),

4
boa_parser/src/parser/expression/primary/generator_expression/mod.rs

@ -22,7 +22,7 @@ use crate::{
use boa_ast::{ use boa_ast::{
expression::Identifier, expression::Identifier,
function::Generator, function::Generator,
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, operations::{bound_names, contains, lexically_declared_names, ContainsSymbol},
Keyword, Punctuator, Keyword, Punctuator,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
@ -143,7 +143,7 @@ where
// https://tc39.es/ecma262/#sec-generator-function-definitions-static-semantics-early-errors // https://tc39.es/ecma262/#sec-generator-function-definitions-static-semantics-early-errors
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;

6
boa_parser/src/parser/expression/primary/generator_expression/tests.rs

@ -2,7 +2,7 @@ use crate::parser::tests::check_script_parser;
use boa_ast::{ use boa_ast::{
declaration::{LexicalDeclaration, Variable}, declaration::{LexicalDeclaration, Variable},
expression::{literal::Literal, Yield}, expression::{literal::Literal, Yield},
function::{FormalParameterList, Generator}, function::{FormalParameterList, FunctionBody, Generator},
Declaration, Expression, Statement, StatementListItem, Declaration, Expression, Statement, StatementListItem,
}; };
use boa_interner::Interner; use boa_interner::Interner;
@ -24,10 +24,12 @@ fn check_generator_function_expression() {
Generator::new( Generator::new(
Some(gen.into()), Some(gen.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Expression( vec![StatementListItem::Statement(Statement::Expression(
Expression::from(Yield::new(Some(Literal::from(1).into()), false)), Expression::from(Yield::new(Some(Literal::from(1).into()), false)),
))] ))]
.into(), .into(),
),
false, false,
) )
.into(), .into(),
@ -57,10 +59,12 @@ fn check_generator_function_delegate_yield_expression() {
Generator::new( Generator::new(
Some(gen.into()), Some(gen.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Expression( vec![StatementListItem::Statement(Statement::Expression(
Expression::from(Yield::new(Some(Literal::from(1).into()), true)), Expression::from(Yield::new(Some(Literal::from(1).into()), true)),
))] ))]
.into(), .into(),
),
false, false,
) )
.into(), .into(),

12
boa_parser/src/parser/expression/primary/object_initializer/mod.rs

@ -32,7 +32,7 @@ use boa_ast::{
AsyncFunction, AsyncGenerator, FormalParameterList, Function, Generator, PrivateName, AsyncFunction, AsyncGenerator, FormalParameterList, Function, Generator, PrivateName,
}, },
operations::{ 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}, property::{self, MethodDefinition},
Expression, Keyword, Punctuator, Expression, Keyword, Punctuator,
@ -442,7 +442,7 @@ where
// https://tc39.es/ecma262/#sec-method-definitions-static-semantics-early-errors // https://tc39.es/ecma262/#sec-method-definitions-static-semantics-early-errors
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&parameters), &bound_names(&parameters),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;
@ -518,7 +518,7 @@ where
// LexicallyDeclaredNames of FunctionBody. // LexicallyDeclaredNames of FunctionBody.
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;
@ -801,7 +801,7 @@ where
// occurs in the LexicallyDeclaredNames of GeneratorBody. // occurs in the LexicallyDeclaredNames of GeneratorBody.
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;
@ -917,7 +917,7 @@ where
// occurs in the LexicallyDeclaredNames of GeneratorBody. // occurs in the LexicallyDeclaredNames of GeneratorBody.
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;
@ -1010,7 +1010,7 @@ where
// occurs in the LexicallyDeclaredNames of GeneratorBody. // occurs in the LexicallyDeclaredNames of GeneratorBody.
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;

22
boa_parser/src/parser/expression/primary/object_initializer/tests.rs

@ -7,10 +7,10 @@ use boa_ast::{
}, },
function::{ function::{
AsyncFunction, AsyncGenerator, FormalParameter, FormalParameterList, AsyncFunction, AsyncGenerator, FormalParameter, FormalParameterList,
FormalParameterListFlags, Function, FormalParameterListFlags, Function, FunctionBody,
}, },
property::{MethodDefinition, PropertyDefinition, PropertyName}, property::{MethodDefinition, PropertyDefinition, PropertyName},
Declaration, StatementList, Declaration,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
use boa_macros::utf16; use boa_macros::utf16;
@ -65,7 +65,7 @@ fn check_object_short_function() {
MethodDefinition::Ordinary(Function::new( MethodDefinition::Ordinary(Function::new(
Some(interner.get_or_intern_static("b", utf16!("b")).into()), Some(interner.get_or_intern_static("b", utf16!("b")).into()),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
)), )),
), ),
]; ];
@ -115,7 +115,7 @@ fn check_object_short_function_arguments() {
MethodDefinition::Ordinary(Function::new( MethodDefinition::Ordinary(Function::new(
Some(interner.get_or_intern_static("b", utf16!("b")).into()), Some(interner.get_or_intern_static("b", utf16!("b")).into()),
parameters, parameters,
StatementList::default(), FunctionBody::default(),
)), )),
), ),
]; ];
@ -157,7 +157,7 @@ fn check_object_getter() {
.into(), .into(),
), ),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
)), )),
), ),
]; ];
@ -210,7 +210,7 @@ fn check_object_setter() {
.into(), .into(),
), ),
params, params,
StatementList::default(), FunctionBody::default(),
)), )),
), ),
]; ];
@ -243,7 +243,7 @@ fn check_object_short_function_get() {
MethodDefinition::Ordinary(Function::new( MethodDefinition::Ordinary(Function::new(
Some(interner.get_or_intern_static("get", utf16!("get")).into()), Some(interner.get_or_intern_static("get", utf16!("get")).into()),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
)), )),
)]; )];
@ -274,7 +274,7 @@ fn check_object_short_function_set() {
MethodDefinition::Ordinary(Function::new( MethodDefinition::Ordinary(Function::new(
Some(interner.get_or_intern_static("set", utf16!("set")).into()), Some(interner.get_or_intern_static("set", utf16!("set")).into()),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
)), )),
)]; )];
@ -422,7 +422,7 @@ fn check_async_method() {
MethodDefinition::Async(AsyncFunction::new( MethodDefinition::Async(AsyncFunction::new(
Some(interner.get_or_intern_static("dive", utf16!("dive")).into()), Some(interner.get_or_intern_static("dive", utf16!("dive")).into()),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
false, false,
)), )),
)]; )];
@ -460,7 +460,7 @@ fn check_async_generator_method() {
.into(), .into(),
), ),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
false, false,
)), )),
)]; )];
@ -518,7 +518,7 @@ fn check_async_ordinary_method() {
.into(), .into(),
), ),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
)), )),
)]; )];

4
boa_parser/src/parser/function/mod.rs

@ -449,7 +449,7 @@ impl<R> TokenParser<R> for FunctionStatementList
where where
R: Read, R: Read,
{ {
type Output = ast::StatementList; type Output = ast::function::FunctionBody;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("FunctionStatementList", "Parsing"); let _timer = Profiler::global().start_event("FunctionStatementList", "Parsing");
@ -478,6 +478,6 @@ where
))); )));
} }
Ok(statement_list) Ok(ast::function::FunctionBody::new(statement_list))
} }
} }

75
boa_parser/src/parser/function/tests.rs

@ -7,9 +7,10 @@ use boa_ast::{
}, },
function::{ function::{
ArrowFunction, FormalParameter, FormalParameterList, FormalParameterListFlags, Function, ArrowFunction, FormalParameter, FormalParameterList, FormalParameterListFlags, Function,
FunctionBody,
}, },
statement::Return, statement::Return,
Declaration, Expression, Statement, StatementList, StatementListItem, Declaration, Expression, Statement, StatementListItem,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use boa_macros::utf16; use boa_macros::utf16;
@ -30,12 +31,14 @@ fn check_basic() {
vec![Declaration::Function(Function::new( vec![Declaration::Function(Function::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(),
)), )),
))] ))]
.into(), .into(),
),
)) ))
.into()], .into()],
interner, interner,
@ -66,12 +69,14 @@ fn check_duplicates_strict_off() {
vec![Declaration::Function(Function::new( vec![Declaration::Function(Function::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(),
)), )),
))] ))]
.into(), .into(),
),
)) ))
.into()], .into()],
interner, interner,
@ -100,12 +105,14 @@ fn check_basic_semicolon_insertion() {
vec![Declaration::Function(Function::new( vec![Declaration::Function(Function::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(),
)), )),
))] ))]
.into(), .into(),
),
)) ))
.into()], .into()],
interner, interner,
@ -127,10 +134,12 @@ fn check_empty_return() {
vec![Declaration::Function(Function::new( vec![Declaration::Function(Function::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(None), Return::new(None),
))] ))]
.into(), .into(),
),
)) ))
.into()], .into()],
interner, interner,
@ -152,10 +161,12 @@ fn check_empty_return_semicolon_insertion() {
vec![Declaration::Function(Function::new( vec![Declaration::Function(Function::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(None), Return::new(None),
))] ))]
.into(), .into(),
),
)) ))
.into()], .into()],
interner, interner,
@ -186,7 +197,7 @@ fn check_rest_operator() {
vec![Declaration::Function(Function::new( vec![Declaration::Function(Function::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
StatementList::default(), FunctionBody::default(),
)) ))
.into()], .into()],
interner, interner,
@ -211,7 +222,7 @@ fn check_arrow_only_rest() {
vec![Statement::Expression(Expression::from(ArrowFunction::new( vec![Statement::Expression(Expression::from(ArrowFunction::new(
None, None,
params, params,
StatementList::default(), FunctionBody::default(),
))) )))
.into()], .into()],
interner, interner,
@ -246,7 +257,7 @@ fn check_arrow_rest() {
vec![Statement::Expression(Expression::from(ArrowFunction::new( vec![Statement::Expression(Expression::from(ArrowFunction::new(
None, None,
params, params,
StatementList::default(), FunctionBody::default(),
))) )))
.into()], .into()],
interner, interner,
@ -274,6 +285,7 @@ fn check_arrow() {
vec![Statement::Expression(Expression::from(ArrowFunction::new( vec![Statement::Expression(Expression::from(ArrowFunction::new(
None, None,
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Binary::new( Binary::new(
@ -285,6 +297,7 @@ fn check_arrow() {
)), )),
))] ))]
.into(), .into(),
),
))) )))
.into()], .into()],
interner, interner,
@ -310,6 +323,7 @@ fn check_arrow_semicolon_insertion() {
vec![Statement::Expression(Expression::from(ArrowFunction::new( vec![Statement::Expression(Expression::from(ArrowFunction::new(
None, None,
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Binary::new( Binary::new(
@ -321,6 +335,7 @@ fn check_arrow_semicolon_insertion() {
)), )),
))] ))]
.into(), .into(),
),
))) )))
.into()], .into()],
interner, interner,
@ -346,10 +361,12 @@ fn check_arrow_epty_return() {
vec![Statement::Expression(Expression::from(ArrowFunction::new( vec![Statement::Expression(Expression::from(ArrowFunction::new(
None, None,
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(None), Return::new(None),
))] ))]
.into(), .into(),
),
))) )))
.into()], .into()],
interner, interner,
@ -375,10 +392,12 @@ fn check_arrow_empty_return_semicolon_insertion() {
vec![Statement::Expression(Expression::from(ArrowFunction::new( vec![Statement::Expression(Expression::from(ArrowFunction::new(
None, None,
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(None), Return::new(None),
))] ))]
.into(), .into(),
),
))) )))
.into()], .into()],
interner, interner,
@ -403,13 +422,17 @@ fn check_arrow_assignment() {
ArrowFunction::new( ArrowFunction::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) Identifier::new(
interner.get_or_intern_static("a", utf16!("a")),
)
.into(), .into(),
)), )),
))] ))]
.into(), .into(),
),
) )
.into(), .into(),
), ),
@ -440,13 +463,17 @@ fn check_arrow_assignment_nobrackets() {
ArrowFunction::new( ArrowFunction::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) Identifier::new(
interner.get_or_intern_static("a", utf16!("a")),
)
.into(), .into(),
)), )),
))] ))]
.into(), .into(),
),
) )
.into(), .into(),
), ),
@ -477,13 +504,17 @@ fn check_arrow_assignment_noparenthesis() {
ArrowFunction::new( ArrowFunction::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) Identifier::new(
interner.get_or_intern_static("a", utf16!("a")),
)
.into(), .into(),
)), )),
))] ))]
.into(), .into(),
),
) )
.into(), .into(),
), ),
@ -514,13 +545,17 @@ fn check_arrow_assignment_noparenthesis_nobrackets() {
ArrowFunction::new( ArrowFunction::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) Identifier::new(
interner.get_or_intern_static("a", utf16!("a")),
)
.into(), .into(),
)), )),
))] ))]
.into(), .into(),
),
) )
.into(), .into(),
), ),
@ -557,13 +592,17 @@ fn check_arrow_assignment_2arg() {
ArrowFunction::new( ArrowFunction::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) Identifier::new(
interner.get_or_intern_static("a", utf16!("a")),
)
.into(), .into(),
)), )),
))] ))]
.into(), .into(),
),
) )
.into(), .into(),
), ),
@ -600,13 +639,17 @@ fn check_arrow_assignment_2arg_nobrackets() {
ArrowFunction::new( ArrowFunction::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) Identifier::new(
interner.get_or_intern_static("a", utf16!("a")),
)
.into(), .into(),
)), )),
))] ))]
.into(), .into(),
),
) )
.into(), .into(),
), ),
@ -647,13 +690,17 @@ fn check_arrow_assignment_3arg() {
ArrowFunction::new( ArrowFunction::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) Identifier::new(
interner.get_or_intern_static("a", utf16!("a")),
)
.into(), .into(),
)), )),
))] ))]
.into(), .into(),
),
) )
.into(), .into(),
), ),
@ -694,13 +741,17 @@ fn check_arrow_assignment_3arg_nobrackets() {
ArrowFunction::new( ArrowFunction::new(
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new(interner.get_or_intern_static("a", utf16!("a"))) Identifier::new(
interner.get_or_intern_static("a", utf16!("a")),
)
.into(), .into(),
)), )),
))] ))]
.into(), .into(),
),
) )
.into(), .into(),
), ),

150
boa_parser/src/parser/mod.rs

@ -20,18 +20,19 @@ use crate::{
}; };
use boa_ast::{ use boa_ast::{
expression::Identifier, expression::Identifier,
function::FormalParameterList, function::{FormalParameterList, FunctionBody},
operations::{ operations::{
all_private_identifiers_valid, check_labels, contains, contains_invalid_object_literal, all_private_identifiers_valid, check_labels, contains, contains_invalid_object_literal,
lexically_declared_names, top_level_lexically_declared_names, top_level_var_declared_names, lexically_declared_names, var_declared_names, ContainsSymbol,
var_declared_names, ContainsSymbol,
}, },
ModuleItemList, Position, StatementList, Position, StatementList,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::{io::Read, path::Path}; use std::{io::Read, path::Path};
use self::statement::ModuleItemList;
/// Trait implemented by parsers. /// Trait implemented by parsers.
/// ///
/// This makes it possible to abstract over the underlying implementation of a parser. /// 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. /// 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 /// # Errors
/// ///
/// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-Script /// [spec]: https://tc39.es/ecma262/#prod-Script
pub fn parse_script(&mut self, interner: &mut Interner) -> ParseResult<StatementList> { pub fn parse_script(&mut self, interner: &mut Interner) -> ParseResult<boa_ast::Script> {
Script::new(false).parse(&mut self.cursor, interner) ScriptParser::new(false).parse(&mut self.cursor, interner)
} }
/// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation. /// 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. /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-Module /// [spec]: https://tc39.es/ecma262/#prod-Module
pub fn parse_module(&mut self, interner: &mut Interner) -> ParseResult<ModuleItemList> pub fn parse_module(&mut self, interner: &mut Interner) -> ParseResult<boa_ast::Module>
where where
R: Read, R: Read,
{ {
Module.parse(&mut self.cursor, interner) ModuleParser.parse(&mut self.cursor, interner)
} }
/// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec] /// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec]
@ -165,8 +166,8 @@ impl<'a, R: Read> Parser<'a, R> {
&mut self, &mut self,
direct: bool, direct: bool,
interner: &mut Interner, interner: &mut Interner,
) -> ParseResult<StatementList> { ) -> ParseResult<boa_ast::Script> {
Script::new(direct).parse(&mut self.cursor, interner) ScriptParser::new(direct).parse(&mut self.cursor, interner)
} }
/// Parses the full input as an [ECMAScript `FunctionBody`][spec] into the boa AST representation. /// 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, interner: &mut Interner,
allow_yield: bool, allow_yield: bool,
allow_await: bool, allow_await: bool,
) -> ParseResult<StatementList> { ) -> ParseResult<FunctionBody> {
FunctionStatementList::new(allow_yield, allow_await).parse(&mut self.cursor, interner) FunctionStatementList::new(allow_yield, allow_await).parse(&mut self.cursor, interner)
} }
@ -235,11 +236,11 @@ impl<R> Parser<'_, R> {
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-Script /// [spec]: https://tc39.es/ecma262/#prod-Script
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Script { pub struct ScriptParser {
direct_eval: bool, direct_eval: bool,
} }
impl Script { impl ScriptParser {
/// Create a new `Script` parser. /// Create a new `Script` parser.
#[inline] #[inline]
const fn new(direct_eval: bool) -> Self { const fn new(direct_eval: bool) -> Self {
@ -247,19 +248,20 @@ impl Script {
} }
} }
impl<R> TokenParser<R> for Script impl<R> TokenParser<R> for ScriptParser
where where
R: Read, R: Read,
{ {
type Output = StatementList; type Output = boa_ast::Script;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let statement_list = let script = boa_ast::Script::new(
ScriptBody::new(true, cursor.strict(), self.direct_eval).parse(cursor, interner)?; 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. // It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries.
let mut lexical_names = FxHashSet::default(); 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) { if !lexical_names.insert(name) {
return Err(Error::general( return Err(Error::general(
"lexical name declared multiple times", "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. // 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) { if lexical_names.contains(&name) {
return Err(Error::general( return Err(Error::general(
"lexical name declared multiple times", "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<T> {
/// Will convert an `Ok(None)` to an [`Error::AbruptEnd`] or return the inner type if not.
fn or_abrupt(self) -> ParseResult<T>;
}
impl<T> OrAbrupt<T> for ParseResult<Option<T>> {
fn or_abrupt(self) -> ParseResult<T> {
self?.ok_or(Error::AbruptEnd)
}
}
/// Parses a full module. /// Parses a full module.
/// ///
/// More information: /// More information:
@ -410,26 +379,22 @@ impl<T> OrAbrupt<T> for ParseResult<Option<T>> {
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-Module /// [spec]: https://tc39.es/ecma262/#prod-Module
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct Module; struct ModuleParser;
impl<R> TokenParser<R> for Module impl<R> TokenParser<R> for ModuleParser
where where
R: Read, R: Read,
{ {
type Output = ModuleItemList; type Output = boa_ast::Module;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
cursor.set_module(); cursor.set_module();
let items = if cursor.peek(0, interner)?.is_some() { let module = boa_ast::Module::new(ModuleItemList.parse(cursor, interner)?);
self::statement::ModuleItemList.parse(cursor, interner)?
} else {
return Ok(Vec::new().into());
};
// It is a Syntax Error if the LexicallyDeclaredNames of ModuleItemList contains any duplicate entries. // It is a Syntax Error if the LexicallyDeclaredNames of ModuleItemList contains any duplicate entries.
let mut bindings = FxHashSet::default(); let mut bindings = FxHashSet::default();
for name in lexically_declared_names(&items) { for name in lexically_declared_names(&module) {
if !bindings.insert(name) { if !bindings.insert(name) {
return Err(Error::general( return Err(Error::general(
format!( format!(
@ -443,7 +408,7 @@ where
// It is a Syntax Error if any element of the LexicallyDeclaredNames of ModuleItemList also occurs in the // It is a Syntax Error if any element of the LexicallyDeclaredNames of ModuleItemList also occurs in the
// VarDeclaredNames of ModuleItemList. // VarDeclaredNames of ModuleItemList.
for name in var_declared_names(&items) { for name in var_declared_names(&module) {
if !bindings.insert(name) { if !bindings.insert(name) {
return Err(Error::general( return Err(Error::general(
format!( format!(
@ -458,7 +423,7 @@ where
// It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. // It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries.
{ {
let mut exported_names = FxHashSet::default(); let mut exported_names = FxHashSet::default();
for name in items.exported_names() { for name in module.items().exported_names() {
if !exported_names.insert(name) { if !exported_names.insert(name) {
return Err(Error::general( return Err(Error::general(
format!( 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 // 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. // 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) { if !bindings.contains(&name) {
return Err(Error::general( return Err(Error::general(
format!( format!(
@ -486,7 +451,7 @@ where
} }
// It is a Syntax Error if ModuleItemList Contains super. // It is a Syntax Error if ModuleItemList Contains super.
if contains(&items, ContainsSymbol::Super) { if contains(&module, ContainsSymbol::Super) {
return Err(Error::general( return Err(Error::general(
"module cannot contain `super` on the top-level", "module cannot contain `super` on the top-level",
Position::new(1, 1), Position::new(1, 1),
@ -494,19 +459,64 @@ where
} }
// It is a Syntax Error if ModuleItemList Contains NewTarget. // It is a Syntax Error if ModuleItemList Contains NewTarget.
if contains(&items, ContainsSymbol::NewTarget) { if contains(&module, ContainsSymbol::NewTarget) {
return Err(Error::general( return Err(Error::general(
"module cannot contain `new.target` on the top-level", "module cannot contain `new.target` on the top-level",
Position::new(1, 1), Position::new(1, 1),
)); ));
} }
// TODO:
// It is a Syntax Error if ContainsDuplicateLabels of ModuleItemList with argument « » is true. // 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 ContainsUndefinedBreakTarget of ModuleItemList with argument « » is true.
// It is a Syntax Error if ContainsUndefinedContinueTarget of ModuleItemList with arguments « » and « » 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. // 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)
}
}
/// 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(())
}
Ok(items) /// Trait to reduce boilerplate in the parser.
trait OrAbrupt<T> {
/// Will convert an `Ok(None)` to an [`Error::AbruptEnd`] or return the inner type if not.
fn or_abrupt(self) -> ParseResult<T>;
}
impl<T> OrAbrupt<T> for ParseResult<Option<T>> {
fn or_abrupt(self) -> ParseResult<T> {
self?.ok_or(Error::AbruptEnd)
} }
} }

6
boa_parser/src/parser/statement/block/tests.rs

@ -12,7 +12,7 @@ use boa_ast::{
}, },
Call, Identifier, Call, Identifier,
}, },
function::{FormalParameterList, Function}, function::{FormalParameterList, Function, FunctionBody},
statement::{Block, Return}, statement::{Block, Return},
Declaration, Expression, Statement, StatementListItem, Declaration, Expression, Statement, StatementListItem,
}; };
@ -81,10 +81,12 @@ fn non_empty() {
Declaration::Function(Function::new( Declaration::Function(Function::new(
Some(hello.into()), Some(hello.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(10).into())), Return::new(Some(Literal::from(10).into())),
))] ))]
.into(), .into(),
),
)) ))
.into(), .into(),
Statement::Var(VarDeclaration( Statement::Var(VarDeclaration(
@ -136,10 +138,12 @@ fn hoisting() {
Declaration::Function(Function::new( Declaration::Function(Function::new(
Some(hello.into()), Some(hello.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(10).into())), Return::new(Some(Literal::from(10).into())),
))] ))]
.into(), .into(),
),
)) ))
.into(), .into(),
], ],

33
boa_parser/src/parser/statement/declaration/export.rs

@ -59,7 +59,7 @@ where
let next = cursor.peek(0, interner).or_abrupt()?; let next = cursor.peek(0, interner).or_abrupt()?;
match next.kind() { let export = match next.kind() {
TokenKind::IdentifierName((Sym::AS, _)) => { TokenKind::IdentifierName((Sym::AS, _)) => {
cursor.advance(interner); cursor.advance(interner);
let tok = cursor.next(interner).or_abrupt()?; let tok = cursor.next(interner).or_abrupt()?;
@ -67,9 +67,10 @@ where
let alias = match tok.kind() { let alias = match tok.kind() {
TokenKind::StringLiteral((export_name, _)) TokenKind::StringLiteral((export_name, _))
| TokenKind::IdentifierName((export_name, _)) => *export_name, | TokenKind::IdentifierName((export_name, _)) => *export_name,
TokenKind::Keyword((kw, _)) => kw.to_sym(),
_ => { _ => {
return Err(Error::expected( return Err(Error::expected(
["identifier".to_owned(), "string literal".to_owned()], ["identifier name".to_owned(), "string literal".to_owned()],
tok.to_string(interner), tok.to_string(interner),
tok.span(), tok.span(),
"export declaration", "export declaration",
@ -102,7 +103,11 @@ where
"export declaration", "export declaration",
)) ))
} }
} };
cursor.expect_semicolon("star re-export", interner)?;
export
} }
TokenKind::Punctuator(Punctuator::OpenBlock) => { TokenKind::Punctuator(Punctuator::OpenBlock) => {
let names = NamedExports.parse(cursor, interner)?; let names = NamedExports.parse(cursor, interner)?;
@ -115,11 +120,16 @@ where
) { ) {
let specifier = let specifier =
FromClause::new("export declaration").parse(cursor, interner)?; FromClause::new("export declaration").parse(cursor, interner)?;
cursor.expect_semicolon("named re-exports", interner)?;
AstExportDeclaration::ReExport { AstExportDeclaration::ReExport {
kind: ReExportKind::Named { names }, kind: ReExportKind::Named { names },
specifier, specifier,
} }
} else { } else {
cursor.expect_semicolon("named exports", interner)?;
AstExportDeclaration::List(names) AstExportDeclaration::List(names)
} }
} }
@ -165,10 +175,14 @@ where
ClassDeclaration::new(false, true, true).parse(cursor, interner)?, ClassDeclaration::new(false, true, true).parse(cursor, interner)?,
) )
} }
_ => AstExportDeclaration::DefaultAssignmentExpression( _ => {
AssignmentExpression::new(None, true, false, true) let expr = AssignmentExpression::new(None, true, false, true)
.parse(cursor, interner)?, .parse(cursor, interner)?;
),
cursor.expect_semicolon("default expression export", interner)?;
AstExportDeclaration::DefaultAssignmentExpression(expr)
}
} }
} }
_ => AstExportDeclaration::Declaration( _ => AstExportDeclaration::Declaration(
@ -222,7 +236,9 @@ where
} }
cursor.advance(interner); cursor.advance(interner);
} }
TokenKind::StringLiteral(_) | TokenKind::IdentifierName(_) => { TokenKind::StringLiteral(_)
| TokenKind::IdentifierName(_)
| TokenKind::Keyword(_) => {
list.push(ExportSpecifier.parse(cursor, interner)?); list.push(ExportSpecifier.parse(cursor, interner)?);
} }
_ => { _ => {
@ -272,6 +288,7 @@ where
Ok(*ident) Ok(*ident)
} }
TokenKind::IdentifierName((ident, _)) => Ok(*ident), TokenKind::IdentifierName((ident, _)) => Ok(*ident),
TokenKind::Keyword((kw, _)) => Ok(kw.to_sym()),
_ => Err(Error::expected( _ => Err(Error::expected(
["identifier".to_owned(), "string literal".to_owned()], ["identifier".to_owned(), "string literal".to_owned()],
tok.to_string(interner), tok.to_string(interner),

10
boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs

@ -1,7 +1,7 @@
use crate::parser::tests::check_script_parser; use crate::parser::tests::check_script_parser;
use boa_ast::{ use boa_ast::{
function::{AsyncFunction, FormalParameterList}, function::{AsyncFunction, FormalParameterList, FunctionBody},
Declaration, StatementList, Declaration,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
use boa_macros::utf16; use boa_macros::utf16;
@ -19,7 +19,7 @@ fn async_function_declaration() {
.into(), .into(),
), ),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
false, false,
)) ))
.into()], .into()],
@ -36,7 +36,7 @@ fn async_function_declaration_keywords() {
vec![Declaration::AsyncFunction(AsyncFunction::new( vec![Declaration::AsyncFunction(AsyncFunction::new(
Some(Sym::YIELD.into()), Some(Sym::YIELD.into()),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
false, false,
)) ))
.into()], .into()],
@ -49,7 +49,7 @@ fn async_function_declaration_keywords() {
vec![Declaration::AsyncFunction(AsyncFunction::new( vec![Declaration::AsyncFunction(AsyncFunction::new(
Some(Sym::AWAIT.into()), Some(Sym::AWAIT.into()),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
false, false,
)) ))
.into()], .into()],

6
boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/tests.rs

@ -1,7 +1,7 @@
use crate::parser::tests::check_script_parser; use crate::parser::tests::check_script_parser;
use boa_ast::{ use boa_ast::{
function::{AsyncGenerator, FormalParameterList}, function::{AsyncGenerator, FormalParameterList, FunctionBody},
Declaration, StatementList, Declaration,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use boa_macros::utf16; use boa_macros::utf16;
@ -14,7 +14,7 @@ fn async_generator_function_declaration() {
vec![Declaration::AsyncGenerator(AsyncGenerator::new( vec![Declaration::AsyncGenerator(AsyncGenerator::new(
Some(interner.get_or_intern_static("gen", utf16!("gen")).into()), Some(interner.get_or_intern_static("gen", utf16!("gen")).into()),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
false, false,
)) ))
.into()], .into()],

6
boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs

@ -696,7 +696,9 @@ where
cursor.set_strict(strict); cursor.set_strict(strict);
statement_list statement_list
}; };
function::ClassElement::StaticBlock(statement_list) function::ClassElement::StaticBlock(ast::function::FunctionBody::new(
statement_list,
))
} }
TokenKind::Punctuator(Punctuator::Mul) => { TokenKind::Punctuator(Punctuator::Mul) => {
let token = cursor.peek(1, interner).or_abrupt()?; let token = cursor.peek(1, interner).or_abrupt()?;
@ -1346,7 +1348,7 @@ where
} }
// ClassStaticBlockBody : ClassStaticBlockStatementList // ClassStaticBlockBody : ClassStaticBlockStatementList
function::ClassElement::StaticBlock(block) => { 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. // It is a Syntax Error if ContainsArguments of ClassStaticBlockStatementList is true.
if contains_arguments(node) { if contains_arguments(node) {
return Err(Error::general( return Err(Error::general(

9
boa_parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs

@ -6,7 +6,7 @@ use boa_ast::{
literal::Literal, literal::Literal,
Call, Identifier, Call, Identifier,
}, },
function::{Class, ClassElement, FormalParameterList, Function}, function::{Class, ClassElement, FormalParameterList, Function, FunctionBody},
property::{MethodDefinition, PropertyName}, property::{MethodDefinition, PropertyName},
Declaration, Expression, Statement, StatementList, StatementListItem, Declaration, Expression, Statement, StatementList, StatementListItem,
}; };
@ -22,7 +22,7 @@ fn check_async_ordinary_method() {
MethodDefinition::Ordinary(Function::new( MethodDefinition::Ordinary(Function::new(
None, None,
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
)), )),
)]; )];
@ -120,7 +120,10 @@ fn check_new_target_with_property_access() {
let constructor = Function::new( let constructor = Function::new(
Some(interner.get_or_intern_static("A", utf16!("A")).into()), Some(interner.get_or_intern_static("A", utf16!("A")).into()),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::new([Statement::Expression(console).into()], false), FunctionBody::new(StatementList::new(
[Statement::Expression(console).into()],
false,
)),
); );
let class = Class::new( let class = Class::new(

8
boa_parser/src/parser/statement/declaration/hoistable/function_decl/tests.rs

@ -1,7 +1,7 @@
use crate::parser::tests::check_script_parser; use crate::parser::tests::check_script_parser;
use boa_ast::{ use boa_ast::{
function::{FormalParameterList, Function}, function::{FormalParameterList, Function, FunctionBody},
Declaration, StatementList, Declaration,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use boa_macros::utf16; use boa_macros::utf16;
@ -19,7 +19,7 @@ fn function_declaration() {
.into(), .into(),
), ),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
)) ))
.into()], .into()],
interner, interner,
@ -38,7 +38,7 @@ fn function_declaration_keywords() {
.into(), .into(),
), ),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
)) ))
.into()] .into()]
}; };

6
boa_parser/src/parser/statement/declaration/hoistable/generator_decl/tests.rs

@ -1,7 +1,7 @@
use crate::parser::tests::check_script_parser; use crate::parser::tests::check_script_parser;
use boa_ast::{ use boa_ast::{
function::{FormalParameterList, Generator}, function::{FormalParameterList, FunctionBody, Generator},
Declaration, StatementList, Declaration,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use boa_macros::utf16; use boa_macros::utf16;
@ -14,7 +14,7 @@ fn generator_function_declaration() {
vec![Declaration::Generator(Generator::new( vec![Declaration::Generator(Generator::new(
Some(interner.get_or_intern_static("gen", utf16!("gen")).into()), Some(interner.get_or_intern_static("gen", utf16!("gen")).into()),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), FunctionBody::default(),
false, false,
)) ))
.into()], .into()],

9
boa_parser/src/parser/statement/declaration/hoistable/mod.rs

@ -27,10 +27,11 @@ use crate::{
Error, Error,
}; };
use boa_ast::{ use boa_ast::{
self as ast,
expression::Identifier, expression::Identifier,
function::FormalParameterList, function::FormalParameterList,
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, operations::{bound_names, contains, lexically_declared_names, ContainsSymbol},
Declaration, Keyword, Punctuator, StatementList, Declaration, Keyword, Punctuator,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -148,7 +149,7 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(
c: &C, c: &C,
cursor: &mut Cursor<R>, cursor: &mut Cursor<R>,
interner: &mut Interner, interner: &mut Interner,
) -> ParseResult<(Identifier, FormalParameterList, StatementList)> { ) -> ParseResult<(Identifier, FormalParameterList, ast::function::FunctionBody)> {
let token = cursor.peek(0, interner).or_abrupt()?; let token = cursor.peek(0, interner).or_abrupt()?;
let name_span = token.span(); let name_span = token.span();
let name = match token.kind() { let name = match token.kind() {
@ -222,7 +223,7 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(
// also occurs in the LexicallyDeclaredNames of FunctionBody. // also occurs in the LexicallyDeclaredNames of FunctionBody.
name_in_lexically_declared_names( name_in_lexically_declared_names(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &lexically_declared_names(&body),
params_start_position, params_start_position,
interner, interner,
)?; )?;

33
boa_parser/src/parser/statement/declaration/import.rs

@ -38,6 +38,39 @@ use std::io::Read;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(in crate::parser) struct ImportDeclaration; pub(in crate::parser) struct ImportDeclaration;
impl ImportDeclaration {
/// Tests if the next node is an `ImportDeclaration`.
pub(in crate::parser) fn test<R: Read>(
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> ParseResult<bool> {
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<R> TokenParser<R> for ImportDeclaration impl<R> TokenParser<R> for ImportDeclaration
where where
R: Read, R: Read,

14
boa_parser/src/parser/statement/mod.rs

@ -961,12 +961,20 @@ where
let tok = cursor.peek(0, interner).or_abrupt()?; let tok = cursor.peek(0, interner).or_abrupt()?;
match tok.kind() { match tok.kind() {
TokenKind::Keyword((Keyword::Import, false)) => ImportDeclaration
.parse(cursor, interner)
.map(Self::Output::ImportDeclaration),
TokenKind::Keyword((Keyword::Export, false)) => ExportDeclaration TokenKind::Keyword((Keyword::Export, false)) => ExportDeclaration
.parse(cursor, interner) .parse(cursor, interner)
.map(Self::Output::ExportDeclaration), .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) _ => StatementListItem::new(false, true, false)
.parse(cursor, interner) .parse(cursor, interner)
.map(Self::Output::StatementListItem), .map(Self::Output::StatementListItem),

12
boa_parser/src/parser/tests/mod.rs

@ -20,10 +20,11 @@ use boa_ast::{
}, },
function::{ function::{
ArrowFunction, FormalParameter, FormalParameterList, FormalParameterListFlags, Function, ArrowFunction, FormalParameter, FormalParameterList, FormalParameterListFlags, Function,
FunctionBody,
}, },
property::PropertyDefinition, property::PropertyDefinition,
statement::{If, Return}, statement::{If, Return},
Expression, Statement, StatementList, StatementListItem, Expression, Script, Statement, StatementList, StatementListItem,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use boa_macros::utf16; use boa_macros::utf16;
@ -39,7 +40,7 @@ where
Parser::new(Source::from_bytes(js)) Parser::new(Source::from_bytes(js))
.parse_script(interner) .parse_script(interner)
.expect("failed to parse"), .expect("failed to parse"),
StatementList::from(expr.into()) Script::new(StatementList::from(expr.into()))
); );
} }
@ -126,7 +127,10 @@ fn hoisting() {
Declaration::Function(Function::new( Declaration::Function(Function::new(
Some(hello.into()), Some(hello.into()),
FormalParameterList::default(), 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(), .into(),
], ],
@ -505,7 +509,9 @@ fn spread_in_arrow_function() {
vec![Statement::Expression(Expression::from(ArrowFunction::new( vec![Statement::Expression(Expression::from(ArrowFunction::new(
None, None,
params, params,
FunctionBody::new(
vec![Statement::Expression(Expression::from(Identifier::from(b))).into()].into(), vec![Statement::Expression(Expression::from(Identifier::from(b))).into()].into(),
),
))) )))
.into()], .into()],
interner, interner,

4
boa_runtime/src/lib.rs

@ -24,7 +24,7 @@
//! let js_code = "console.log('Hello World from a JS code string!')"; //! let js_code = "console.log('Hello World from a JS code string!')";
//! //!
//! // Parse the source code //! // Parse the source code
//! match context.eval_script(Source::from_bytes(js_code)) { //! match context.eval(Source::from_bytes(js_code)) {
//! Ok(res) => { //! Ok(res) => {
//! println!( //! println!(
//! "{}", //! "{}",
@ -186,7 +186,7 @@ pub(crate) mod test {
) { ) {
#[track_caller] #[track_caller]
fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult<JsValue> { fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult<JsValue> {
context.eval_script(Source::from_bytes(source)) context.eval(Source::from_bytes(source))
} }
#[track_caller] #[track_caller]

5
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 // https://github.com/tc39/proposal-duplicate-named-capturing-groups
"regexp-duplicate-named-groups" => SpecEdition::ESNext, "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.toReversed, Array.prototype.toSorted, Array.prototype.toSpliced,
// Array.prototype.with and the equivalent TypedArray methods. // Array.prototype.with and the equivalent TypedArray methods.
// https://github.com/tc39/proposal-change-array-by-copy/ // 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.DateTimeFormat-extend-timezonename" => SpecEdition::ESNext,
"Intl.DisplayNames-v2" => SpecEdition::ESNext, "Intl.DisplayNames-v2" => SpecEdition::ESNext,
"Intl.Segmenter" => SpecEdition::ESNext, "Intl.Segmenter" => SpecEdition::ESNext,
"symbols-as-weakmap-keys" => SpecEdition::ESNext,
// Standard language features // Standard language features

12
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<JsValue> { fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
args.get(0).and_then(JsValue::as_string).map_or_else( args.get(0).and_then(JsValue::as_string).map_or_else(
|| Ok(JsValue::undefined()), || Ok(JsValue::undefined()),
|source_text| match context |source_text| context.eval(Source::from_bytes(&source_text.to_std_string_escaped())),
.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())),
},
) )
} }

40
boa_tester/src/exec/mod.rs

@ -13,12 +13,13 @@ use boa_engine::{
object::FunctionObjectBuilder, object::FunctionObjectBuilder,
optimizer::OptimizerOptions, optimizer::OptimizerOptions,
property::Attribute, property::Attribute,
script::Script,
Context, JsArgs, JsError, JsNativeErrorKind, JsValue, Source, Context, JsArgs, JsError, JsNativeErrorKind, JsValue, Source,
}; };
use colored::Colorize; use colored::Colorize;
use fxhash::FxHashSet; use fxhash::FxHashSet;
use rayon::prelude::*; use rayon::prelude::*;
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, eprintln, rc::Rc};
impl TestSuite { impl TestSuite {
/// Runs the test suite. /// Runs the test suite.
@ -264,12 +265,26 @@ impl Test {
} }
PromiseState::Fulfilled(v) => v, PromiseState::Fulfilled(v) => v,
PromiseState::Rejected(err) => { 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 { } else {
context.strict(strict); context.strict(strict);
match context.eval_script(source) { match context.eval(source) {
Ok(v) => v, Ok(v) => v,
Err(err) => return (false, format!("Uncaught {err}")), Err(err) => return (false, format!("Uncaught {err}")),
} }
@ -312,7 +327,7 @@ impl Test {
} }
} else { } else {
context.strict(strict); context.strict(strict);
match context.parse_script(source) { match Script::parse(source, None, context) {
Ok(_) => (false, "StatementList parsing should fail".to_owned()), Ok(_) => (false, "StatementList parsing should fail".to_owned()),
Err(e) => (true, format!("Uncaught {e}")), Err(e) => (true, format!("Uncaught {e}")),
} }
@ -431,6 +446,8 @@ impl Test {
let promise = module.evaluate(context); let promise = module.evaluate(context);
context.run_jobs();
match promise match promise
.state() .state()
.expect("tester can only use builtin promises") .expect("tester can only use builtin promises")
@ -443,15 +460,12 @@ impl Test {
} }
} else { } else {
context.strict(strict); context.strict(strict);
let script = match context.parse_script(source) { let script = match Script::parse(source, None, context) {
Ok(code) => code, Ok(code) => code,
Err(e) => return (false, format!("Uncaught {e}")), Err(e) => return (false, format!("Uncaught {e}")),
}; };
match context match script.evaluate(context) {
.compile_script(&script)
.and_then(|code| context.execute(code))
{
Ok(_) => return (false, "Script execution should fail".to_owned()), Ok(_) => return (false, "Script execution should fail".to_owned()),
Err(e) => e, Err(e) => e,
} }
@ -545,10 +559,10 @@ impl Test {
let sta = Source::from_reader(harness.sta.content.as_bytes(), Some(&harness.sta.path)); let sta = Source::from_reader(harness.sta.content.as_bytes(), Some(&harness.sta.path));
context context
.eval_script(assert) .eval(assert)
.map_err(|e| format!("could not run assert.js:\n{e}"))?; .map_err(|e| format!("could not run assert.js:\n{e}"))?;
context context
.eval_script(sta) .eval(sta)
.map_err(|e| format!("could not run sta.js:\n{e}"))?; .map_err(|e| format!("could not run sta.js:\n{e}"))?;
if self.flags.contains(TestFlags::ASYNC) { if self.flags.contains(TestFlags::ASYNC) {
@ -557,7 +571,7 @@ impl Test {
Some(&harness.doneprint_handle.path), Some(&harness.doneprint_handle.path),
); );
context context
.eval_script(dph) .eval(dph)
.map_err(|e| format!("could not run doneprintHandle.js:\n{e}"))?; .map_err(|e| format!("could not run doneprintHandle.js:\n{e}"))?;
} }
@ -567,7 +581,7 @@ impl Test {
.get(include_name) .get(include_name)
.ok_or_else(|| format!("could not find the {include_name} include file."))?; .ok_or_else(|| format!("could not find the {include_name} include file."))?;
let source = Source::from_reader(include.content.as_bytes(), Some(&include.path)); 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}",) format!("could not run the harness `{include_name}`:\nUncaught {e}",)
})?; })?;
} }

2
boa_wasm/src/lib.rs

@ -68,7 +68,7 @@ use wasm_bindgen::prelude::*;
pub fn evaluate(src: &str) -> Result<String, JsValue> { pub fn evaluate(src: &str) -> Result<String, JsValue> {
// Setup executor // Setup executor
Context::default() Context::default()
.eval_script(Source::from_bytes(src)) .eval(Source::from_bytes(src))
.map_err(|e| JsValue::from(format!("Uncaught {e}"))) .map_err(|e| JsValue::from(format!("Uncaught {e}")))
.map(|v| v.display().to_string()) .map(|v| v.display().to_string())
} }

4
test_ignore.toml

@ -10,11 +10,11 @@ features = [
"ShadowRealm", "ShadowRealm",
"FinalizationRegistry", "FinalizationRegistry",
"Atomics", "Atomics",
"dynamic_import",
"decorators", "decorators",
"array-grouping", "array-grouping",
"IsHTMLDDA", "IsHTMLDDA",
"legacy-regexp", "legacy-regexp",
"symbols-as-weakmap-keys",
# Non-implemented Intl features # Non-implemented Intl features
"intl-normative-optional", "intl-normative-optional",
@ -24,8 +24,6 @@ features = [
# Stage 3 proposals # Stage 3 proposals
# https://github.com/tc39/proposal-symbols-as-weakmap-keys
"symbols-as-weakmap-keys",
# https://github.com/tc39/proposal-intl-locale-info # https://github.com/tc39/proposal-intl-locale-info
"Intl.Locale-info", "Intl.Locale-info",
# https://github.com/tc39/proposal-intl-enumeration # https://github.com/tc39/proposal-intl-enumeration

Loading…
Cancel
Save