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 2 years 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. 412
      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. 101
      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. 92
      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. 135
      boa_engine/src/module/source.rs
  37. 2
      boa_engine/src/object/builtins/jspromise.rs
  38. 163
      boa_engine/src/script.rs
  39. 166
      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. 41
      boa_parser/src/parser/expression/assignment/arrow_function.rs
  55. 41
      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. 62
      boa_parser/src/parser/expression/left_hand_side/mod.rs
  59. 4
      boa_parser/src/parser/expression/primary/async_function_expression/mod.rs
  60. 58
      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. 58
      boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs
  63. 4
      boa_parser/src/parser/expression/primary/function_expression/mod.rs
  64. 66
      boa_parser/src/parser/expression/primary/function_expression/tests.rs
  65. 4
      boa_parser/src/parser/expression/primary/generator_expression/mod.rs
  66. 22
      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. 267
      boa_parser/src/parser/function/tests.rs
  71. 150
      boa_parser/src/parser/mod.rs
  72. 22
      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. 14
      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;
/// The kind of re-export in an [`ExportDeclaration`].
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ReExportKind {
/// Namespaced Re-export (`export * as name from "module-name"`).
Namespaced {
@ -76,7 +78,8 @@ impl VisitWith for ReExportKind {
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ExportDeclaration
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum ExportDeclaration {
/// Re-export.
ReExport {
@ -165,7 +168,9 @@ impl VisitWith for ExportDeclaration {
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ExportSpecifier
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub struct ExportSpecifier {
alias: Sym,
private_name: Sym,

11
boa_ast/src/declaration/import.rs

@ -21,7 +21,9 @@ use boa_interner::Sym;
use super::ModuleSpecifier;
/// The kind of import in an [`ImportDeclaration`].
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ImportKind {
/// Default (`import defaultName from "module-name"`) or unnamed (`import "module-name").
DefaultOrUnnamed,
@ -77,7 +79,8 @@ impl VisitWith for ImportKind {
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ImportDeclaration
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ImportDeclaration {
/// Binding for the default export of `specifier`.
default: Option<Identifier>,
@ -155,7 +158,9 @@ impl VisitWith for ImportDeclaration {
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ImportSpecifier
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ImportSpecifier {
binding: Identifier,
export_name: Sym,

4
boa_ast/src/declaration/mod.rs

@ -105,7 +105,9 @@ impl VisitWith for Declaration {
/// This is equivalent to the [`ModuleSpecifier`] production.
///
/// [`FromClause`]: https://tc39.es/ecma262/#prod-ModuleSpecifier
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub struct ModuleSpecifier {
module: Sym,
}

65
boa_ast/src/expression/call.rs

@ -162,3 +162,68 @@ impl VisitWith for SuperCall {
ControlFlow::Continue(())
}
}
/// The import() syntax, commonly called dynamic import, is a function-like expression that allows
/// loading an ECMAScript module asynchronously and dynamically into a potentially non-module
/// environment.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ImportCall
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ImportCall {
arg: Box<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::Set(expression)
| MethodDefinition::Ordinary(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
block_to_string(
expression.body().statements(),
interner,
indent_n + 1,
)
}
MethodDefinition::Generator(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
block_to_string(
expression.body().statements(),
interner,
indent_n + 1,
)
}
MethodDefinition::AsyncGenerator(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
block_to_string(
expression.body().statements(),
interner,
indent_n + 1,
)
}
MethodDefinition::Async(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
block_to_string(
expression.body().statements(),
interner,
indent_n + 1,
)
}
},
)

9
boa_ast/src/expression/mod.rs

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

14
boa_ast/src/function/arrow_function.rs

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

14
boa_ast/src/function/async_arrow_function.rs

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

14
boa_ast/src/function/async_function.rs

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

16
boa_ast/src/function/async_generator.rs

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

53
boa_ast/src/function/class.rs

@ -6,7 +6,7 @@ use crate::{
property::{MethodDefinition, PropertyName},
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
Declaration, StatementList, ToStringEscaped,
Declaration, ToStringEscaped,
};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
@ -121,7 +121,7 @@ impl ToIndentedString for Class {
buf.push_str(&format!(
"{indentation}constructor({}) {}\n",
join_nodes(interner, expr.parameters().as_ref()),
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
));
}
for element in self.elements.iter() {
@ -155,16 +155,16 @@ impl ToIndentedString for Class {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
},
)
@ -198,16 +198,16 @@ impl ToIndentedString for Class {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
},
)
@ -268,16 +268,16 @@ impl ToIndentedString for Class {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
},
)
@ -311,16 +311,16 @@ impl ToIndentedString for Class {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
block_to_string(expr.body().statements(), interner, indent_n + 1)
}
},
)
@ -355,10 +355,10 @@ impl ToIndentedString for Class {
)
}
},
ClassElement::StaticBlock(statement_list) => {
ClassElement::StaticBlock(body) => {
format!(
"{indentation}static {}\n",
block_to_string(statement_list, interner, indent_n + 1)
block_to_string(body.statements(), interner, indent_n + 1)
)
}
});
@ -420,6 +420,13 @@ impl VisitWith for Class {
}
}
/// The body of a class' static block, as defined by the [spec].
///
/// Just an alias for [`Script`](crate::Script), since it has the same exact semantics.
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassStaticBlockBody
type StaticBlockBody = crate::Script;
/// An element that can be within a [`Class`], as defined by the [spec].
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassElement
@ -454,7 +461,7 @@ pub enum ClassElement {
PrivateStaticFieldDefinition(PrivateName, Option<Expression>),
/// A static block, where a class can have initialization logic for its static fields.
StaticBlock(StatementList),
StaticBlock(StaticBlockBody),
}
impl VisitWith for ClassElement {
@ -489,7 +496,7 @@ impl VisitWith for ClassElement {
ControlFlow::Continue(())
}
}
Self::StaticBlock(sl) => visitor.visit_statement_list(sl),
Self::StaticBlock(sl) => visitor.visit_script(sl),
}
}
@ -524,7 +531,7 @@ impl VisitWith for ClassElement {
ControlFlow::Continue(())
}
}
Self::StaticBlock(sl) => visitor.visit_statement_list_mut(sl),
Self::StaticBlock(sl) => visitor.visit_script_mut(sl),
}
}
}

16
boa_ast/src/function/generator.rs

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

29
boa_ast/src/function/mod.rs

@ -38,9 +38,9 @@ use core::ops::ControlFlow;
pub use generator::Generator;
pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{block_to_string, join_nodes, StatementList};
use crate::{block_to_string, join_nodes};
use crate::{try_break, Script};
use boa_interner::{Interner, ToIndentedString};
use super::expression::{Expression, Identifier};
@ -62,7 +62,7 @@ use super::Declaration;
pub struct Function {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
body: FunctionBody,
has_binding_identifier: bool,
}
@ -73,7 +73,7 @@ impl Function {
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
body: FunctionBody,
) -> Self {
Self {
name,
@ -89,7 +89,7 @@ impl Function {
pub const fn new_with_binding_identifier(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
body: FunctionBody,
has_binding_identifier: bool,
) -> Self {
Self {
@ -117,7 +117,7 @@ impl Function {
/// Gets the body of the function declaration.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
pub const fn body(&self) -> &FunctionBody {
&self.body
}
@ -138,7 +138,7 @@ impl ToIndentedString for Function {
buf.push_str(&format!(
"({}) {}",
join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation)
block_to_string(self.body.statements(), interner, indentation)
));
buf
@ -168,7 +168,7 @@ impl VisitWith for Function {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
visitor.visit_script(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -179,6 +179,17 @@ impl VisitWith for Function {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
visitor.visit_script_mut(&mut self.body)
}
}
/// A Function body.
///
/// Since [`Script`] and `FunctionBody` has the same semantics, this is currently
/// only an alias of the former.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-FunctionBody
pub type FunctionBody = Script;

4
boa_ast/src/lib.rs

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

6
boa_ast/src/module_item_list/mod.rs

@ -31,7 +31,8 @@ use crate::{
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ModuleItemList
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ModuleItemList {
items: Box<[ModuleItem]>,
}
@ -462,7 +463,8 @@ impl VisitWith for ModuleItemList {
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ModuleItem
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum ModuleItem {
/// See [`ImportDeclaration`].
ImportDeclaration(ImportDeclaration),

412
boa_ast/src/operations.rs

@ -9,7 +9,9 @@ use boa_interner::{Interner, Sym};
use rustc_hash::FxHashSet;
use crate::{
declaration::{Binding, ExportDeclaration, ImportDeclaration, VarDeclaration, Variable},
declaration::{
Binding, ExportDeclaration, ImportDeclaration, LexicalDeclaration, VarDeclaration, Variable,
},
expression::{
access::{PrivatePropertyAccess, SuperPropertyAccess},
operator::BinaryInPrivate,
@ -26,7 +28,7 @@ use crate::{
},
try_break,
visitor::{NodeRef, VisitWith, Visitor},
Declaration, Expression, ModuleItem, Statement, StatementList, StatementListItem,
Declaration, Expression, ModuleItem, Script, Statement, StatementList, StatementListItem,
};
/// Represents all the possible symbols searched for by the [`Contains`][contains] operation.
@ -451,63 +453,101 @@ struct LexicallyDeclaredNamesVisitor<'a, T: IdentList>(&'a mut T);
impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T> {
type BreakTy = Infallible;
fn visit_script(&mut self, node: &'ast Script) -> ControlFlow<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> {
ControlFlow::Continue(())
}
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
if let Statement::Labelled(labelled) = node {
return self.visit_labelled(labelled);
}
ControlFlow::Continue(())
}
fn visit_declaration(&mut self, node: &'ast Declaration) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_declaration(node)
}
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
LabelledItem::Function(f) => BoundNamesVisitor(self.0).visit_function(f),
LabelledItem::Statement(_) => ControlFlow::Continue(()),
}
}
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
self.visit_script(node.body())
}
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
self.visit_script(node.body())
}
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
self.visit_script(node.body())
}
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
self.visit_script(node.body())
}
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
self.visit_script(node.body())
}
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
self.visit_script(node.body())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(stmts) = node {
top_level_lexicals(stmts, self.0);
if let ClassElement::StaticBlock(body) = node {
self.visit_script(body);
}
ControlFlow::Continue(())
}
fn visit_import_declaration(
&mut self,
node: &'ast ImportDeclaration,
) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_import_declaration(node)
}
fn visit_export_declaration(
&mut self,
node: &'ast ExportDeclaration,
@ -517,10 +557,6 @@ impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T>
}
BoundNamesVisitor(self.0).visit_export_declaration(node)
}
// TODO: ScriptBody : StatementList
// 1. Return TopLevelLexicallyDeclaredNames of StatementList.
// But we don't have that node yet. In the meantime, use `top_level_lexically_declared_names` directly.
}
/// Returns a list with the lexical bindings of a node, which may contain duplicates.
@ -562,6 +598,34 @@ struct VarDeclaredNamesVisitor<'a>(&'a mut FxHashSet<Identifier>);
impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
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> {
match node {
Statement::Empty
@ -682,28 +746,24 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
}
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
self.visit_script(node.body())
}
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
self.visit_script(node.body())
}
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
self.visit_script(node.body())
}
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
self.visit_script(node.body())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(stmts) = node {
top_level_vars(stmts, self.0);
if let ClassElement::StaticBlock(body) = node {
self.visit_script(body);
}
node.visit_with(self)
}
@ -726,10 +786,6 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
_ => ControlFlow::Continue(()),
}
}
// TODO: ScriptBody : StatementList
// 1. Return TopLevelVarDeclaredNames of StatementList.
// But we don't have that node yet. In the meantime, use `top_level_var_declared_names` directly.
}
/// Returns a set with the var bindings of a node, with no duplicates.
@ -748,6 +804,10 @@ where
}
/// Utility function that collects the top level lexicals of a statement list into `names`.
///
/// This is equivalent to the [`TopLevelLexicallyDeclaredNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallydeclarednames
fn top_level_lexicals<T: IdentList>(stmts: &StatementList, names: &mut T) {
for stmt in stmts.statements() {
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
#[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`.
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvardeclarednames
fn top_level_vars(stmts: &StatementList, names: &mut FxHashSet<Identifier>) {
for stmt in stmts.statements() {
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.
///
/// This is equivalent to the [`AllPrivateIdentifiersValid`][spec] syntax operation in the spec.
@ -1319,13 +1357,69 @@ where
node.visit_with(&mut visitor).is_break()
}
/// The type of a lexically scoped declaration.
#[derive(Clone, Debug)]
pub enum LexicallyScopedDeclaration {
/// See [`LexicalDeclaration`]
LexicalDeclaration(LexicalDeclaration),
/// See [`Function`]
Function(Function),
/// See [`Generator`]
Generator(Generator),
/// See [`AsyncFunction`]
AsyncFunction(AsyncFunction),
/// See [`AsyncGenerator`]
AsyncGenerator(AsyncGenerator),
/// See [`Class`]
Class(Class),
/// A default assignment expression as an export declaration.
///
/// Only valid inside module exports.
AssignmentExpression(Expression),
}
impl LexicallyScopedDeclaration {
/// Return the bound names of the declaration.
#[must_use]
pub fn bound_names(&self) -> Vec<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.
///
/// This is equivalent to the [`LexicallyScopedDeclarations`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallyscopeddeclarations
#[must_use]
pub fn lexically_scoped_declarations<'a, N>(node: &'a N) -> Vec<Declaration>
pub fn lexically_scoped_declarations<'a, N>(node: &'a N) -> Vec<LexicallyScopedDeclaration>
where
&'a N: Into<NodeRef<'a>>,
{
@ -1336,22 +1430,91 @@ where
/// The [`Visitor`] used to obtain the lexically scoped declarations of a node.
#[derive(Debug)]
struct LexicallyScopedDeclarationsVisitor<'a>(&'a mut Vec<Declaration>);
struct LexicallyScopedDeclarationsVisitor<'a>(&'a mut Vec<LexicallyScopedDeclaration>);
impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_> {
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(
&mut self,
node: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> {
match node {
// StatementListItem : Statement
StatementListItem::Statement(Statement::Labelled(labelled)) => {
// 1. If Statement is Statement : LabelledStatement , return LexicallyScopedDeclarations of LabelledStatement.
self.visit_labelled(labelled)
}
StatementListItem::Statement(_) => ControlFlow::Continue(()),
StatementListItem::Statement(_) => {
// 2. Return a new empty List.
ControlFlow::Continue(())
}
// StatementListItem : Declaration
StatementListItem::Declaration(declaration) => {
self.0.push(declaration.clone());
// 1. Return a List whose sole element is DeclarationPart of Declaration.
self.0.push(declaration.clone().into());
ControlFlow::Continue(())
}
}
@ -1359,45 +1522,76 @@ impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_> {
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
// LabelledItem : FunctionDeclaration
LabelledItem::Function(f) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
// 1. Return « FunctionDeclaration ».
self.0.push(LexicallyScopedDeclaration::Function(f.clone()));
}
// LabelledItem : Statement
LabelledItem::Statement(_) => {
// 1. Return a new empty List.
}
LabelledItem::Statement(_) => ControlFlow::Continue(()),
}
ControlFlow::Continue(())
}
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
match node {
ModuleItem::StatementListItem(item) => self.visit_statement_list_item(item),
ModuleItem::ExportDeclaration(ExportDeclaration::Declaration(declaration)) => {
self.0.push(declaration.clone());
ControlFlow::Continue(())
}
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultFunction(f)) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
}
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultGenerator(f)) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
}
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultAsyncFunction(f)) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
}
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultAsyncGenerator(f)) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
}
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultClassDeclaration(f)) => {
self.0.push(f.clone().into());
ModuleItem::ExportDeclaration(export) => self.visit_export_declaration(export),
// ModuleItem : ImportDeclaration
ModuleItem::ImportDeclaration(_) => {
// 1. Return a new empty List.
ControlFlow::Continue(())
}
ModuleItem::ImportDeclaration(_) | ModuleItem::ExportDeclaration(_) => {
ControlFlow::Continue(())
}
}
}
/// The [`Visitor`] used to obtain the top level lexically scoped declarations of a node.
///
/// This is equivalent to the [`TopLevelLexicallyScopedDeclarations`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallyscopeddeclarations
#[derive(Debug)]
struct TopLevelLexicallyScopedDeclarationsVisitor<'a>(&'a mut Vec<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.
}
// 2. Return « Declaration ».
Declaration::Class(cl) => {
self.0.push(LexicallyScopedDeclaration::Class(cl.clone()));
}
Declaration::Lexical(lex) => {
self.0
.push(LexicallyScopedDeclaration::LexicalDeclaration(lex.clone()));
}
},
// StatementListItem : Statement
StatementListItem::Statement(_) => {
// 1. Return a new empty List.
}
}
ControlFlow::Continue(())
}
}
@ -1456,6 +1650,12 @@ struct VarScopedDeclarationsVisitor<'a>(&'a mut Vec<VarScopedDeclaration>);
impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> {
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> {
match node {
Statement::Block(s) => self.visit(s),
@ -1590,29 +1790,31 @@ impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> {
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
match node {
ModuleItem::ExportDeclaration(ExportDeclaration::VarStatement(var)) => self.visit(var),
ModuleItem::StatementListItem(item) => self.visit(item),
_ => ControlFlow::Continue(()),
// ModuleItem : ExportDeclaration
ModuleItem::ExportDeclaration(decl) => {
if let ExportDeclaration::VarStatement(var) = decl {
// 1. If ExportDeclaration is export VariableStatement, return VarScopedDeclarations of VariableStatement.
self.visit_var_declaration(var);
}
// 2. Return a new empty List.
}
ModuleItem::StatementListItem(item) => {
self.visit_statement_list_item(item);
}
// ModuleItem : ImportDeclaration
ModuleItem::ImportDeclaration(_) => {
// 1. Return a new empty List.
}
}
ControlFlow::Continue(())
}
}
/// Returns a list of top level var scoped declarations of the given node.
/// The [`Visitor`] used to obtain the top level var scoped declarations of a node.
///
/// This is equivalent to the [`TopLevelVarScopedDeclarations`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvarscopeddeclarations
#[must_use]
pub fn top_level_var_scoped_declarations<'a, N>(node: &'a N) -> Vec<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)]
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 core::ops::ControlFlow;
use std::ops::Deref;
/// An item inside a [`StatementList`] Parse Node, as defined by the [spec].
///
@ -149,6 +150,14 @@ impl From<Vec<StatementListItem>> for StatementList {
}
}
impl Deref for StatementList {
type Target = [StatementListItem];
fn deref(&self) -> &Self::Target {
&self.statements
}
}
impl ToIndentedString for StatementList {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = String::new();

19
boa_ast/src/visitor.rs

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

101
boa_cli/src/main.rs

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

32
boa_engine/benches/full.rs

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

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
use crate::{
builtins::BuiltInObject, bytecompiler::ByteCompiler, context::intrinsics::Intrinsics,
environments::Environment, error::JsNativeError, object::JsObject, realm::Realm, vm::Opcode,
builtins::BuiltInObject,
bytecompiler::ByteCompiler,
context::intrinsics::Intrinsics,
environments::Environment,
error::JsNativeError,
object::JsObject,
realm::Realm,
vm::{CallFrame, Opcode},
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_ast::operations::{contains, contains_arguments, ContainsSymbol};
@ -229,7 +235,7 @@ impl Eval {
let push_env = compiler.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
compiler.eval_declaration_instantiation(&body, strict)?;
compiler.compile_statement_list(&body, true, false);
compiler.compile_statement_list(body.statements(), true, false);
let env_info = compiler.pop_compile_environment();
compiler.patch_jump_with_target(push_env.0, env_info.num_bindings);
@ -246,6 +252,11 @@ impl Eval {
context.vm.environments.extend_outer_function_environment();
}
context.execute(code_block)
context.vm.push_frame(CallFrame::new(code_block));
context.realm().resize_global_env();
let record = context.run();
context.vm.pop_frame();
record.consume()
}
}

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

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

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

@ -29,6 +29,7 @@ use crate::{
string::{utf16, CodePoint},
symbol::JsSymbol,
value::IntegerOrInfinity,
vm::CallFrame,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_gc::Gc;
@ -114,19 +115,25 @@ impl Json {
// 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral.
let mut parser = Parser::new(Source::from_bytes(&script_string));
parser.set_json_parse();
let statement_list = parser.parse_script(context.interner_mut())?;
let script = parser.parse_script(context.interner_mut())?;
let code_block = {
let mut compiler = ByteCompiler::new(
Sym::MAIN,
statement_list.strict(),
script.strict(),
true,
context.realm().environment().compile_env(),
context,
);
compiler.compile_statement_list(&statement_list, true, false);
compiler.compile_statement_list(script.statements(), true, false);
Gc::new(compiler.finish())
};
let unfiltered = context.execute(code_block)?;
context.vm.push_frame(CallFrame::new(code_block));
context.realm().resize_global_env();
let record = context.run();
context.vm.pop_frame();
let unfiltered = record.consume()?;
// 11. If IsCallable(reviver) is true, then
if let Some(obj) = args.get_or_undefined(1).as_callable() {

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 }.
NativeJob::with_realm(job, realm)
NativeJob::with_realm(job, realm, context)
}
/// More information:
@ -2387,5 +2387,5 @@ fn new_promise_resolve_thenable_job(
};
// 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }.
NativeJob::with_realm(job, realm)
NativeJob::with_realm(job, realm, context)
}

8
boa_engine/src/bytecompiler/class.rs

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

60
boa_engine/src/bytecompiler/declarations.rs

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

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

@ -320,6 +320,13 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::Pop);
}
}
Expression::ImportCall(import) => {
self.compile_expr(import.argument(), true);
self.emit_opcode(Opcode::ImportCall);
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}
Expression::NewTarget => {
if use_expr {
self.emit_opcode(Opcode::PushNewTarget);

6
boa_engine/src/bytecompiler/function.rs

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

4
boa_engine/src/bytecompiler/mod.rs

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

92
boa_engine/src/context/mod.rs

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

39
boa_engine/src/job.rs

@ -22,6 +22,7 @@ use std::{any::Any, cell::RefCell, collections::VecDeque, fmt::Debug, future::Fu
use crate::{
object::{JsFunction, NativeObject},
realm::Realm,
vm::ActiveRunnable,
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
@ -68,6 +69,7 @@ pub struct NativeJob {
#[allow(clippy::type_complexity)]
f: Box<dyn FnOnce(&mut Context<'_>) -> JsResult<JsValue>>,
realm: Option<Realm>,
active_runnable: Option<ActiveRunnable>,
}
impl Debug for NativeJob {
@ -85,17 +87,19 @@ impl NativeJob {
Self {
f: Box::new(f),
realm: None,
active_runnable: None,
}
}
/// Creates a new `NativeJob` from a closure and an execution realm.
pub fn with_realm<F>(f: F, realm: Realm) -> Self
pub fn with_realm<F>(f: F, realm: Realm, context: &mut Context<'_>) -> Self
where
F: FnOnce(&mut Context<'_>) -> JsResult<JsValue> + 'static,
{
Self {
f: Box::new(f),
realm: Some(realm),
active_runnable: context.vm.active_runnable.clone(),
}
}
@ -110,11 +114,24 @@ impl NativeJob {
///
/// If the native job has an execution realm defined, this sets the running execution
/// context to the realm's before calling the inner closure, and resets it after execution.
pub fn call(self, context: &mut Context<'_>) -> JsResult<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 {
let old_realm = context.enter_realm(realm);
// Let scriptOrModule be GetActiveScriptOrModule() at the time HostEnqueuePromiseJob is
// invoked. If realm is not null, each time job is invoked the implementation must
// perform implementation-defined steps such that scriptOrModule is the active script or
// module at the time of job's invocation.
std::mem::swap(&mut context.vm.active_runnable, &mut self.active_runnable);
let result = (self.f)(context);
context.enter_realm(old_realm);
std::mem::swap(&mut context.vm.active_runnable, &mut self.active_runnable);
result
} else {
(self.f)(context)
@ -173,8 +190,22 @@ impl JobCallback {
pub trait JobQueue {
/// [`HostEnqueuePromiseJob ( job, realm )`][spec].
///
/// Enqueues a [`NativeJob`] on the job queue. Note that host-defined [Jobs] need to satisfy
/// a set of requirements for them to be spec-compliant.
/// Enqueues a [`NativeJob`] on the job queue.
///
/// # Requirements
///
/// Per the [spec]:
/// > An implementation of `HostEnqueuePromiseJob` must conform to the requirements in [9.5][Jobs] as well as the
/// following:
/// > - If `realm` is not null, each time `job` is invoked the implementation must perform implementation-defined steps
/// such that execution is prepared to evaluate ECMAScript code at the time of job's invocation.
/// > - Let `scriptOrModule` be `GetActiveScriptOrModule()` at the time `HostEnqueuePromiseJob` is invoked. If realm
/// is not null, each time job is invoked the implementation must perform implementation-defined steps such that
/// `scriptOrModule` is the active script or module at the time of job's invocation.
/// > - Jobs must run in the same order as the `HostEnqueuePromiseJob` invocations that scheduled them.
///
/// Of all the requirements, Boa guarantees the first two by its internal implementation of `NativeJob`, meaning
/// the implementer must only guarantee that jobs are run in the same order as they're enqueued.
///
/// [spec]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob
/// [Jobs]: https://tc39.es/ecma262/#sec-jobs

7
boa_engine/src/lib.rs

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

19
boa_engine/src/module/mod.rs

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

135
boa_engine/src/module/source.rs

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

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()
}
}

166
boa_engine/src/vm/code_block.rs

@ -511,6 +511,7 @@ impl CodeBlock {
| Opcode::IsObject
| Opcode::SetNameByLocator
| Opcode::PopPrivateEnvironment
| Opcode::ImportCall
| Opcode::Nop => String::new(),
}
}
@ -612,6 +613,8 @@ pub(crate) fn create_function_object(
let length: JsValue = code.length.into();
let script_or_module = context.vm.active_runnable.clone();
let function = if r#async {
Function::new(
FunctionKind::Async {
@ -619,6 +622,7 @@ pub(crate) fn create_function_object(
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
},
context.realm().clone(),
)
@ -632,6 +636,7 @@ pub(crate) fn create_function_object(
fields: ThinVec::new(),
private_methods: ThinVec::new(),
class_object: None,
script_or_module,
},
context.realm().clone(),
)
@ -693,12 +698,15 @@ pub(crate) fn create_function_object_fast(
let length: JsValue = code.length.into();
let script_or_module = context.vm.active_runnable.clone();
let function = if r#async {
FunctionKind::Async {
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
}
} else {
FunctionKind::Ordinary {
@ -709,6 +717,7 @@ pub(crate) fn create_function_object_fast(
fields: ThinVec::new(),
private_methods: ThinVec::new(),
class_object: None,
script_or_module,
}
};
@ -799,6 +808,8 @@ pub(crate) fn create_generator_function_object(
ObjectData::ordinary(),
);
let script_or_module = context.vm.active_runnable.clone();
let constructor = if r#async {
let function = Function::new(
FunctionKind::AsyncGenerator {
@ -806,6 +817,7 @@ pub(crate) fn create_generator_function_object(
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
},
context.realm().clone(),
);
@ -821,6 +833,7 @@ pub(crate) fn create_generator_function_object(
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
},
context.realm().clone(),
);
@ -875,80 +888,91 @@ impl JsObject {
context.enter_realm(realm);
context.vm.active_function = Some(active_function);
let (code, mut environments, class_object, async_, gen) = match function_object.kind() {
FunctionKind::Native {
function,
constructor,
} => {
let function = function.clone();
let constructor = *constructor;
drop(object);
return if constructor.is_some() {
function.call(&JsValue::undefined(), args, context)
} else {
function.call(this, args, context)
let (code, mut environments, class_object, mut script_or_module, async_, gen) =
match function_object.kind() {
FunctionKind::Native {
function,
constructor,
} => {
let function = function.clone();
let constructor = *constructor;
drop(object);
return if constructor.is_some() {
function.call(&JsValue::undefined(), args, context)
} else {
function.call(this, args, context)
}
.map_err(|err| err.inject_realm(context.realm().clone()));
}
.map_err(|err| err.inject_realm(context.realm().clone()));
}
FunctionKind::Ordinary {
code,
environments,
class_object,
..
} => {
let code = code.clone();
if code.is_class_constructor {
return Err(JsNativeError::typ()
.with_message("class constructor cannot be invoked without 'new'")
.with_realm(context.realm().clone())
.into());
FunctionKind::Ordinary {
code,
environments,
class_object,
script_or_module,
..
} => {
let code = code.clone();
if code.is_class_constructor {
return Err(JsNativeError::typ()
.with_message("class constructor cannot be invoked without 'new'")
.with_realm(context.realm().clone())
.into());
}
(
code,
environments.clone(),
class_object.clone(),
script_or_module.clone(),
false,
false,
)
}
(
FunctionKind::Async {
code,
environments,
class_object,
script_or_module,
..
} => (
code.clone(),
environments.clone(),
class_object.clone(),
script_or_module.clone(),
true,
false,
),
FunctionKind::Generator {
code,
environments,
class_object,
script_or_module,
..
} => (
code.clone(),
environments.clone(),
class_object.clone(),
script_or_module.clone(),
false,
)
}
FunctionKind::Async {
code,
environments,
class_object,
..
} => (
code.clone(),
environments.clone(),
class_object.clone(),
true,
false,
),
FunctionKind::Generator {
code,
environments,
class_object,
..
} => (
code.clone(),
environments.clone(),
class_object.clone(),
false,
true,
),
FunctionKind::AsyncGenerator {
code,
environments,
class_object,
..
} => (
code.clone(),
environments.clone(),
class_object.clone(),
true,
true,
),
};
true,
),
FunctionKind::AsyncGenerator {
code,
environments,
class_object,
script_or_module,
..
} => (
code.clone(),
environments.clone(),
class_object.clone(),
script_or_module.clone(),
true,
true,
),
};
drop(object);
@ -1063,6 +1087,8 @@ impl JsObject {
.with_arg_count(arg_count);
frame.promise_capability = promise_capability.clone();
std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module);
context.vm.push_frame(frame);
let result = context
@ -1073,6 +1099,7 @@ impl JsObject {
let call_frame = context.vm.pop_frame().expect("frame must exist");
std::mem::swap(&mut environments, &mut context.vm.environments);
std::mem::swap(&mut context.vm.stack, &mut stack);
std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module);
if let Some(promise_capability) = promise_capability {
Ok(promise_capability.promise().clone().into())
@ -1208,10 +1235,12 @@ impl JsObject {
code,
environments,
constructor_kind,
script_or_module,
..
} => {
let code = code.clone();
let mut environments = environments.clone();
let mut script_or_module = script_or_module.clone();
let constructor_kind = *constructor_kind;
drop(object);
@ -1317,6 +1346,8 @@ impl JsObject {
let param_count = code.params.as_ref().len();
let has_binding_identifier = code.has_binding_identifier;
std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module);
context.vm.push_frame(
CallFrame::new(code)
.with_param_count(param_count)
@ -1328,6 +1359,7 @@ impl JsObject {
context.vm.pop_frame();
std::mem::swap(&mut environments, &mut context.vm.environments);
std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module);
let environment = if has_binding_identifier {
environments.truncate(environments_len + 2);

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

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

23
boa_engine/src/vm/mod.rs

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

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

@ -1,8 +1,10 @@
use crate::{
builtins::function::FunctionKind,
builtins::{function::FunctionKind, promise::PromiseCapability, Promise},
error::JsNativeError,
module::{ModuleKind, Referrer},
object::FunctionObjectBuilder,
vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue,
Context, JsResult, JsValue, NativeFunction,
};
/// `CallEval` implements the Opcode Operation for `Opcode::CallEval`
@ -250,3 +252,220 @@ impl Operation for CallSpread {
Ok(CompletionType::Normal)
}
}
/// `ImportCall` implements the Opcode Operation for `Opcode::ImportCall`
///
/// Operation:
/// - Dynamically imports a module
#[derive(Debug, Clone, Copy)]
pub(crate) struct ImportCall;
impl Operation for ImportCall {
const NAME: &'static str = "ImportCall";
const INSTRUCTION: &'static str = "INST - ImportCall";
fn execute(context: &mut Context<'_>) -> JsResult<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 **=>**
SuperCallDerived,
/// Dynamically import a module.
///
/// Operands:
///
/// Stack: specifier **=>** promise
ImportCall,
/// Pop the two values of the stack, strict equal compares the two values,
/// if true jumps to address, otherwise push the second pop'ed value.
///

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`,
// and access the `Person` class defined in Rust!
context
.eval_script(Source::from_bytes(
.eval(Source::from_bytes(
r"
let person = new Person('John', 19);
person.sayHello();

13
boa_examples/src/bin/closures.rs

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

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());
let mut ctx = Context::default();
let mut statements = parser.parse_script(ctx.interner_mut()).unwrap();
let mut script = parser.parse_script(ctx.interner_mut()).unwrap();
let mut visitor = CommutorVisitor::default();
assert!(matches!(
visitor.visit_statement_list_mut(&mut statements),
visitor.visit_statement_list_mut(script.statements_mut()),
ControlFlow::Continue(_)
));
println!("{}", statements.to_interned_string(ctx.interner()));
println!("{}", script.to_interned_string(ctx.interner()));
}

2
boa_examples/src/bin/derive.rs

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

2
boa_examples/src/bin/futures.rs

@ -166,7 +166,7 @@ fn main() {
"#;
let now = Instant::now();
context.eval_script(Source::from_bytes(script)).unwrap();
context.eval(Source::from_bytes(script)).unwrap();
// Important to run this after evaluating, since this is what triggers to run the enqueued jobs.
context.run_jobs();

2
boa_examples/src/bin/loadfile.rs

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

2
boa_examples/src/bin/loadstring.rs

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

6
boa_examples/src/bin/modulehandler.rs

@ -33,8 +33,7 @@ fn main() {
// Instantiating the engine with the execution context
// Loading, parsing and executing the JS code from the source file
ctx.eval_script(Source::from_bytes(&buffer.unwrap()))
.unwrap();
ctx.eval(Source::from_bytes(&buffer.unwrap())).unwrap();
}
// Custom implementation that mimics the 'require' module loader
@ -55,8 +54,7 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult<JsV
Ok(JsValue::Rational(-1.0))
} else {
// Load and parse the module source
ctx.eval_script(Source::from_bytes(&buffer.unwrap()))
.unwrap();
ctx.eval(Source::from_bytes(&buffer.unwrap())).unwrap();
// Access module.exports and return as ResultValue
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);
// 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"
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.
//
// 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"
try {
for (let i = 0; i < 11; ++i) { }
@ -34,7 +34,7 @@ fn main() {
assert!(result.is_err());
// Preventing an infinity loops
let result = context.eval_script(Source::from_bytes(
let result = context.eval(Source::from_bytes(
r"
while (true) { }
",
@ -42,7 +42,7 @@ fn main() {
assert!(result.is_err());
// The limit applies to all types of loops.
let result = context.eval_script(Source::from_bytes(
let result = context.eval(Source::from_bytes(
r"
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.
let result = context.eval_script(Source::from_bytes(
let result = context.eval(Source::from_bytes(
r"
function factorial(n) {
if (n == 0) {
@ -68,17 +68,17 @@ fn main() {
assert!(result.is_ok());
// 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)));
// Setting runtime limit for recustion to 10.
context.runtime_limits_mut().set_recursion_limit(10);
// 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)));
// 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());
}

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());
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();
assert!(matches!(
visitor.visit_statement_list(&statements),
visitor.visit_statement_list(script.statements()),
ControlFlow::Continue(_)
));

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

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

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

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

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

@ -32,7 +32,7 @@ use boa_ast::{
operator::assign::{Assign, AssignOp, AssignTarget},
Identifier,
},
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol},
operations::{bound_names, contains, lexically_declared_names, ContainsSymbol},
Expression, Keyword, Punctuator,
};
use boa_interner::Interner;
@ -225,7 +225,7 @@ where
// https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors
name_in_lexically_declared_names(
&bound_names(&parameters),
&top_level_lexically_declared_names(&body),
&lexically_declared_names(&body),
position,
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 mut lhs = if token.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) {
let lhs = if token.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) {
let args =
Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
Call::new(self.first_member_expr, args).into()
@ -86,6 +86,42 @@ where
));
};
CallExpressionTail::new(self.allow_yield, self.allow_await, lhs).parse(cursor, interner)
}
}
/// Parses the tail parts of a call expression (property access, sucessive call, array access).
#[derive(Debug)]
pub(super) struct CallExpressionTail {
allow_yield: AllowYield,
allow_await: AllowAwait,
call: ast::Expression,
}
impl CallExpressionTail {
/// Creates a new `CallExpressionTail` parser.
pub(super) fn new<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)? {
let token = tok.clone();
match token.kind() {
@ -147,6 +183,7 @@ where
_ => break,
}
}
Ok(lhs)
}
}

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

@ -19,15 +19,21 @@ mod template;
use crate::{
lexer::{InputElement, TokenKind},
parser::{
expression::left_hand_side::{
arguments::Arguments, call::CallExpression, member::MemberExpression,
optional::OptionalExpression,
expression::{
left_hand_side::{
arguments::Arguments,
call::{CallExpression, CallExpressionTail},
member::MemberExpression,
optional::OptionalExpression,
},
AssignmentExpression,
},
AllowAwait, AllowYield, Cursor, ParseResult, TokenParser,
AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser,
},
Error,
};
use boa_ast::{
expression::{Identifier, SuperCall},
expression::{Identifier, ImportCall, SuperCall},
Expression, Keyword, Punctuator,
};
use boa_interner::Interner;
@ -96,6 +102,7 @@ where
}
Ok(false)
}
let _timer = Profiler::global().start_event("LeftHandSideExpression", "Parsing");
cursor.set_goal(InputElement::TemplateTail);
@ -106,15 +113,48 @@ where
Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
SuperCall::new(args).into()
} else {
let mut member = MemberExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
if let Some(tok) = cursor.peek(0, interner)? {
if tok.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) {
member = CallExpression::new(self.allow_yield, self.allow_await, member)
let next = cursor.peek(0, interner).or_abrupt()?;
if let TokenKind::Keyword((Keyword::Import, escaped)) = next.kind() {
if *escaped {
return Err(Error::general(
"keyword `import` must not contain escaped characters",
next.span().start(),
));
}
cursor.advance(interner);
cursor.expect(
TokenKind::Punctuator(Punctuator::OpenParen),
"import call",
interner,
)?;
let arg = AssignmentExpression::new(None, true, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseParen),
"import call",
interner,
)?;
CallExpressionTail::new(
self.allow_yield,
self.allow_await,
ImportCall::new(arg).into(),
)
.parse(cursor, interner)?
} else {
let mut member =
MemberExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
if let Some(tok) = cursor.peek(0, interner)? {
if tok.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) {
member = CallExpression::new(self.allow_yield, self.allow_await, member)
.parse(cursor, interner)?;
}
}
member
}
member
};
if let Some(tok) = cursor.peek(0, interner)? {

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

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

58
boa_parser/src/parser/expression/primary/async_function_expression/tests.rs

@ -2,7 +2,7 @@ use crate::parser::tests::check_script_parser;
use boa_ast::{
declaration::{Declaration, LexicalDeclaration, Variable},
expression::literal::Literal,
function::{AsyncFunction, FormalParameterList},
function::{AsyncFunction, FormalParameterList, FunctionBody},
statement::Return,
Statement, StatementListItem,
};
@ -26,10 +26,12 @@ fn check_async_expression() {
AsyncFunction::new(
Some(add.into()),
FormalParameterList::default(),
vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())),
))]
.into(),
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())),
))]
.into(),
),
false,
)
.into(),
@ -62,28 +64,32 @@ fn check_nested_async_expression() {
AsyncFunction::new(
Some(a.into()),
FormalParameterList::default(),
vec![Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier(
b.into(),
Some(
AsyncFunction::new(
Some(b.into()),
FormalParameterList::default(),
vec![Statement::Return(Return::new(Some(
Literal::from(1).into(),
)))
.into()]
FunctionBody::new(
vec![Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier(
b.into(),
Some(
AsyncFunction::new(
Some(b.into()),
FormalParameterList::default(),
FunctionBody::new(
vec![Statement::Return(Return::new(Some(
Literal::from(1).into(),
)))
.into()]
.into(),
),
false,
)
.into(),
false,
)
.into(),
),
)]
.try_into()
.unwrap(),
))
.into()]
.into(),
),
)]
.try_into()
.unwrap(),
))
.into()]
.into(),
),
false,
)
.into(),

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

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

58
boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs

@ -2,7 +2,7 @@ use crate::parser::tests::check_script_parser;
use boa_ast::{
declaration::{LexicalDeclaration, Variable},
expression::literal::Literal,
function::{AsyncGenerator, FormalParameterList},
function::{AsyncGenerator, FormalParameterList, FunctionBody},
statement::Return,
Declaration, Statement, StatementListItem,
};
@ -27,10 +27,12 @@ fn check_async_generator_expr() {
AsyncGenerator::new(
Some(add.into()),
FormalParameterList::default(),
vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())),
))]
.into(),
FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())),
))]
.into(),
),
false,
)
.into(),
@ -63,27 +65,33 @@ fn check_nested_async_generator_expr() {
AsyncGenerator::new(
Some(a.into()),
FormalParameterList::default(),
vec![Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier(
b.into(),
Some(
AsyncGenerator::new(
Some(b.into()),
FormalParameterList::default(),
vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())),
))]
FunctionBody::new(
vec![Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier(
b.into(),
Some(
AsyncGenerator::new(
Some(b.into()),
FormalParameterList::default(),
FunctionBody::new(
vec![StatementListItem::Statement(
Statement::Return(Return::new(Some(
Literal::from(1).into(),
))),
)]
.into(),
),
false,
)
.into(),
false,
)
.into(),
),
)]
.try_into()
.unwrap(),
))
.into()]
.into(),
),
)]
.try_into()
.unwrap(),
))
.into()]
.into(),
),
false,
)
.into(),

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

150
boa_parser/src/parser/mod.rs

@ -20,18 +20,19 @@ use crate::{
};
use boa_ast::{
expression::Identifier,
function::FormalParameterList,
function::{FormalParameterList, FunctionBody},
operations::{
all_private_identifiers_valid, check_labels, contains, contains_invalid_object_literal,
lexically_declared_names, top_level_lexically_declared_names, top_level_var_declared_names,
var_declared_names, ContainsSymbol,
lexically_declared_names, var_declared_names, ContainsSymbol,
},
ModuleItemList, Position, StatementList,
Position, StatementList,
};
use boa_interner::Interner;
use rustc_hash::FxHashSet;
use std::{io::Read, path::Path};
use self::statement::ModuleItemList;
/// Trait implemented by parsers.
///
/// This makes it possible to abstract over the underlying implementation of a parser.
@ -126,15 +127,15 @@ impl<'a, R: Read> Parser<'a, R> {
}
/// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation.
/// The resulting `StatementList` can be compiled into boa bytecode and executed in the boa vm.
/// The resulting `Script` can be compiled into boa bytecode and executed in the boa vm.
///
/// # Errors
///
/// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
///
/// [spec]: https://tc39.es/ecma262/#prod-Script
pub fn parse_script(&mut self, interner: &mut Interner) -> ParseResult<StatementList> {
Script::new(false).parse(&mut self.cursor, interner)
pub fn parse_script(&mut self, interner: &mut Interner) -> ParseResult<boa_ast::Script> {
ScriptParser::new(false).parse(&mut self.cursor, interner)
}
/// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation.
@ -145,11 +146,11 @@ impl<'a, R: Read> Parser<'a, R> {
/// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
///
/// [spec]: https://tc39.es/ecma262/#prod-Module
pub fn parse_module(&mut self, interner: &mut Interner) -> ParseResult<ModuleItemList>
pub fn parse_module(&mut self, interner: &mut Interner) -> ParseResult<boa_ast::Module>
where
R: Read,
{
Module.parse(&mut self.cursor, interner)
ModuleParser.parse(&mut self.cursor, interner)
}
/// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec]
@ -165,8 +166,8 @@ impl<'a, R: Read> Parser<'a, R> {
&mut self,
direct: bool,
interner: &mut Interner,
) -> ParseResult<StatementList> {
Script::new(direct).parse(&mut self.cursor, interner)
) -> ParseResult<boa_ast::Script> {
ScriptParser::new(direct).parse(&mut self.cursor, interner)
}
/// Parses the full input as an [ECMAScript `FunctionBody`][spec] into the boa AST representation.
@ -181,7 +182,7 @@ impl<'a, R: Read> Parser<'a, R> {
interner: &mut Interner,
allow_yield: bool,
allow_await: bool,
) -> ParseResult<StatementList> {
) -> ParseResult<FunctionBody> {
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
#[derive(Debug, Clone, Copy)]
pub struct Script {
pub struct ScriptParser {
direct_eval: bool,
}
impl Script {
impl ScriptParser {
/// Create a new `Script` parser.
#[inline]
const fn new(direct_eval: bool) -> Self {
@ -247,19 +248,20 @@ impl Script {
}
}
impl<R> TokenParser<R> for Script
impl<R> TokenParser<R> for ScriptParser
where
R: Read,
{
type Output = StatementList;
type Output = boa_ast::Script;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let statement_list =
ScriptBody::new(true, cursor.strict(), self.direct_eval).parse(cursor, interner)?;
let script = boa_ast::Script::new(
ScriptBody::new(true, cursor.strict(), self.direct_eval).parse(cursor, interner)?,
);
// It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries.
let mut lexical_names = FxHashSet::default();
for name in top_level_lexically_declared_names(&statement_list) {
for name in lexically_declared_names(&script) {
if !lexical_names.insert(name) {
return Err(Error::general(
"lexical name declared multiple times",
@ -269,7 +271,7 @@ where
}
// It is a Syntax Error if any element of the LexicallyDeclaredNames of ScriptBody also occurs in the VarDeclaredNames of ScriptBody.
for name in top_level_var_declared_names(&statement_list) {
for name in var_declared_names(&script) {
if lexical_names.contains(&name) {
return Err(Error::general(
"lexical name declared multiple times",
@ -278,7 +280,7 @@ where
}
}
Ok(statement_list)
Ok(script)
}
}
@ -370,39 +372,6 @@ where
}
}
/// Helper to check if any parameter names are declared in the given list.
fn name_in_lexically_declared_names(
bound_names: &[Identifier],
lexical_names: &[Identifier],
position: Position,
interner: &Interner,
) -> ParseResult<()> {
for name in bound_names {
if lexical_names.contains(name) {
return Err(Error::general(
format!(
"formal parameter `{}` declared in lexically declared names",
interner.resolve_expect(name.sym())
),
position,
));
}
}
Ok(())
}
/// Trait to reduce boilerplate in the parser.
trait OrAbrupt<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.
///
/// More information:
@ -410,26 +379,22 @@ impl<T> OrAbrupt<T> for ParseResult<Option<T>> {
///
/// [spec]: https://tc39.es/ecma262/#prod-Module
#[derive(Debug, Clone, Copy)]
struct Module;
struct ModuleParser;
impl<R> TokenParser<R> for Module
impl<R> TokenParser<R> for ModuleParser
where
R: Read,
{
type Output = ModuleItemList;
type Output = boa_ast::Module;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
cursor.set_module();
let items = if cursor.peek(0, interner)?.is_some() {
self::statement::ModuleItemList.parse(cursor, interner)?
} else {
return Ok(Vec::new().into());
};
let module = boa_ast::Module::new(ModuleItemList.parse(cursor, interner)?);
// It is a Syntax Error if the LexicallyDeclaredNames of ModuleItemList contains any duplicate entries.
let mut bindings = FxHashSet::default();
for name in lexically_declared_names(&items) {
for name in lexically_declared_names(&module) {
if !bindings.insert(name) {
return Err(Error::general(
format!(
@ -443,7 +408,7 @@ where
// It is a Syntax Error if any element of the LexicallyDeclaredNames of ModuleItemList also occurs in the
// VarDeclaredNames of ModuleItemList.
for name in var_declared_names(&items) {
for name in var_declared_names(&module) {
if !bindings.insert(name) {
return Err(Error::general(
format!(
@ -458,7 +423,7 @@ where
// It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries.
{
let mut exported_names = FxHashSet::default();
for name in items.exported_names() {
for name in module.items().exported_names() {
if !exported_names.insert(name) {
return Err(Error::general(
format!(
@ -473,7 +438,7 @@ where
// It is a Syntax Error if any element of the ExportedBindings of ModuleItemList does not also occur in either
// the VarDeclaredNames of ModuleItemList, or the LexicallyDeclaredNames of ModuleItemList.
for name in items.exported_bindings() {
for name in module.items().exported_bindings() {
if !bindings.contains(&name) {
return Err(Error::general(
format!(
@ -486,7 +451,7 @@ where
}
// It is a Syntax Error if ModuleItemList Contains super.
if contains(&items, ContainsSymbol::Super) {
if contains(&module, ContainsSymbol::Super) {
return Err(Error::general(
"module cannot contain `super` on the top-level",
Position::new(1, 1),
@ -494,19 +459,64 @@ where
}
// It is a Syntax Error if ModuleItemList Contains NewTarget.
if contains(&items, ContainsSymbol::NewTarget) {
if contains(&module, ContainsSymbol::NewTarget) {
return Err(Error::general(
"module cannot contain `new.target` on the top-level",
Position::new(1, 1),
));
}
// TODO:
// It is a Syntax Error if ContainsDuplicateLabels of ModuleItemList with argument « » is true.
// It is a Syntax Error if ContainsUndefinedBreakTarget of ModuleItemList with argument « » is true.
// It is a Syntax Error if ContainsUndefinedContinueTarget of ModuleItemList with arguments « » and « » is true.
check_labels(&module).map_err(|error| {
Error::lex(LexError::Syntax(
error.message(interner).into(),
Position::new(1, 1),
))
})?;
// It is a Syntax Error if AllPrivateIdentifiersValid of ModuleItemList with argument « » is false.
if !all_private_identifiers_valid(&module, Vec::new()) {
return Err(Error::general(
"invalid private identifier usage",
Position::new(1, 1),
));
}
Ok(module)
}
}
Ok(items)
/// Helper to check if any parameter names are declared in the given list.
fn name_in_lexically_declared_names(
bound_names: &[Identifier],
lexical_names: &[Identifier],
position: Position,
interner: &Interner,
) -> ParseResult<()> {
for name in bound_names {
if lexical_names.contains(name) {
return Err(Error::general(
format!(
"formal parameter `{}` declared in lexically declared names",
interner.resolve_expect(name.sym())
),
position,
));
}
}
Ok(())
}
/// Trait to reduce boilerplate in the parser.
trait OrAbrupt<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)
}
}

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

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

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

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

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 boa_ast::{
function::{AsyncFunction, FormalParameterList},
Declaration, StatementList,
function::{AsyncFunction, FormalParameterList, FunctionBody},
Declaration,
};
use boa_interner::{Interner, Sym};
use boa_macros::utf16;
@ -19,7 +19,7 @@ fn async_function_declaration() {
.into(),
),
FormalParameterList::default(),
StatementList::default(),
FunctionBody::default(),
false,
))
.into()],
@ -36,7 +36,7 @@ fn async_function_declaration_keywords() {
vec![Declaration::AsyncFunction(AsyncFunction::new(
Some(Sym::YIELD.into()),
FormalParameterList::default(),
StatementList::default(),
FunctionBody::default(),
false,
))
.into()],
@ -49,7 +49,7 @@ fn async_function_declaration_keywords() {
vec![Declaration::AsyncFunction(AsyncFunction::new(
Some(Sym::AWAIT.into()),
FormalParameterList::default(),
StatementList::default(),
FunctionBody::default(),
false,
))
.into()],

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 boa_ast::{
function::{AsyncGenerator, FormalParameterList},
Declaration, StatementList,
function::{AsyncGenerator, FormalParameterList, FunctionBody},
Declaration,
};
use boa_interner::Interner;
use boa_macros::utf16;
@ -14,7 +14,7 @@ fn async_generator_function_declaration() {
vec![Declaration::AsyncGenerator(AsyncGenerator::new(
Some(interner.get_or_intern_static("gen", utf16!("gen")).into()),
FormalParameterList::default(),
StatementList::default(),
FunctionBody::default(),
false,
))
.into()],

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4
boa_runtime/src/lib.rs

@ -24,7 +24,7 @@
//! let js_code = "console.log('Hello World from a JS code string!')";
//!
//! // Parse the source code
//! match context.eval_script(Source::from_bytes(js_code)) {
//! match context.eval(Source::from_bytes(js_code)) {
//! Ok(res) => {
//! println!(
//! "{}",
@ -186,7 +186,7 @@ pub(crate) mod test {
) {
#[track_caller]
fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult<JsValue> {
context.eval_script(Source::from_bytes(source))
context.eval(Source::from_bytes(source))
}
#[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
"regexp-duplicate-named-groups" => SpecEdition::ESNext,
// Symbols as WeakMap keys
// https://github.com/tc39/proposal-symbols-as-weakmap-keys
"symbols-as-weakmap-keys" => SpecEdition::ESNext,
// Array.prototype.toReversed, Array.prototype.toSorted, Array.prototype.toSpliced,
// Array.prototype.with and the equivalent TypedArray methods.
// https://github.com/tc39/proposal-change-array-by-copy/
@ -116,6 +112,7 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! {
"Intl.DateTimeFormat-extend-timezonename" => SpecEdition::ESNext,
"Intl.DisplayNames-v2" => SpecEdition::ESNext,
"Intl.Segmenter" => SpecEdition::ESNext,
"symbols-as-weakmap-keys" => SpecEdition::ESNext,
// Standard language features

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> {
args.get(0).and_then(JsValue::as_string).map_or_else(
|| Ok(JsValue::undefined()),
|source_text| match context
.parse_script(Source::from_bytes(&source_text.to_std_string_escaped()))
{
// TODO: check strict
Err(e) => Err(JsNativeError::typ()
.with_message(format!("Uncaught Syntax Error: {e}"))
.into()),
// Calling eval here parses the code a second time.
// TODO: We can fix this after we have have defined the public api for the vm executer.
Ok(_) => context.eval_script(Source::from_bytes(&source_text.to_std_string_escaped())),
},
|source_text| context.eval(Source::from_bytes(&source_text.to_std_string_escaped())),
)
}

40
boa_tester/src/exec/mod.rs

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

2
boa_wasm/src/lib.rs

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

4
test_ignore.toml

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

Loading…
Cancel
Save