Browse Source

Module parsing (#2411)

I'm creating this draft PR, since I wanted to have some early feedback, and because I though I would have time to finish it last week, but I got caught up with other stuff. Feel free to contribute :)

The main thing here is that I have divided `eval()`, `parse()` and similar functions so that they can decide if they are parsing scripts or modules. Let me know your thoughts.

Then, I was checking the import & export parsing, and I noticed we are using `TokenKind::Identifier` for `IdentifierName`, so I changed that name. An `Identifier` is an `IdentifierName` that isn't a `ReservedWord`. This means we should probably also adapt all `IdentifierReference`, `BindingIdentifier` and so on parsing. I already created an `Identifier` parser.

Something interesting there is that `await` is not a valid `Identifier` if the goal symbol is `Module`, as you can see in the [spec](https://tc39.es/ecma262/#prod-LabelIdentifier), but currently we don't have that information in the `InputElement` enumeration, we only have `Div`, `RegExp` and `TemplateTail`. How could we approach this?

Co-authored-by: jedel1043 <jedel0124@gmail.com>
pull/2588/head
Iban Eguia Moraza 2 years ago
parent
commit
3f9f6f0fce
  1. 215
      boa_ast/src/declaration/export.rs
  2. 206
      boa_ast/src/declaration/import.rs
  3. 54
      boa_ast/src/declaration/mod.rs
  4. 2
      boa_ast/src/lib.rs
  5. 275
      boa_ast/src/module_item_list/mod.rs
  6. 115
      boa_ast/src/operations.rs
  7. 51
      boa_ast/src/visitor.rs
  8. 10
      boa_cli/src/main.rs
  9. 10
      boa_engine/benches/full.rs
  10. 64
      boa_engine/src/builtins/array/tests.rs
  11. 2
      boa_engine/src/builtins/json/mod.rs
  12. 4
      boa_engine/src/builtins/object/tests.rs
  13. 2
      boa_engine/src/builtins/promise/tests.rs
  14. 4
      boa_engine/src/builtins/weak/weak_ref.rs
  15. 4
      boa_engine/src/bytecompiler/class.rs
  16. 2
      boa_engine/src/bytecompiler/function.rs
  17. 10
      boa_engine/src/bytecompiler/mod.rs
  18. 65
      boa_engine/src/bytecompiler/module.rs
  19. 2
      boa_engine/src/bytecompiler/statement/block.rs
  20. 4
      boa_engine/src/bytecompiler/statement/switch.rs
  21. 6
      boa_engine/src/bytecompiler/statement/try.rs
  22. 2
      boa_engine/src/context/hooks.rs
  23. 93
      boa_engine/src/context/mod.rs
  24. 6
      boa_engine/src/lib.rs
  25. 4
      boa_engine/src/tests.rs
  26. 12
      boa_engine/src/value/serde_json.rs
  27. 30
      boa_engine/src/vm/tests.rs
  28. 2
      boa_examples/src/bin/classes.rs
  29. 13
      boa_examples/src/bin/closures.rs
  30. 2
      boa_examples/src/bin/commuter_visitor.rs
  31. 2
      boa_examples/src/bin/loadfile.rs
  32. 2
      boa_examples/src/bin/loadstring.rs
  33. 6
      boa_examples/src/bin/modulehandler.rs
  34. 2
      boa_examples/src/bin/symbol_visitor.rs
  35. 7
      boa_interner/src/sym.rs
  36. 91
      boa_macros/src/lib.rs
  37. 21
      boa_parser/src/error.rs
  38. 1
      boa_parser/src/lexer/error.rs
  39. 2
      boa_parser/src/lexer/token.rs
  40. 1
      boa_parser/src/parser/expression/assignment/arrow_function.rs
  41. 1
      boa_parser/src/parser/expression/assignment/async_arrow_function.rs
  42. 1
      boa_parser/src/parser/expression/assignment/mod.rs
  43. 2
      boa_parser/src/parser/expression/identifiers.rs
  44. 12
      boa_parser/src/parser/expression/left_hand_side/optional/tests.rs
  45. 6
      boa_parser/src/parser/expression/mod.rs
  46. 1
      boa_parser/src/parser/expression/primary/async_function_expression/mod.rs
  47. 1
      boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs
  48. 2
      boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs
  49. 1
      boa_parser/src/parser/expression/primary/function_expression/mod.rs
  50. 1
      boa_parser/src/parser/expression/primary/generator_expression/mod.rs
  51. 18
      boa_parser/src/parser/expression/primary/object_initializer/mod.rs
  52. 12
      boa_parser/src/parser/expression/primary/object_initializer/tests.rs
  53. 10
      boa_parser/src/parser/expression/tests.rs
  54. 4
      boa_parser/src/parser/function/tests.rs
  55. 149
      boa_parser/src/parser/mod.rs
  56. 2
      boa_parser/src/parser/statement/block/tests.rs
  57. 10
      boa_parser/src/parser/statement/break_stm/tests.rs
  58. 10
      boa_parser/src/parser/statement/continue_stm/tests.rs
  59. 316
      boa_parser/src/parser/statement/declaration/export.rs
  60. 4
      boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/mod.rs
  61. 14
      boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs
  62. 4
      boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/mod.rs
  63. 12
      boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs
  64. 4
      boa_parser/src/parser/statement/declaration/hoistable/generator_decl/mod.rs
  65. 15
      boa_parser/src/parser/statement/declaration/hoistable/mod.rs
  66. 327
      boa_parser/src/parser/statement/declaration/import.rs
  67. 2
      boa_parser/src/parser/statement/declaration/lexical.rs
  68. 65
      boa_parser/src/parser/statement/declaration/mod.rs
  69. 32
      boa_parser/src/parser/statement/declaration/tests.rs
  70. 12
      boa_parser/src/parser/statement/iteration/for_statement.rs
  71. 6
      boa_parser/src/parser/statement/iteration/tests.rs
  72. 73
      boa_parser/src/parser/statement/mod.rs
  73. 14
      boa_parser/src/parser/statement/switch/tests.rs
  74. 18
      boa_parser/src/parser/statement/try_stm/tests.rs
  75. 6
      boa_parser/src/parser/statement/variable/mod.rs
  76. 2
      boa_parser/src/parser/tests/format/mod.rs
  77. 6
      boa_parser/src/parser/tests/mod.rs
  78. 5
      boa_tester/src/exec/js262.rs
  79. 54
      boa_tester/src/exec/mod.rs
  80. 8
      boa_tester/src/main.rs
  81. 2
      boa_wasm/src/lib.rs
  82. 2
      fuzz/fuzz_targets/bytecompiler-implied.rs
  83. 4
      fuzz/fuzz_targets/parser-idempotency.rs
  84. 2
      test_ignore.toml

215
boa_ast/src/declaration/export.rs

@ -0,0 +1,215 @@
//! Export declaration AST nodes.
//!
//! This module contains `export` declaration AST nodes.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript specification][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-exports
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
use std::ops::ControlFlow;
use super::{ModuleSpecifier, VarDeclaration};
use crate::{
function::{AsyncFunction, AsyncGenerator, Class, Function, Generator},
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
Declaration, Expression,
};
use boa_interner::Sym;
/// The kind of re-export in an [`ExportDeclaration`].
#[derive(Debug, Clone)]
pub enum ReExportKind {
/// Namespaced Re-export (`export * as name from "module-name"`).
Namespaced {
/// Reexported name for the imported module.
name: Option<Sym>,
},
/// Re-export list (`export { export1, export2 as alias2 } from "module-name"`).
Named {
/// List of the required re-exports of the re-exported module.
names: Box<[ExportSpecifier]>,
},
}
impl VisitWith for ReExportKind {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Namespaced { name: Some(name) } => visitor.visit_sym(name),
Self::Namespaced { name: None } => ControlFlow::Continue(()),
Self::Named { names } => {
for name in &**names {
try_break!(visitor.visit_export_specifier(name));
}
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Namespaced { name: Some(name) } => visitor.visit_sym_mut(name),
Self::Namespaced { name: None } => ControlFlow::Continue(()),
Self::Named { names } => {
for name in &mut **names {
try_break!(visitor.visit_export_specifier_mut(name));
}
ControlFlow::Continue(())
}
}
}
}
/// An export declaration AST node.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ExportDeclaration
#[derive(Debug, Clone)]
pub enum ExportDeclaration {
/// Re-export.
ReExport {
/// The kind of reexport declared.
kind: ReExportKind,
/// Reexported module specifier.
specifier: ModuleSpecifier,
},
/// List of exports.
List(Box<[ExportSpecifier]>),
/// Variable statement export.
VarStatement(VarDeclaration),
/// Declaration export.
Declaration(Declaration),
/// Default function export.
DefaultFunction(Function),
/// Default generator export.
DefaultGenerator(Generator),
/// Default async function export.
DefaultAsyncFunction(AsyncFunction),
/// Default async generator export.
DefaultAsyncGenerator(AsyncGenerator),
/// Default class declaration export.
DefaultClassDeclaration(Class),
/// Default assignment expression export.
DefaultAssignmentExpression(Expression),
}
impl VisitWith for ExportDeclaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::ReExport { specifier, kind } => {
try_break!(visitor.visit_module_specifier(specifier));
visitor.visit_re_export_kind(kind)
}
Self::List(list) => {
for item in &**list {
try_break!(visitor.visit_export_specifier(item));
}
ControlFlow::Continue(())
}
Self::VarStatement(var) => visitor.visit_var_declaration(var),
Self::Declaration(decl) => visitor.visit_declaration(decl),
Self::DefaultFunction(f) => visitor.visit_function(f),
Self::DefaultGenerator(g) => visitor.visit_generator(g),
Self::DefaultAsyncFunction(af) => visitor.visit_async_function(af),
Self::DefaultAsyncGenerator(ag) => visitor.visit_async_generator(ag),
Self::DefaultClassDeclaration(c) => visitor.visit_class(c),
Self::DefaultAssignmentExpression(expr) => visitor.visit_expression(expr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::ReExport { specifier, kind } => {
try_break!(visitor.visit_module_specifier_mut(specifier));
visitor.visit_re_export_kind_mut(kind)
}
Self::List(list) => {
for item in &mut **list {
try_break!(visitor.visit_export_specifier_mut(item));
}
ControlFlow::Continue(())
}
Self::VarStatement(var) => visitor.visit_var_declaration_mut(var),
Self::Declaration(decl) => visitor.visit_declaration_mut(decl),
Self::DefaultFunction(f) => visitor.visit_function_mut(f),
Self::DefaultGenerator(g) => visitor.visit_generator_mut(g),
Self::DefaultAsyncFunction(af) => visitor.visit_async_function_mut(af),
Self::DefaultAsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
Self::DefaultClassDeclaration(c) => visitor.visit_class_mut(c),
Self::DefaultAssignmentExpression(expr) => visitor.visit_expression_mut(expr),
}
}
}
/// Export specifier
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ExportSpecifier
#[derive(Debug, Clone, Copy)]
pub struct ExportSpecifier {
alias: Sym,
private_name: Sym,
}
impl ExportSpecifier {
/// Creates a new [`ExportSpecifier`].
#[inline]
#[must_use]
pub const fn new(alias: Sym, private_name: Sym) -> Self {
Self {
alias,
private_name,
}
}
/// Gets the original alias.
#[inline]
#[must_use]
pub const fn alias(self) -> Sym {
self.alias
}
/// Gets the private name of the export inside the module.
#[inline]
#[must_use]
pub const fn private_name(self) -> Sym {
self.private_name
}
}
impl VisitWith for ExportSpecifier {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_sym(&self.alias));
visitor.visit_sym(&self.private_name)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_sym_mut(&mut self.alias));
visitor.visit_sym_mut(&mut self.private_name)
}
}

206
boa_ast/src/declaration/import.rs

@ -0,0 +1,206 @@
//! Import declaration AST nodes.
//!
//! This module contains `import` declaration AST nodes.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript specification][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-imports
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
use std::ops::ControlFlow;
use crate::{
expression::Identifier,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::Sym;
use super::ModuleSpecifier;
/// The kind of import in an [`ImportDeclaration`].
#[derive(Debug, Clone)]
pub enum ImportKind {
/// Default (`import defaultName from "module-name"`) or unnamed (`import "module-name").
DefaultOrUnnamed,
/// Namespaced import (`import * as name from "module-name"`).
Namespaced {
/// Binding for the namespace created from the exports of the imported module.
binding: Identifier,
},
/// Import list (`import { export1, export2 as alias2 } from "module-name"`).
Named {
/// List of the required exports of the imported module.
names: Box<[ImportSpecifier]>,
},
}
impl VisitWith for ImportKind {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::DefaultOrUnnamed => ControlFlow::Continue(()),
Self::Namespaced { binding } => visitor.visit_identifier(binding),
Self::Named { names } => {
for name in &**names {
try_break!(visitor.visit_import_specifier(name));
}
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::DefaultOrUnnamed => ControlFlow::Continue(()),
Self::Namespaced { binding } => visitor.visit_identifier_mut(binding),
Self::Named { names } => {
for name in &mut **names {
try_break!(visitor.visit_import_specifier_mut(name));
}
ControlFlow::Continue(())
}
}
}
}
/// An import declaration AST node.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ImportDeclaration
#[derive(Debug, Clone)]
pub struct ImportDeclaration {
/// Binding for the default export of `specifier`.
default: Option<Identifier>,
/// See [`ImportKind`].
kind: ImportKind,
/// Module specifier.
specifier: ModuleSpecifier,
}
impl ImportDeclaration {
/// Creates a new import declaration.
#[inline]
#[must_use]
pub const fn new(
default: Option<Identifier>,
kind: ImportKind,
specifier: ModuleSpecifier,
) -> Self {
Self {
default,
kind,
specifier,
}
}
/// Gets the binding for the default export of the module.
#[inline]
#[must_use]
pub const fn default(&self) -> Option<Identifier> {
self.default
}
/// Gets the module specifier of the import declaration.
#[inline]
#[must_use]
pub const fn specifier(&self) -> ModuleSpecifier {
self.specifier
}
/// Gets the import kind of the import declaration
#[inline]
#[must_use]
pub const fn kind(&self) -> &ImportKind {
&self.kind
}
}
impl VisitWith for ImportDeclaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(default) = &self.default {
try_break!(visitor.visit_identifier(default));
}
try_break!(visitor.visit_import_kind(&self.kind));
visitor.visit_module_specifier(&self.specifier)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(default) = &mut self.default {
try_break!(visitor.visit_identifier_mut(default));
}
try_break!(visitor.visit_import_kind_mut(&mut self.kind));
visitor.visit_module_specifier_mut(&mut self.specifier)
}
}
/// Import specifier
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ImportSpecifier
#[derive(Debug, Clone, Copy)]
pub struct ImportSpecifier {
binding: Identifier,
export_name: Sym,
}
impl ImportSpecifier {
/// Creates a new [`ImportSpecifier`].
#[inline]
#[must_use]
pub const fn new(binding: Identifier, export_name: Sym) -> Self {
Self {
binding,
export_name,
}
}
/// Gets the binding of the import specifier.
#[inline]
#[must_use]
pub const fn binding(self) -> Identifier {
self.binding
}
/// Gets the optional export name of the import.
#[inline]
#[must_use]
pub const fn export_name(self) -> Sym {
self.export_name
}
}
impl VisitWith for ImportSpecifier {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_identifier(&self.binding));
visitor.visit_sym(&self.export_name)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_identifier_mut(&mut self.binding));
visitor.visit_sym_mut(&mut self.export_name)
}
}

54
boa_ast/src/declaration/mod.rs

@ -15,12 +15,16 @@
//! [diff]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements#difference_between_statements_and_declarations //! [diff]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements#difference_between_statements_and_declarations
use super::function::{AsyncFunction, AsyncGenerator, Class, Function, Generator}; use super::function::{AsyncFunction, AsyncGenerator, Class, Function, Generator};
use boa_interner::{Interner, ToIndentedString, ToInternedString}; use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow; use core::ops::ControlFlow;
mod export;
mod import;
mod variable; mod variable;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
pub use export::*;
pub use import::*;
pub use variable::*; pub use variable::*;
/// The `Declaration` Parse Node. /// The `Declaration` Parse Node.
@ -95,3 +99,51 @@ impl VisitWith for Declaration {
} }
} }
} }
/// Module specifier.
///
/// This is equivalent to the [`ModuleSpecifier`] production.
///
/// [`FromClause`]: https://tc39.es/ecma262/#prod-ModuleSpecifier
#[derive(Debug, Clone, Copy)]
pub struct ModuleSpecifier {
module: Sym,
}
impl ModuleSpecifier {
/// Creates a `ModuleSpecifier` from a `Sym`.
#[must_use]
pub const fn new(module: Sym) -> Self {
Self { module }
}
/// Gets the inner `Sym` of the module specifier.
#[inline]
#[must_use]
pub const fn sym(self) -> Sym {
self.module
}
}
impl From<Sym> for ModuleSpecifier {
#[inline]
fn from(module: Sym) -> Self {
Self::new(module)
}
}
impl VisitWith for ModuleSpecifier {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_sym(&self.module)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_sym_mut(&mut self.module)
}
}

2
boa_ast/src/lib.rs

@ -102,6 +102,7 @@ pub mod declaration;
pub mod expression; pub mod expression;
pub mod function; pub mod function;
pub mod keyword; pub mod keyword;
pub mod module_item_list;
pub mod operations; pub mod operations;
pub mod pattern; pub mod pattern;
pub mod property; pub mod property;
@ -114,6 +115,7 @@ pub use self::{
declaration::Declaration, declaration::Declaration,
expression::Expression, expression::Expression,
keyword::Keyword, keyword::Keyword,
module_item_list::{ModuleItem, ModuleItemList},
position::{Position, Span}, position::{Position, Span},
punctuator::Punctuator, punctuator::Punctuator,
statement::Statement, statement::Statement,

275
boa_ast/src/module_item_list/mod.rs

@ -0,0 +1,275 @@
//! Module item list AST nodes.
//!
//! More information:
//! - [ECMAScript specification][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-modules
use std::{convert::Infallible, ops::ControlFlow};
use boa_interner::Sym;
use rustc_hash::FxHashSet;
use crate::{
declaration::{ExportDeclaration, ExportSpecifier, ImportDeclaration, ReExportKind},
expression::Identifier,
operations::BoundNamesVisitor,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
StatementListItem,
};
/// Module item list AST node.
///
/// It contains a list of
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ModuleItemList
#[derive(Debug, Clone)]
pub struct ModuleItemList {
items: Box<[ModuleItem]>,
}
impl ModuleItemList {
/// Gets the list of module items.
#[inline]
#[must_use]
pub const fn items(&self) -> &[ModuleItem] {
&self.items
}
/// Abstract operation [`ExportedNames`][spec].
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-exportednames
#[inline]
#[must_use]
pub fn exported_names(&self) -> Vec<Sym> {
#[derive(Debug)]
struct ExportedItemsVisitor<'vec>(&'vec mut Vec<Sym>);
impl<'ast> Visitor<'ast> for ExportedItemsVisitor<'_> {
type BreakTy = Infallible;
fn visit_import_declaration(
&mut self,
_: &'ast ImportDeclaration,
) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_statement_list_item(
&mut self,
_: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_export_specifier(
&mut self,
node: &'ast ExportSpecifier,
) -> ControlFlow<Self::BreakTy> {
self.0.push(node.alias());
ControlFlow::Continue(())
}
fn visit_export_declaration(
&mut self,
node: &'ast ExportDeclaration,
) -> ControlFlow<Self::BreakTy> {
match node {
ExportDeclaration::ReExport { kind, .. } => {
match kind {
ReExportKind::Namespaced { name: Some(name) } => self.0.push(*name),
ReExportKind::Namespaced { name: None } => {}
ReExportKind::Named { names } => {
for specifier in &**names {
try_break!(self.visit_export_specifier(specifier));
}
}
}
ControlFlow::Continue(())
}
ExportDeclaration::List(list) => {
for specifier in &**list {
try_break!(self.visit_export_specifier(specifier));
}
ControlFlow::Continue(())
}
ExportDeclaration::VarStatement(var) => {
BoundNamesVisitor(self.0).visit_var_declaration(var)
}
ExportDeclaration::Declaration(decl) => {
BoundNamesVisitor(self.0).visit_declaration(decl)
}
ExportDeclaration::DefaultFunction(_)
| ExportDeclaration::DefaultGenerator(_)
| ExportDeclaration::DefaultAsyncFunction(_)
| ExportDeclaration::DefaultAsyncGenerator(_)
| ExportDeclaration::DefaultClassDeclaration(_)
| ExportDeclaration::DefaultAssignmentExpression(_) => {
self.0.push(Sym::DEFAULT);
ControlFlow::Continue(())
}
}
}
}
let mut names = Vec::new();
ExportedItemsVisitor(&mut names).visit_module_item_list(self);
names
}
/// Abstract operation [`ExportedBindings`][spec].
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-exportedbindings
#[inline]
#[must_use]
pub fn exported_bindings(&self) -> FxHashSet<Identifier> {
#[derive(Debug)]
struct ExportedBindingsVisitor<'vec>(&'vec mut FxHashSet<Identifier>);
impl<'ast> Visitor<'ast> for ExportedBindingsVisitor<'_> {
type BreakTy = Infallible;
fn visit_import_declaration(
&mut self,
_: &'ast ImportDeclaration,
) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_statement_list_item(
&mut self,
_: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_export_specifier(
&mut self,
node: &'ast ExportSpecifier,
) -> ControlFlow<Self::BreakTy> {
self.0.insert(Identifier::new(node.private_name()));
ControlFlow::Continue(())
}
fn visit_export_declaration(
&mut self,
node: &'ast ExportDeclaration,
) -> ControlFlow<Self::BreakTy> {
let name = match node {
ExportDeclaration::ReExport { .. } => return ControlFlow::Continue(()),
ExportDeclaration::List(list) => {
for specifier in &**list {
try_break!(self.visit_export_specifier(specifier));
}
return ControlFlow::Continue(());
}
ExportDeclaration::DefaultAssignmentExpression(expr) => {
return BoundNamesVisitor(self.0).visit_expression(expr);
}
ExportDeclaration::VarStatement(var) => {
return BoundNamesVisitor(self.0).visit_var_declaration(var);
}
ExportDeclaration::Declaration(decl) => {
return BoundNamesVisitor(self.0).visit_declaration(decl);
}
ExportDeclaration::DefaultFunction(f) => f.name(),
ExportDeclaration::DefaultGenerator(g) => g.name(),
ExportDeclaration::DefaultAsyncFunction(af) => af.name(),
ExportDeclaration::DefaultAsyncGenerator(ag) => ag.name(),
ExportDeclaration::DefaultClassDeclaration(cl) => cl.name(),
};
self.0
.insert(name.unwrap_or_else(|| Identifier::new(Sym::DEFAULT_EXPORT)));
ControlFlow::Continue(())
}
}
let mut names = FxHashSet::default();
ExportedBindingsVisitor(&mut names).visit_module_item_list(self);
names
}
}
impl<T> From<T> for ModuleItemList
where
T: Into<Box<[ModuleItem]>>,
{
#[inline]
fn from(items: T) -> Self {
Self {
items: items.into(),
}
}
}
impl VisitWith for ModuleItemList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for item in &*self.items {
try_break!(visitor.visit_module_item(item));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for item in &mut *self.items {
try_break!(visitor.visit_module_item_mut(item));
}
ControlFlow::Continue(())
}
}
/// Module item AST node.
///
/// This is an extension over a [`StatementList`](crate::StatementList), which can also include
/// multiple [`ImportDeclaration`] and [`ExportDeclaration`] nodes, along with
/// [`StatementListItem`] nodes.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ModuleItem
#[derive(Debug, Clone)]
pub enum ModuleItem {
/// See [`ImportDeclaration`].
ImportDeclaration(ImportDeclaration),
/// See [`ExportDeclaration`].
ExportDeclaration(ExportDeclaration),
/// See [`StatementListItem`].
StatementListItem(StatementListItem),
}
impl VisitWith for ModuleItem {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::ImportDeclaration(i) => visitor.visit_import_declaration(i),
Self::ExportDeclaration(e) => visitor.visit_export_declaration(e),
Self::StatementListItem(s) => visitor.visit_statement_list_item(s),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::ImportDeclaration(i) => visitor.visit_import_declaration_mut(i),
Self::ExportDeclaration(e) => visitor.visit_export_declaration_mut(e),
Self::StatementListItem(s) => visitor.visit_statement_list_item_mut(s),
}
}
}

115
boa_ast/src/operations.rs

@ -9,7 +9,7 @@ use boa_interner::Sym;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use crate::{ use crate::{
declaration::VarDeclaration, declaration::{ExportDeclaration, ImportDeclaration, VarDeclaration},
expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield}, expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield},
function::{ function::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement,
@ -299,37 +299,43 @@ pub fn has_direct_super(method: &MethodDefinition) -> bool {
} }
/// A container that [`BoundNamesVisitor`] can use to push the found identifiers. /// A container that [`BoundNamesVisitor`] can use to push the found identifiers.
trait IdentList { pub(crate) trait IdentList {
fn add(&mut self, value: Identifier, function: bool); fn add(&mut self, value: Sym, function: bool);
} }
impl IdentList for Vec<Identifier> { impl IdentList for Vec<Sym> {
fn add(&mut self, value: Identifier, _function: bool) { fn add(&mut self, value: Sym, _function: bool) {
self.push(value); self.push(value);
} }
} }
impl IdentList for Vec<Identifier> {
fn add(&mut self, value: Sym, _function: bool) {
self.push(Identifier::new(value));
}
}
impl IdentList for Vec<(Identifier, bool)> { impl IdentList for Vec<(Identifier, bool)> {
fn add(&mut self, value: Identifier, function: bool) { fn add(&mut self, value: Sym, function: bool) {
self.push((value, function)); self.push((Identifier::new(value), function));
} }
} }
impl IdentList for FxHashSet<Identifier> { impl IdentList for FxHashSet<Identifier> {
fn add(&mut self, value: Identifier, _function: bool) { fn add(&mut self, value: Sym, _function: bool) {
self.insert(value); self.insert(Identifier::new(value));
} }
} }
/// The [`Visitor`] used to obtain the bound names of a node. /// The [`Visitor`] used to obtain the bound names of a node.
#[derive(Debug)] #[derive(Debug)]
struct BoundNamesVisitor<'a, T: IdentList>(&'a mut T); pub(crate) struct BoundNamesVisitor<'a, T: IdentList>(pub(crate) &'a mut T);
impl<'ast, T: IdentList> Visitor<'ast> for BoundNamesVisitor<'_, T> { impl<'ast, T: IdentList> Visitor<'ast> for BoundNamesVisitor<'_, T> {
type BreakTy = Infallible; type BreakTy = Infallible;
fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> { fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> {
self.0.add(*node, false); self.0.add(node.sym(), false);
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
@ -337,41 +343,82 @@ impl<'ast, T: IdentList> Visitor<'ast> for BoundNamesVisitor<'_, T> {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
// TODO: add "*default" for module default functions without name
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> { fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() { if let Some(ident) = node.name() {
self.0.add(ident, true); self.0.add(ident.sym(), true);
} }
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> { fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() { if let Some(ident) = node.name() {
self.0.add(ident, false); self.0.add(ident.sym(), false);
} }
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> { fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() { if let Some(ident) = node.name() {
self.0.add(ident, false); self.0.add(ident.sym(), false);
} }
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> { fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() { if let Some(ident) = node.name() {
self.0.add(ident, false); self.0.add(ident.sym(), false);
} }
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_class(&mut self, node: &'ast Class) -> ControlFlow<Self::BreakTy> { fn visit_class(&mut self, node: &'ast Class) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() { if let Some(ident) = node.name() {
self.0.add(ident, false); self.0.add(ident.sym(), false);
} }
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_export_declaration(
&mut self,
node: &'ast ExportDeclaration,
) -> ControlFlow<Self::BreakTy> {
match node {
ExportDeclaration::VarStatement(var) => try_break!(self.visit_var_declaration(var)),
ExportDeclaration::Declaration(decl) => try_break!(self.visit_declaration(decl)),
ExportDeclaration::DefaultFunction(f) => {
self.0
.add(f.name().map_or(Sym::DEFAULT_EXPORT, Identifier::sym), true);
}
ExportDeclaration::DefaultGenerator(g) => {
self.0
.add(g.name().map_or(Sym::DEFAULT_EXPORT, Identifier::sym), false);
}
ExportDeclaration::DefaultAsyncFunction(af) => {
self.0.add(
af.name().map_or(Sym::DEFAULT_EXPORT, Identifier::sym),
false,
);
}
ExportDeclaration::DefaultAsyncGenerator(ag) => {
self.0.add(
ag.name().map_or(Sym::DEFAULT_EXPORT, Identifier::sym),
false,
);
}
ExportDeclaration::DefaultClassDeclaration(cl) => {
self.0.add(
cl.name().map_or(Sym::DEFAULT_EXPORT, Identifier::sym),
false,
);
}
ExportDeclaration::DefaultAssignmentExpression(_) => {
self.0.add(Sym::DEFAULT_EXPORT, false);
}
ExportDeclaration::ReExport { .. } | ExportDeclaration::List(_) => {}
}
ControlFlow::Continue(())
}
} }
/// Returns a list with the bound names of an AST node, which may contain duplicates. /// Returns a list with the bound names of an AST node, which may contain duplicates.
@ -448,6 +495,21 @@ impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T>
} }
ControlFlow::Continue(()) 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,
) -> ControlFlow<Self::BreakTy> {
if matches!(node, ExportDeclaration::VarStatement(_)) {
return ControlFlow::Continue(());
}
BoundNamesVisitor(self.0).visit_export_declaration(node)
}
// TODO: ScriptBody : StatementList // TODO: ScriptBody : StatementList
// 1. Return TopLevelLexicallyDeclaredNames of StatementList. // 1. Return TopLevelLexicallyDeclaredNames of StatementList.
@ -552,6 +614,25 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
node.visit_with(self) node.visit_with(self)
} }
fn visit_import_declaration(
&mut self,
_: &'ast ImportDeclaration,
) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_export_declaration(
&mut self,
node: &'ast ExportDeclaration,
) -> ControlFlow<Self::BreakTy> {
match node {
ExportDeclaration::VarStatement(var) => {
BoundNamesVisitor(self.0).visit_var_declaration(var)
}
_ => ControlFlow::Continue(()),
}
}
// TODO: ScriptBody : StatementList // TODO: ScriptBody : StatementList
// 1. Return TopLevelVarDeclaredNames of StatementList. // 1. Return TopLevelVarDeclaredNames of StatementList.
// But we don't have that node yet. In the meantime, use `top_level_var_declared_names` directly. // But we don't have that node yet. In the meantime, use `top_level_var_declared_names` directly.

51
boa_ast/src/visitor.rs

@ -7,7 +7,9 @@ use std::ops::ControlFlow;
use crate::{ use crate::{
declaration::{ declaration::{
Binding, Declaration, LexicalDeclaration, VarDeclaration, Variable, VariableList, Binding, Declaration, ExportDeclaration, ExportSpecifier, ImportDeclaration, ImportKind,
ImportSpecifier, LexicalDeclaration, ModuleSpecifier, ReExportKind, VarDeclaration,
Variable, VariableList,
}, },
expression::{ expression::{
access::{ access::{
@ -36,7 +38,7 @@ use crate::{
Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw, Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw,
Try, Try,
}, },
StatementList, StatementListItem, ModuleItem, ModuleItemList, StatementList, StatementListItem,
}; };
use boa_interner::Sym; use boa_interner::Sym;
@ -194,6 +196,15 @@ node_ref! {
ArrayPatternElement, ArrayPatternElement,
PropertyAccessField, PropertyAccessField,
OptionalOperationKind, OptionalOperationKind,
ModuleItemList,
ModuleItem,
ModuleSpecifier,
ImportKind,
ImportDeclaration,
ImportSpecifier,
ReExportKind,
ExportDeclaration,
ExportSpecifier
} }
/// Represents an AST visitor. /// Represents an AST visitor.
@ -282,6 +293,15 @@ pub trait Visitor<'ast>: Sized {
define_visit!(visit_array_pattern_element, ArrayPatternElement); define_visit!(visit_array_pattern_element, ArrayPatternElement);
define_visit!(visit_property_access_field, PropertyAccessField); define_visit!(visit_property_access_field, PropertyAccessField);
define_visit!(visit_optional_operation_kind, OptionalOperationKind); define_visit!(visit_optional_operation_kind, OptionalOperationKind);
define_visit!(visit_module_item_list, ModuleItemList);
define_visit!(visit_module_item, ModuleItem);
define_visit!(visit_module_specifier, ModuleSpecifier);
define_visit!(visit_import_kind, ImportKind);
define_visit!(visit_import_declaration, ImportDeclaration);
define_visit!(visit_import_specifier, ImportSpecifier);
define_visit!(visit_re_export_kind, ReExportKind);
define_visit!(visit_export_declaration, ExportDeclaration);
define_visit!(visit_export_specifier, ExportSpecifier);
/// Generic entry point for a node that is visitable by a `Visitor`. /// Generic entry point for a node that is visitable by a `Visitor`.
/// ///
@ -367,6 +387,15 @@ pub trait Visitor<'ast>: Sized {
NodeRef::ArrayPatternElement(n) => self.visit_array_pattern_element(n), NodeRef::ArrayPatternElement(n) => self.visit_array_pattern_element(n),
NodeRef::PropertyAccessField(n) => self.visit_property_access_field(n), NodeRef::PropertyAccessField(n) => self.visit_property_access_field(n),
NodeRef::OptionalOperationKind(n) => self.visit_optional_operation_kind(n), NodeRef::OptionalOperationKind(n) => self.visit_optional_operation_kind(n),
NodeRef::ModuleItemList(n) => self.visit_module_item_list(n),
NodeRef::ModuleItem(n) => self.visit_module_item(n),
NodeRef::ModuleSpecifier(n) => self.visit_module_specifier(n),
NodeRef::ImportKind(n) => self.visit_import_kind(n),
NodeRef::ImportDeclaration(n) => self.visit_import_declaration(n),
NodeRef::ImportSpecifier(n) => self.visit_import_specifier(n),
NodeRef::ReExportKind(n) => self.visit_re_export_kind(n),
NodeRef::ExportDeclaration(n) => self.visit_export_declaration(n),
NodeRef::ExportSpecifier(n) => self.visit_export_specifier(n),
} }
} }
} }
@ -457,6 +486,15 @@ pub trait VisitorMut<'ast>: Sized {
define_visit_mut!(visit_array_pattern_element_mut, ArrayPatternElement); define_visit_mut!(visit_array_pattern_element_mut, ArrayPatternElement);
define_visit_mut!(visit_property_access_field_mut, PropertyAccessField); define_visit_mut!(visit_property_access_field_mut, PropertyAccessField);
define_visit_mut!(visit_optional_operation_kind_mut, OptionalOperationKind); define_visit_mut!(visit_optional_operation_kind_mut, OptionalOperationKind);
define_visit_mut!(visit_module_item_list_mut, ModuleItemList);
define_visit_mut!(visit_module_item_mut, ModuleItem);
define_visit_mut!(visit_module_specifier_mut, ModuleSpecifier);
define_visit_mut!(visit_import_kind_mut, ImportKind);
define_visit_mut!(visit_import_declaration_mut, ImportDeclaration);
define_visit_mut!(visit_import_specifier_mut, ImportSpecifier);
define_visit_mut!(visit_re_export_kind_mut, ReExportKind);
define_visit_mut!(visit_export_declaration_mut, ExportDeclaration);
define_visit_mut!(visit_export_specifier_mut, ExportSpecifier);
/// Generic entry point for a node that is visitable by a `VisitorMut`. /// Generic entry point for a node that is visitable by a `VisitorMut`.
/// ///
@ -542,6 +580,15 @@ pub trait VisitorMut<'ast>: Sized {
NodeRefMut::ArrayPatternElement(n) => self.visit_array_pattern_element_mut(n), NodeRefMut::ArrayPatternElement(n) => self.visit_array_pattern_element_mut(n),
NodeRefMut::PropertyAccessField(n) => self.visit_property_access_field_mut(n), NodeRefMut::PropertyAccessField(n) => self.visit_property_access_field_mut(n),
NodeRefMut::OptionalOperationKind(n) => self.visit_optional_operation_kind_mut(n), NodeRefMut::OptionalOperationKind(n) => self.visit_optional_operation_kind_mut(n),
NodeRefMut::ModuleItemList(n) => self.visit_module_item_list_mut(n),
NodeRefMut::ModuleItem(n) => self.visit_module_item_mut(n),
NodeRefMut::ModuleSpecifier(n) => self.visit_module_specifier_mut(n),
NodeRefMut::ImportKind(n) => self.visit_import_kind_mut(n),
NodeRefMut::ImportDeclaration(n) => self.visit_import_declaration_mut(n),
NodeRefMut::ImportSpecifier(n) => self.visit_import_specifier_mut(n),
NodeRefMut::ReExportKind(n) => self.visit_re_export_kind_mut(n),
NodeRefMut::ExportDeclaration(n) => self.visit_export_declaration_mut(n),
NodeRefMut::ExportSpecifier(n) => self.visit_export_specifier_mut(n),
} }
} }
} }

10
boa_cli/src/main.rs

@ -194,7 +194,7 @@ where
S: AsRef<[u8]> + ?Sized, S: AsRef<[u8]> + ?Sized,
{ {
boa_parser::Parser::new(Source::from_bytes(&src)) boa_parser::Parser::new(Source::from_bytes(&src))
.parse_all(context.interner_mut()) .parse_script(context.interner_mut())
.map_err(|e| format!("Uncaught SyntaxError: {e}")) .map_err(|e| format!("Uncaught SyntaxError: {e}"))
} }
@ -232,8 +232,8 @@ fn generate_flowgraph(
format: FlowgraphFormat, format: FlowgraphFormat,
direction: Option<FlowgraphDirection>, direction: Option<FlowgraphDirection>,
) -> JsResult<String> { ) -> JsResult<String> {
let ast = context.parse(Source::from_bytes(src))?; let ast = context.parse_script(Source::from_bytes(src))?;
let code = context.compile(&ast)?; let code = context.compile_script(&ast)?;
let direction = match direction { let direction = match direction {
Some(FlowgraphDirection::TopToBottom) | None => Direction::TopToBottom, Some(FlowgraphDirection::TopToBottom) | None => Direction::TopToBottom,
@ -281,7 +281,7 @@ fn main() -> Result<(), io::Error> {
Err(v) => eprintln!("Uncaught {v}"), Err(v) => eprintln!("Uncaught {v}"),
} }
} else { } else {
match context.eval(Source::from_bytes(&buffer)) { match context.eval_script(Source::from_bytes(&buffer)) {
Ok(v) => println!("{}", v.display()), Ok(v) => println!("{}", v.display()),
Err(v) => eprintln!("Uncaught {v}"), Err(v) => eprintln!("Uncaught {v}"),
} }
@ -338,7 +338,7 @@ fn main() -> Result<(), io::Error> {
Err(v) => eprintln!("Uncaught {v}"), Err(v) => eprintln!("Uncaught {v}"),
} }
} else { } else {
match context.eval(Source::from_bytes(line.trim_end())) { match context.eval_script(Source::from_bytes(line.trim_end())) {
Ok(v) => { Ok(v) => {
println!("{}", v.display()); println!("{}", v.display());
} }

10
boa_engine/benches/full.rs

@ -25,7 +25,7 @@ macro_rules! full_benchmarks {
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default(); let mut context = Context::default();
c.bench_function(concat!($id, " (Parser)"), move |b| { c.bench_function(concat!($id, " (Parser)"), move |b| {
b.iter(|| context.parse(black_box(Source::from_bytes(CODE)))) b.iter(|| context.parse_script(black_box(Source::from_bytes(CODE))))
}); });
} }
)* )*
@ -35,10 +35,10 @@ macro_rules! full_benchmarks {
{ {
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default(); let mut context = Context::default();
let statement_list = context.parse(Source::from_bytes(CODE)).expect("parsing failed"); let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed");
c.bench_function(concat!($id, " (Compiler)"), move |b| { c.bench_function(concat!($id, " (Compiler)"), move |b| {
b.iter(|| { b.iter(|| {
context.compile(black_box(&statement_list)) context.compile_script(black_box(&statement_list))
}) })
}); });
} }
@ -49,8 +49,8 @@ macro_rules! full_benchmarks {
{ {
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default(); let mut context = Context::default();
let statement_list = context.parse(Source::from_bytes(CODE)).expect("parsing failed"); let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed");
let code_block = context.compile(&statement_list).unwrap(); let code_block = context.compile_script(&statement_list).unwrap();
c.bench_function(concat!($id, " (Execution)"), move |b| { c.bench_function(concat!($id, " (Execution)"), move |b| {
b.iter(|| { b.iter(|| {
context.execute(black_box(code_block.clone())).unwrap() context.execute(black_box(code_block.clone())).unwrap()

64
boa_engine/src/builtins/array/tests.rs

@ -12,62 +12,64 @@ fn is_array() {
var new_arr = new Array(); var new_arr = new Array();
var many = ["a", "b", "c"]; var many = ["a", "b", "c"];
"#; "#;
context.eval(Source::from_bytes(init)).unwrap(); context.eval_script(Source::from_bytes(init)).unwrap();
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.isArray(empty)")) .eval_script(Source::from_bytes("Array.isArray(empty)"))
.unwrap(), .unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.isArray(new_arr)")) .eval_script(Source::from_bytes("Array.isArray(new_arr)"))
.unwrap(), .unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.isArray(many)")) .eval_script(Source::from_bytes("Array.isArray(many)"))
.unwrap(), .unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.isArray([1, 2, 3])")) .eval_script(Source::from_bytes("Array.isArray([1, 2, 3])"))
.unwrap(), .unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.isArray([])")) .eval_script(Source::from_bytes("Array.isArray([])"))
.unwrap(), .unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.isArray({})")) .eval_script(Source::from_bytes("Array.isArray({})"))
.unwrap(), .unwrap(),
JsValue::new(false) JsValue::new(false)
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.isArray(new Array)")) .eval_script(Source::from_bytes("Array.isArray(new Array)"))
.unwrap(), .unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context.eval(Source::from_bytes("Array.isArray()")).unwrap(), context
.eval_script(Source::from_bytes("Array.isArray()"))
.unwrap(),
JsValue::new(false) JsValue::new(false)
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.isArray({ constructor: Array })")) .eval_script(Source::from_bytes("Array.isArray({ constructor: Array })"))
.unwrap(), .unwrap(),
JsValue::new(false) JsValue::new(false)
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
"Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })" "Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })"
)) ))
.unwrap(), .unwrap(),
@ -75,13 +77,13 @@ fn is_array() {
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.isArray(17)")) .eval_script(Source::from_bytes("Array.isArray(17)"))
.unwrap(), .unwrap(),
JsValue::new(false) JsValue::new(false)
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
"Array.isArray({ __proto__: Array.prototype })" "Array.isArray({ __proto__: Array.prototype })"
)) ))
.unwrap(), .unwrap(),
@ -89,7 +91,7 @@ fn is_array() {
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.isArray({ length: 0 })")) .eval_script(Source::from_bytes("Array.isArray({ length: 0 })"))
.unwrap(), .unwrap(),
JsValue::new(false) JsValue::new(false)
); );
@ -100,66 +102,66 @@ fn of() {
let mut context = Context::default(); let mut context = Context::default();
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.of(1, 2, 3)")) .eval_script(Source::from_bytes("Array.of(1, 2, 3)"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap(), .unwrap(),
context context
.eval(Source::from_bytes("[1, 2, 3]")) .eval_script(Source::from_bytes("[1, 2, 3]"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap() .unwrap()
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.of(1, 'a', [], undefined, null)")) .eval_script(Source::from_bytes("Array.of(1, 'a', [], undefined, null)"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap(), .unwrap(),
context context
.eval(Source::from_bytes("[1, 'a', [], undefined, null]")) .eval_script(Source::from_bytes("[1, 'a', [], undefined, null]"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap() .unwrap()
); );
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("Array.of()")) .eval_script(Source::from_bytes("Array.of()"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap(), .unwrap(),
context context
.eval(Source::from_bytes("[]")) .eval_script(Source::from_bytes("[]"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap() .unwrap()
); );
context context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
r#"let a = Array.of.call(Date, "a", undefined, 3);"#, r#"let a = Array.of.call(Date, "a", undefined, 3);"#,
)) ))
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes("a instanceof Date")) .eval_script(Source::from_bytes("a instanceof Date"))
.unwrap(), .unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context.eval(Source::from_bytes("a[0]")).unwrap(), context.eval_script(Source::from_bytes("a[0]")).unwrap(),
JsValue::new("a") JsValue::new("a")
); );
assert_eq!( assert_eq!(
context.eval(Source::from_bytes("a[1]")).unwrap(), context.eval_script(Source::from_bytes("a[1]")).unwrap(),
JsValue::undefined() JsValue::undefined()
); );
assert_eq!( assert_eq!(
context.eval(Source::from_bytes("a[2]")).unwrap(), context.eval_script(Source::from_bytes("a[2]")).unwrap(),
JsValue::new(3) JsValue::new(3)
); );
assert_eq!( assert_eq!(
context.eval(Source::from_bytes("a.length")).unwrap(), context.eval_script(Source::from_bytes("a.length")).unwrap(),
JsValue::new(3) JsValue::new(3)
); );
} }
@ -171,31 +173,31 @@ fn concat() {
var empty = []; var empty = [];
var one = [1]; var one = [1];
"#; "#;
context.eval(Source::from_bytes(init)).unwrap(); context.eval_script(Source::from_bytes(init)).unwrap();
// Empty ++ Empty // Empty ++ Empty
let ee = context let ee = context
.eval(Source::from_bytes("empty.concat(empty)")) .eval_script(Source::from_bytes("empty.concat(empty)"))
.unwrap() .unwrap()
.display() .display()
.to_string(); .to_string();
assert_eq!(ee, "[]"); assert_eq!(ee, "[]");
// Empty ++ NonEmpty // Empty ++ NonEmpty
let en = context let en = context
.eval(Source::from_bytes("empty.concat(one)")) .eval_script(Source::from_bytes("empty.concat(one)"))
.unwrap() .unwrap()
.display() .display()
.to_string(); .to_string();
assert_eq!(en, "[ 1 ]"); assert_eq!(en, "[ 1 ]");
// NonEmpty ++ Empty // NonEmpty ++ Empty
let ne = context let ne = context
.eval(Source::from_bytes("one.concat(empty)")) .eval_script(Source::from_bytes("one.concat(empty)"))
.unwrap() .unwrap()
.display() .display()
.to_string(); .to_string();
assert_eq!(ne, "[ 1 ]"); assert_eq!(ne, "[ 1 ]");
// NonEmpty ++ NonEmpty // NonEmpty ++ NonEmpty
let nn = context let nn = context
.eval(Source::from_bytes("one.concat(one)")) .eval_script(Source::from_bytes("one.concat(one)"))
.unwrap() .unwrap()
.display() .display()
.to_string(); .to_string();

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

@ -205,7 +205,7 @@ impl Json {
// 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral. // 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral.
let mut parser = Parser::new(Source::from_bytes(&script_string)); let mut parser = Parser::new(Source::from_bytes(&script_string));
parser.set_json_parse(); parser.set_json_parse();
let statement_list = parser.parse_all(context.interner_mut())?; let statement_list = parser.parse_script(context.interner_mut())?;
let code_block = context.compile_json_parse(&statement_list)?; let code_block = context.compile_json_parse(&statement_list)?;
let unfiltered = context.execute(code_block)?; let unfiltered = context.execute(code_block)?;

4
boa_engine/src/builtins/object/tests.rs

@ -252,7 +252,7 @@ fn get_own_property_descriptor_1_arg_returns_undefined() {
Object.getOwnPropertyDescriptor(obj) Object.getOwnPropertyDescriptor(obj)
"#; "#;
assert_eq!( assert_eq!(
context.eval(Source::from_bytes(code)).unwrap(), context.eval_script(Source::from_bytes(code)).unwrap(),
JsValue::undefined() JsValue::undefined()
); );
} }
@ -324,7 +324,7 @@ fn object_is_prototype_of() {
"#; "#;
assert_eq!( assert_eq!(
context.eval(Source::from_bytes(init)).unwrap(), context.eval_script(Source::from_bytes(init)).unwrap(),
JsValue::new(true) JsValue::new(true)
); );
} }

2
boa_engine/src/builtins/promise/tests.rs

@ -15,7 +15,7 @@ fn promise() {
count += 1; count += 1;
count; count;
"#; "#;
let result = context.eval(Source::from_bytes(init)).unwrap(); let result = context.eval_script(Source::from_bytes(init)).unwrap();
assert_eq!(result.as_number(), Some(2_f64)); assert_eq!(result.as_number(), Some(2_f64));
context.run_jobs(); context.run_jobs();
let after_completion = forward(&mut context, "count"); let after_completion = forward(&mut context, "count");

4
boa_engine/src/builtins/weak/weak_ref.rs

@ -150,7 +150,7 @@ mod tests {
let context = &mut Context::default(); let context = &mut Context::default();
assert!(context assert!(context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
r#" r#"
var ptr; var ptr;
{ {
@ -167,7 +167,7 @@ mod tests {
assert_eq!( assert_eq!(
context context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
r#" r#"
ptr.deref() ptr.deref()
"# "#

4
boa_engine/src/bytecompiler/class.rs

@ -97,7 +97,7 @@ impl ByteCompiler<'_, '_> {
} else { } else {
None None
}; };
compiler.create_decls(expr.body(), false); compiler.create_script_decls(expr.body(), false);
compiler.compile_statement_list(expr.body(), false, false)?; compiler.compile_statement_list(expr.body(), false, false)?;
if let Some(env_label) = env_label { if let Some(env_label) = env_label {
let (num_bindings, compile_environment) = let (num_bindings, compile_environment) =
@ -445,7 +445,7 @@ impl ByteCompiler<'_, '_> {
.context .context
.create_immutable_binding(class_name.into(), true); .create_immutable_binding(class_name.into(), true);
compiler.context.push_compile_time_environment(true); compiler.context.push_compile_time_environment(true);
compiler.create_decls(statement_list, false); compiler.create_script_decls(statement_list, false);
compiler.compile_statement_list(statement_list, false, false)?; compiler.compile_statement_list(statement_list, false, false)?;
let (num_bindings, compile_environment) = let (num_bindings, compile_environment) =
compiler.context.pop_compile_time_environment(); compiler.context.pop_compile_time_environment();

2
boa_engine/src/bytecompiler/function.rs

@ -199,7 +199,7 @@ impl FunctionCompiler {
compiler.emit_opcode(Opcode::Yield); compiler.emit_opcode(Opcode::Yield);
} }
compiler.create_decls(body, false); compiler.create_script_decls(body, false);
compiler.compile_statement_list(body, false, false)?; compiler.compile_statement_list(body, false, false)?;
if let Some(env_label) = env_label { if let Some(env_label) = env_label {

10
boa_engine/src/bytecompiler/mod.rs

@ -5,6 +5,7 @@ mod declaration;
mod expression; mod expression;
mod function; mod function;
mod jump_control; mod jump_control;
mod module;
mod statement; mod statement;
use crate::{ use crate::{
@ -703,7 +704,7 @@ impl<'b, 'host> ByteCompiler<'b, 'host> {
self.context.push_compile_time_environment(strict); self.context.push_compile_time_environment(strict);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_decls(list, true); self.create_script_decls(list, true);
if use_expr { if use_expr {
let expr_index = list let expr_index = list
@ -1271,7 +1272,12 @@ impl<'b, 'host> ByteCompiler<'b, 'host> {
self.compile_declaration_pattern_impl(pattern, def) self.compile_declaration_pattern_impl(pattern, def)
} }
pub(crate) fn create_decls(&mut self, stmt_list: &StatementList, configurable_globals: bool) { /// Creates the declarations for a sript.
pub(crate) fn create_script_decls(
&mut self,
stmt_list: &StatementList,
configurable_globals: bool,
) {
for node in stmt_list.statements() { for node in stmt_list.statements() {
self.create_decls_from_stmt_list_item(node, configurable_globals); self.create_decls_from_stmt_list_item(node, configurable_globals);
} }

65
boa_engine/src/bytecompiler/module.rs

@ -0,0 +1,65 @@
use boa_ast::{ModuleItem, ModuleItemList};
use crate::JsResult;
use super::ByteCompiler;
impl ByteCompiler<'_, '_> {
/// Compiles a [`ModuleItemList`].
#[inline]
pub fn compile_module_item_list(
&mut self,
list: &ModuleItemList,
configurable_globals: bool,
) -> JsResult<()> {
for node in list.items() {
self.compile_module_item(node, configurable_globals)?;
}
Ok(())
}
/// Compiles a [`ModuleItem`].
#[inline]
#[allow(unused_variables, clippy::missing_panics_doc)] // Unimplemented
pub fn compile_module_item(
&mut self,
item: &ModuleItem,
configurable_globals: bool,
) -> JsResult<()> {
match item {
ModuleItem::ImportDeclaration(import) => todo!("import declaration compilation"),
ModuleItem::ExportDeclaration(export) => todo!("export declaration compilation"),
ModuleItem::StatementListItem(stmt) => {
self.compile_stmt_list_item(stmt, false, configurable_globals)
}
}
}
/// Creates the declarations for a module.
pub(crate) fn create_module_decls(
&mut self,
stmt_list: &ModuleItemList,
configurable_globals: bool,
) {
for node in stmt_list.items() {
self.create_decls_from_module_item(node, configurable_globals);
}
}
/// Creates the declarations from a [`ModuleItem`].
#[inline]
#[allow(unused_variables)] // Unimplemented
pub(crate) fn create_decls_from_module_item(
&mut self,
item: &ModuleItem,
configurable_globals: bool,
) -> bool {
match item {
ModuleItem::ImportDeclaration(import) => todo!("import declaration generation"),
ModuleItem::ExportDeclaration(export) => todo!("export declaration generation"),
ModuleItem::StatementListItem(stmt) => {
self.create_decls_from_stmt_list_item(stmt, configurable_globals)
}
}
}
}

2
boa_engine/src/bytecompiler/statement/block.rs

@ -13,7 +13,7 @@ impl ByteCompiler<'_, '_> {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_and_track_decl_env(); let push_env = self.emit_and_track_decl_env();
self.create_decls(block.statement_list(), configurable_globals); self.create_script_decls(block.statement_list(), configurable_globals);
self.compile_statement_list(block.statement_list(), use_expr, configurable_globals)?; self.compile_statement_list(block.statement_list(), use_expr, configurable_globals)?;
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();

4
boa_engine/src/bytecompiler/statement/switch.rs

@ -12,7 +12,7 @@ impl ByteCompiler<'_, '_> {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
for case in switch.cases() { for case in switch.cases() {
self.create_decls(case.body(), configurable_globals); self.create_script_decls(case.body(), configurable_globals);
} }
self.emit_opcode(Opcode::LoopStart); self.emit_opcode(Opcode::LoopStart);
@ -35,7 +35,7 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(exit); self.patch_jump(exit);
if let Some(body) = switch.default() { if let Some(body) = switch.default() {
self.create_decls(body, configurable_globals); self.create_script_decls(body, configurable_globals);
self.compile_statement_list(body, false, configurable_globals)?; self.compile_statement_list(body, false, configurable_globals)?;
} }

6
boa_engine/src/bytecompiler/statement/try.rs

@ -19,7 +19,7 @@ impl ByteCompiler<'_, '_> {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_decls(t.block().statement_list(), configurable_globals); self.create_script_decls(t.block().statement_list(), configurable_globals);
self.compile_statement_list(t.block().statement_list(), use_expr, configurable_globals)?; self.compile_statement_list(t.block().statement_list(), use_expr, configurable_globals)?;
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
@ -58,7 +58,7 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Pop);
} }
self.create_decls(catch.block().statement_list(), configurable_globals); self.create_script_decls(catch.block().statement_list(), configurable_globals);
self.compile_statement_list( self.compile_statement_list(
catch.block().statement_list(), catch.block().statement_list(),
use_expr, use_expr,
@ -96,7 +96,7 @@ impl ByteCompiler<'_, '_> {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_decls(finally.block().statement_list(), configurable_globals); self.create_script_decls(finally.block().statement_list(), configurable_globals);
self.compile_statement_list( self.compile_statement_list(
finally.block().statement_list(), finally.block().statement_list(),
false, false,

2
boa_engine/src/context/hooks.rs

@ -32,7 +32,7 @@ use super::intrinsics::Intrinsics;
/// } /// }
/// let hooks = Hooks; // Can have additional state. /// let hooks = Hooks; // Can have additional state.
/// let context = &mut ContextBuilder::new().host_hooks(&hooks).build(); /// let context = &mut ContextBuilder::new().host_hooks(&hooks).build();
/// let result = context.eval(Source::from_bytes(r#"eval("let a = 5")"#)); /// let result = context.eval_script(Source::from_bytes(r#"eval("let a = 5")"#));
/// assert_eq!(result.unwrap_err().to_string(), "TypeError: eval calls not available"); /// assert_eq!(result.unwrap_err().to_string(), "TypeError: eval calls not available");
/// ``` /// ```
/// ///

93
boa_engine/src/context/mod.rs

@ -28,8 +28,7 @@ use crate::{
vm::{CallFrame, CodeBlock, Vm}, vm::{CallFrame, CodeBlock, Vm},
JsResult, JsValue, Source, JsResult, JsValue, Source,
}; };
use boa_ast::{ModuleItemList, StatementList};
use boa_ast::StatementList;
use boa_gc::Gc; use boa_gc::Gc;
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
use boa_parser::{Error as ParseError, Parser}; use boa_parser::{Error as ParseError, Parser};
@ -65,7 +64,7 @@ use boa_profiler::Profiler;
/// let mut context = Context::default(); /// let mut context = Context::default();
/// ///
/// // Populate the script definition to the context. /// // Populate the script definition to the context.
/// context.eval(Source::from_bytes(script)).unwrap(); /// context.eval_script(Source::from_bytes(script)).unwrap();
/// ///
/// // Create an object that can be used in eval calls. /// // Create an object that can be used in eval calls.
/// let arg = ObjectInitializer::new(&mut context) /// let arg = ObjectInitializer::new(&mut context)
@ -73,7 +72,7 @@ use boa_profiler::Profiler;
/// .build(); /// .build();
/// context.register_global_property("arg", arg, Attribute::all()); /// context.register_global_property("arg", arg, Attribute::all());
/// ///
/// let value = context.eval(Source::from_bytes("test(arg)")).unwrap(); /// let value = context.eval_script(Source::from_bytes("test(arg)")).unwrap();
/// ///
/// assert_eq!(value.as_number(), Some(12.0)) /// assert_eq!(value.as_number(), Some(12.0))
/// ``` /// ```
@ -141,7 +140,7 @@ impl Context<'_> {
ContextBuilder::default() ContextBuilder::default()
} }
/// Evaluates the given script `Source` by compiling down to bytecode, then interpreting the /// Evaluates the given script `src` by compiling down to bytecode, then interpreting the
/// bytecode into a value. /// bytecode into a value.
/// ///
/// # Examples /// # Examples
@ -150,7 +149,7 @@ impl Context<'_> {
/// let mut context = Context::default(); /// let mut context = Context::default();
/// ///
/// let source = Source::from_bytes("1 + 3"); /// let source = Source::from_bytes("1 + 3");
/// let value = context.eval(source).unwrap(); /// let value = context.eval_script(source).unwrap();
/// ///
/// assert!(value.is_number()); /// assert!(value.is_number());
/// assert_eq!(value.as_number().unwrap(), 4.0); /// assert_eq!(value.as_number().unwrap(), 4.0);
@ -159,11 +158,41 @@ impl Context<'_> {
/// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`] /// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`]
/// on the context or [`JobQueue::run_jobs`] on the provided queue to run them. /// on the context or [`JobQueue::run_jobs`] on the provided queue to run them.
#[allow(clippy::unit_arg, clippy::drop_copy)] #[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval<R: Read>(&mut self, src: Source<'_, R>) -> JsResult<JsValue> { pub fn eval_script<R: Read>(&mut self, src: Source<'_, R>) -> JsResult<JsValue> {
let main_timer = Profiler::global().start_event("Evaluation", "Main"); 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);
// The main_timer needs to be dropped before the Profiler is.
drop(main_timer);
Profiler::global().drop();
result
}
/// Evaluates the given module `src` by compiling down to bytecode, then interpreting the
/// bytecode into a value.
///
/// # Examples
/// ```
/// # use boa_engine::{Context, Source};
/// let mut context = Context::default();
///
/// let source = Source::from_bytes("1 + 3");
///
/// let value = context.eval_module(source).unwrap();
///
/// assert!(value.is_number());
/// assert_eq!(value.as_number().unwrap(), 4.0);
/// ```
#[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval_module<R: Read>(&mut self, src: Source<'_, R>) -> JsResult<JsValue> {
let main_timer = Profiler::global().start_event("Module evaluation", "Main");
let script = self.parse(src)?; let module_item_list = self.parse_module(src)?;
let code_block = self.compile(&script)?; let code_block = self.compile_module(&module_item_list)?;
let result = self.execute(code_block); let result = self.execute(code_block);
// The main_timer needs to be dropped before the Profiler is. // The main_timer needs to be dropped before the Profiler is.
@ -174,30 +203,54 @@ impl Context<'_> {
} }
/// Parse the given source script. /// Parse the given source script.
pub fn parse<R: Read>(&mut self, src: Source<'_, R>) -> Result<StatementList, ParseError> { pub fn parse_script<R: Read>(
let _timer = Profiler::global().start_event("Parsing", "Main"); &mut self,
src: Source<'_, R>,
) -> Result<StatementList, ParseError> {
let _timer = Profiler::global().start_event("Script parsing", "Main");
let mut parser = Parser::new(src); let mut parser = Parser::new(src);
if self.strict { if self.strict {
parser.set_strict(); parser.set_strict();
} }
parser.parse_all(&mut self.interner) parser.parse_script(&mut self.interner)
} }
/// Compile the AST into a `CodeBlock` ready to be executed by the VM. /// Parse the given source script.
pub fn compile(&mut self, statement_list: &StatementList) -> JsResult<Gc<CodeBlock>> { pub fn parse_module<R: Read>(
let _timer = Profiler::global().start_event("Compilation", "Main"); &mut self,
src: Source<'_, R>,
) -> Result<ModuleItemList, ParseError> {
let _timer = Profiler::global().start_event("Module parsing", "Main");
let mut parser = Parser::new(src);
parser.parse_module(&mut self.interner)
}
/// 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); let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), false, self);
compiler.create_decls(statement_list, false); compiler.create_script_decls(statement_list, false);
compiler.compile_statement_list(statement_list, true, false)?; compiler.compile_statement_list(statement_list, true, false)?;
Ok(Gc::new(compiler.finish())) Ok(Gc::new(compiler.finish()))
} }
/// Compile the module AST into a `CodeBlock` ready to be executed by the VM.
pub fn compile_module(&mut self, statement_list: &ModuleItemList) -> JsResult<Gc<CodeBlock>> {
let _timer = Profiler::global().start_event("Module compilation", "Main");
let mut compiler = ByteCompiler::new(Sym::MAIN, true, false, self);
compiler.create_module_decls(statement_list, false);
compiler.compile_module_item_list(statement_list, false)?;
Ok(Gc::new(compiler.finish()))
}
/// Call the VM with a `CodeBlock` and return the result. /// 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 /// 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 /// 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 /// times, there is no need to re-compile it, and you can just call `clone()` on the
/// `Gc<CodeBlock>` returned by the [`Self::compile()`] function. /// `Gc<CodeBlock>` returned by the [`Context::compile_script`] or [`Context::compile_module`]
/// functions.
/// ///
/// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`] /// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`]
/// on the context or [`JobQueue::run_jobs`] on the provided queue to run them. /// on the context or [`JobQueue::run_jobs`] on the provided queue to run them.
@ -373,7 +426,7 @@ impl Context<'_> {
self.vm.trace = trace; self.vm.trace = trace;
} }
/// Executes all code in strict mode. /// Changes the strictness mode of the context.
pub fn strict(&mut self, strict: bool) { pub fn strict(&mut self, strict: bool) {
self.strict = strict; self.strict = strict;
} }
@ -417,7 +470,7 @@ impl Context<'_> {
) -> JsResult<Gc<CodeBlock>> { ) -> JsResult<Gc<CodeBlock>> {
let _timer = Profiler::global().start_event("Compilation", "Main"); let _timer = Profiler::global().start_event("Compilation", "Main");
let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), true, self); let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), true, self);
compiler.create_decls(statement_list, false); compiler.create_script_decls(statement_list, false);
compiler.compile_statement_list(statement_list, true, false)?; compiler.compile_statement_list(statement_list, true, false)?;
Ok(Gc::new(compiler.finish())) Ok(Gc::new(compiler.finish()))
} }

6
boa_engine/src/lib.rs

@ -189,7 +189,7 @@ where
S: AsRef<[u8]> + ?Sized, S: AsRef<[u8]> + ?Sized,
{ {
context context
.eval(Source::from_bytes(src)) .eval_script(Source::from_bytes(src))
.map_or_else(|e| format!("Uncaught {e}"), |v| v.display().to_string()) .map_or_else(|e| format!("Uncaught {e}"), |v| v.display().to_string())
} }
@ -207,7 +207,7 @@ pub(crate) fn forward_val<T: AsRef<[u8]> + ?Sized>(
let main_timer = Profiler::global().start_event("Main", "Main"); let main_timer = Profiler::global().start_event("Main", "Main");
let result = context.eval(Source::from_bytes(src)); let result = context.eval_script(Source::from_bytes(src));
// The main_timer needs to be dropped before the Profiler is. // The main_timer needs to be dropped before the Profiler is.
drop(main_timer); drop(main_timer);
@ -219,7 +219,7 @@ pub(crate) fn forward_val<T: AsRef<[u8]> + ?Sized>(
/// Create a clean Context and execute the code /// Create a clean Context and execute the code
#[cfg(test)] #[cfg(test)]
pub(crate) fn exec<T: AsRef<[u8]> + ?Sized>(src: &T) -> String { pub(crate) fn exec<T: AsRef<[u8]> + ?Sized>(src: &T) -> String {
match Context::default().eval(Source::from_bytes(src)) { match Context::default().eval_script(Source::from_bytes(src)) {
Ok(value) => value.display().to_string(), Ok(value) => value.display().to_string(),
Err(error) => error.to_string(), Err(error) => error.to_string(),
} }

4
boa_engine/src/tests.rs

@ -456,7 +456,9 @@ fn test_invalid_break_target() {
} }
"#; "#;
assert!(Context::default().eval(Source::from_bytes(src)).is_err()); assert!(Context::default()
.eval_script(Source::from_bytes(src))
.is_err());
} }
#[test] #[test]

12
boa_engine/src/value/serde_json.rs

@ -227,7 +227,7 @@ mod tests {
let mut context = Context::default(); let mut context = Context::default();
let add = context let add = context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
r#" r#"
1000000 + 500 1000000 + 500
"#, "#,
@ -237,7 +237,7 @@ mod tests {
assert_eq!(add, 1_000_500); assert_eq!(add, 1_000_500);
let sub = context let sub = context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
r#" r#"
1000000 - 500 1000000 - 500
"#, "#,
@ -247,7 +247,7 @@ mod tests {
assert_eq!(sub, 999_500); assert_eq!(sub, 999_500);
let mult = context let mult = context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
r#" r#"
1000000 * 500 1000000 * 500
"#, "#,
@ -257,7 +257,7 @@ mod tests {
assert_eq!(mult, 500_000_000); assert_eq!(mult, 500_000_000);
let div = context let div = context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
r#" r#"
1000000 / 500 1000000 / 500
"#, "#,
@ -267,7 +267,7 @@ mod tests {
assert_eq!(div, 2000); assert_eq!(div, 2000);
let rem = context let rem = context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
r#" r#"
233894 % 500 233894 % 500
"#, "#,
@ -277,7 +277,7 @@ mod tests {
assert_eq!(rem, 394); assert_eq!(rem, 394);
let pow = context let pow = context
.eval(Source::from_bytes( .eval_script(Source::from_bytes(
r#" r#"
36 ** 5 36 ** 5
"#, "#,

30
boa_engine/src/vm/tests.rs

@ -47,7 +47,7 @@ fn try_catch_finally_from_init() {
assert_eq!( assert_eq!(
Context::default() Context::default()
.eval(Source::from_bytes(source)) .eval_script(Source::from_bytes(source))
.unwrap_err() .unwrap_err()
.as_opaque() .as_opaque()
.unwrap(), .unwrap(),
@ -70,7 +70,9 @@ fn multiple_catches() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(Source::from_bytes(source)).unwrap(), Context::default()
.eval_script(Source::from_bytes(source))
.unwrap(),
JsValue::Undefined JsValue::Undefined
); );
} }
@ -89,7 +91,9 @@ fn use_last_expr_try_block() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(Source::from_bytes(source)).unwrap(), Context::default()
.eval_script(Source::from_bytes(source))
.unwrap(),
JsValue::from("Hello!") JsValue::from("Hello!")
); );
} }
@ -107,7 +111,9 @@ fn use_last_expr_catch_block() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(Source::from_bytes(source)).unwrap(), Context::default()
.eval_script(Source::from_bytes(source))
.unwrap(),
JsValue::from("Hello!") JsValue::from("Hello!")
); );
} }
@ -123,7 +129,9 @@ fn no_use_last_expr_finally_block() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(Source::from_bytes(source)).unwrap(), Context::default()
.eval_script(Source::from_bytes(source))
.unwrap(),
JsValue::undefined() JsValue::undefined()
); );
} }
@ -142,7 +150,9 @@ fn finally_block_binding_env() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(Source::from_bytes(source)).unwrap(), Context::default()
.eval_script(Source::from_bytes(source))
.unwrap(),
JsValue::from("Hey hey people") JsValue::from("Hey hey people")
); );
} }
@ -161,7 +171,9 @@ fn run_super_method_in_object() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(Source::from_bytes(source)).unwrap(), Context::default()
.eval_script(Source::from_bytes(source))
.unwrap(),
JsValue::from("super") JsValue::from("super")
); );
} }
@ -187,7 +199,9 @@ fn get_reference_by_super() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(Source::from_bytes(source)).unwrap(), Context::default()
.eval_script(Source::from_bytes(source))
.unwrap(),
JsValue::from("ab") JsValue::from("ab")
); );
} }

2
boa_examples/src/bin/classes.rs

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

13
boa_examples/src/bin/closures.rs

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

2
boa_examples/src/bin/commuter_visitor.rs

@ -69,7 +69,7 @@ fn main() {
Parser::new(Source::from_filepath(Path::new("boa_examples/scripts/calc.js")).unwrap()); Parser::new(Source::from_filepath(Path::new("boa_examples/scripts/calc.js")).unwrap());
let mut ctx = Context::default(); let mut ctx = Context::default();
let mut statements = parser.parse_all(ctx.interner_mut()).unwrap(); let mut statements = parser.parse_script(ctx.interner_mut()).unwrap();
let mut visitor = CommutorVisitor::default(); let mut visitor = CommutorVisitor::default();

2
boa_examples/src/bin/loadfile.rs

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

2
boa_examples/src/bin/loadstring.rs

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

6
boa_examples/src/bin/modulehandler.rs

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

2
boa_examples/src/bin/symbol_visitor.rs

@ -28,7 +28,7 @@ fn main() {
Parser::new(Source::from_filepath(Path::new("boa_examples/scripts/calc.js")).unwrap()); Parser::new(Source::from_filepath(Path::new("boa_examples/scripts/calc.js")).unwrap());
let mut ctx = Context::default(); let mut ctx = Context::default();
let statements = parser.parse_all(ctx.interner_mut()).unwrap(); let statements = parser.parse_script(ctx.interner_mut()).unwrap();
let mut visitor = SymbolVisitor::default(); let mut visitor = SymbolVisitor::default();

7
boa_interner/src/sym.rs

@ -122,7 +122,7 @@ static_syms! {
"yield", "yield",
// End strict reserved identifiers // End strict reserved identifiers
"", ("", EMPTY_STRING),
"prototype", "prototype",
"constructor", "constructor",
"arguments", "arguments",
@ -130,13 +130,16 @@ static_syms! {
"RegExp", "RegExp",
"get", "get",
"set", "set",
"<main>", ("<main>", MAIN),
"raw", "raw",
"anonymous", "anonymous",
"async", "async",
"of", "of",
"target", "target",
"as",
"from",
"__proto__", "__proto__",
"name", "name",
"await", "await",
("*default*", DEFAULT_EXPORT)
} }

91
boa_macros/src/lib.rs

@ -59,20 +59,76 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Ident; use proc_macro2::Ident;
use quote::quote; use quote::{quote, ToTokens};
use syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
parse_macro_input, parse_macro_input,
punctuated::Punctuated, punctuated::Punctuated,
LitStr, Token, Expr, ExprLit, Lit, LitStr, Token,
}; };
use synstructure::{decl_derive, AddBounds, Structure}; use synstructure::{decl_derive, AddBounds, Structure};
struct Syms(Vec<LitStr>); struct Static {
literal: LitStr,
ident: Ident,
}
impl Parse for Static {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let expr = Expr::parse(input)?;
match expr {
Expr::Tuple(expr) => {
let mut elems = expr.elems.iter().cloned();
let literal = elems
.next()
.ok_or_else(|| syn::Error::new_spanned(&expr, "invalid empty tuple"))?;
let ident = elems.next();
if elems.next().is_some() {
return Err(syn::Error::new_spanned(
&expr,
"invalid tuple with more than two elements",
));
}
let Expr::Lit(ExprLit {
lit: Lit::Str(literal), ..
}) = literal else {
return Err(syn::Error::new_spanned(
literal,
"expected an UTF-8 string literal",
));
};
let ident = if let Some(ident) = ident {
syn::parse2::<Ident>(ident.into_token_stream())?
} else {
Ident::new(&literal.value().to_uppercase(), literal.span())
};
Ok(Self { literal, ident })
}
Expr::Lit(expr) => match expr.lit {
Lit::Str(str) => Ok(Self {
ident: Ident::new(&str.value().to_uppercase(), str.span()),
literal: str,
}),
_ => Err(syn::Error::new_spanned(
expr,
"expected an UTF-8 string literal",
)),
},
_ => Err(syn::Error::new_spanned(
expr,
"expected a string literal or a tuple expression",
)),
}
}
}
struct Syms(Vec<Static>);
impl Parse for Syms { impl Parse for Syms {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> { fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let parsed = Punctuated::<LitStr, Token![,]>::parse_terminated(input)?; let parsed = Punctuated::<Static, Token![,]>::parse_terminated(input)?;
let literals = parsed.into_iter().collect(); let literals = parsed.into_iter().collect();
Ok(Self(literals)) Ok(Self(literals))
} }
@ -84,22 +140,15 @@ pub fn static_syms(input: TokenStream) -> TokenStream {
let literals = parse_macro_input!(input as Syms).0; let literals = parse_macro_input!(input as Syms).0;
let consts = literals.iter().enumerate().map(|(mut idx, lit)| { let consts = literals.iter().enumerate().map(|(mut idx, lit)| {
let ident = lit.value(); let doc = format!(
let (doc, ident) = match &*ident { "Symbol for the \"{}\" string.",
"" => ( lit.literal
String::from("Symbol for the empty string."), .value()
String::from("EMPTY_STRING"), .replace('<', r"\<")
), .replace('>', r"\>")
"<main>" => ( .replace('*', r"\*")
String::from("Symbol for the `<main>` string."), );
String::from("MAIN"), let ident = &lit.ident;
),
ident => (
format!("Symbol for the `{ident}` string.",),
ident.to_uppercase(),
),
};
let ident = Ident::new(&ident, lit.span());
idx += 1; idx += 1;
quote! { quote! {
#[doc = #doc] #[doc = #doc]
@ -107,6 +156,8 @@ pub fn static_syms(input: TokenStream) -> TokenStream {
} }
}); });
let literals = literals.iter().map(|lit| &lit.literal).collect::<Vec<_>>();
let caches = quote! { let caches = quote! {
type Set<T> = ::indexmap::IndexSet<T, ::core::hash::BuildHasherDefault<::rustc_hash::FxHasher>>; type Set<T> = ::indexmap::IndexSet<T, ::core::hash::BuildHasherDefault<::rustc_hash::FxHasher>>;

21
boa_parser/src/error.rs

@ -66,7 +66,7 @@ pub enum Error {
/// Catch all General Error /// Catch all General Error
General { General {
/// The error message. /// The error message.
message: &'static str, message: Box<str>,
/// Position of the source code where the error occurred. /// Position of the source code where the error occurred.
position: Position, position: Position,
@ -115,22 +115,29 @@ impl Error {
} }
/// Creates a "general" parsing error. /// Creates a "general" parsing error.
pub(crate) const fn general(message: &'static str, position: Position) -> Self { pub(crate) fn general<S>(message: S, position: Position) -> Self
Self::General { message, position } where
S: Into<Box<str>>,
{
Self::General {
message: message.into(),
position,
}
} }
/// Creates a "general" parsing error with the specific error message for a wrong function declaration in non-strict mode. /// Creates a "general" parsing error with the specific error message for a wrong function declaration in non-strict mode.
pub(crate) const fn wrong_function_declaration_non_strict(position: Position) -> Self { pub(crate) fn wrong_function_declaration_non_strict(position: Position) -> Self {
Self::General { Self::General {
message: "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.", message: "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.".into(),
position position
} }
} }
/// Creates a "general" parsing error with the specific error message for a wrong function declaration with label. /// Creates a "general" parsing error with the specific error message for a wrong function declaration with label.
pub(crate) const fn wrong_labelled_function_declaration(position: Position) -> Self { pub(crate) fn wrong_labelled_function_declaration(position: Position) -> Self {
Self::General { Self::General {
message: "Labelled functions can only be declared at top level or inside a block", message: "Labelled functions can only be declared at top level or inside a block"
.into(),
position, position,
} }
} }

1
boa_parser/src/lexer/error.rs

@ -33,6 +33,7 @@ impl From<io::Error> for Error {
impl Error { impl Error {
/// Creates a new syntax error. /// Creates a new syntax error.
#[inline]
pub(super) fn syntax<M, P>(err: M, pos: P) -> Self pub(super) fn syntax<M, P>(err: M, pos: P) -> Self
where where
M: Into<Box<str>>, M: Into<Box<str>>,

2
boa_parser/src/lexer/token.rs

@ -48,6 +48,7 @@ impl Token {
} }
/// Converts the token to a `String`. /// Converts the token to a `String`.
#[inline]
pub(crate) fn to_string(&self, interner: &Interner) -> String { pub(crate) fn to_string(&self, interner: &Interner) -> String {
self.kind.to_string(interner) self.kind.to_string(interner)
} }
@ -198,6 +199,7 @@ impl TokenKind {
} }
/// Creates a `NumericLiteral` token kind. /// Creates a `NumericLiteral` token kind.
#[must_use]
pub fn numeric_literal<L>(lit: L) -> Self pub fn numeric_literal<L>(lit: L) -> Self
where where
L: Into<Numeric>, L: Into<Numeric>,

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

@ -161,6 +161,7 @@ where
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
Ok(ast::function::ArrowFunction::new(self.name, params, body)) Ok(ast::function::ArrowFunction::new(self.name, params, body))

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

@ -147,6 +147,7 @@ where
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
Ok(ast::function::AsyncArrowFunction::new( Ok(ast::function::AsyncArrowFunction::new(

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

@ -228,6 +228,7 @@ where
&bound_names(&parameters), &bound_names(&parameters),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
position, position,
interner,
)?; )?;
return Ok(boa_ast::function::ArrowFunction::new(self.name, parameters, body).into()); return Ok(boa_ast::function::ArrowFunction::new(self.name, parameters, body).into());

2
boa_parser/src/parser/expression/identifiers.rs

@ -29,6 +29,7 @@ pub(in crate::parser) struct IdentifierReference {
impl IdentifierReference { impl IdentifierReference {
/// Creates a new `IdentifierReference` parser. /// Creates a new `IdentifierReference` parser.
#[inline]
pub(in crate::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self pub(in crate::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
@ -82,6 +83,7 @@ pub(in crate::parser) struct BindingIdentifier {
impl BindingIdentifier { impl BindingIdentifier {
/// Creates a new `BindingIdentifier` parser. /// Creates a new `BindingIdentifier` parser.
#[inline]
pub(in crate::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self pub(in crate::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,

12
boa_parser/src/parser/expression/left_hand_side/optional/tests.rs

@ -1,7 +1,5 @@
use boa_interner::Interner; use crate::parser::tests::{check_invalid_script, check_script_parser};
use boa_macros::utf16;
use crate::parser::tests::{check_invalid, check_script_parser};
use boa_ast::{ use boa_ast::{
expression::{ expression::{
access::PropertyAccessField, literal::Literal, Identifier, Optional, OptionalOperation, access::PropertyAccessField, literal::Literal, Identifier, Optional, OptionalOperation,
@ -9,6 +7,8 @@ use boa_ast::{
}, },
Expression, Statement, Expression, Statement,
}; };
use boa_interner::Interner;
use boa_macros::utf16;
#[test] #[test]
fn simple() { fn simple() {
@ -81,9 +81,9 @@ fn complex_chain() {
#[test] #[test]
fn reject_templates() { fn reject_templates() {
check_invalid("console.log?.`Hello`"); check_invalid_script("console.log?.`Hello`");
check_invalid("console?.log`Hello`"); check_invalid_script("console?.log`Hello`");
check_invalid( check_invalid_script(
r#" r#"
const a = console?.log const a = console?.log
`Hello`"#, `Hello`"#,

6
boa_parser/src/parser/expression/mod.rs

@ -773,14 +773,14 @@ expression!(
); );
/// Returns an error if `arguments` or `eval` are used as identifier in strict mode. /// Returns an error if `arguments` or `eval` are used as identifier in strict mode.
const fn check_strict_arguments_or_eval(ident: Identifier, position: Position) -> ParseResult<()> { fn check_strict_arguments_or_eval(ident: Identifier, position: Position) -> ParseResult<()> {
match ident.sym() { match ident.sym() {
Sym::ARGUMENTS => Err(Error::general( Sym::ARGUMENTS => Err(Error::general(
"unexpected identifier 'arguments' in strict mode", "unexpected identifier `arguments` in strict mode",
position, position,
)), )),
Sym::EVAL => Err(Error::general( Sym::EVAL => Err(Error::general(
"unexpected identifier 'eval' in strict mode", "unexpected identifier `eval` in strict mode",
position, position,
)), )),
_ => Ok(()), _ => Ok(()),

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

@ -142,6 +142,7 @@ where
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
let function = AsyncFunction::new(name.or(self.name), params, body, name.is_some()); let function = AsyncFunction::new(name.or(self.name), params, body, name.is_some());

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

@ -179,6 +179,7 @@ where
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
let function = AsyncGenerator::new(name.or(self.name), params, body, name.is_some()); let function = AsyncGenerator::new(name.or(self.name), params, body, name.is_some());

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

@ -1,5 +1,3 @@
use std::convert::TryInto;
use crate::parser::tests::check_script_parser; use crate::parser::tests::check_script_parser;
use boa_ast::{ use boa_ast::{
declaration::{LexicalDeclaration, Variable}, declaration::{LexicalDeclaration, Variable},

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

@ -137,6 +137,7 @@ where
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
let function = let function =

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

@ -145,6 +145,7 @@ where
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
// It is a Syntax Error if FormalParameters Contains YieldExpression is true. // It is a Syntax Error if FormalParameters Contains YieldExpression is true.

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

@ -436,6 +436,7 @@ where
&bound_names(&parameters), &bound_names(&parameters),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
let name = property_name.literal().map(|name| { let name = property_name.literal().map(|name| {
@ -511,6 +512,7 @@ where
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
let method = MethodDefinition::Ordinary(Function::new( let method = MethodDefinition::Ordinary(Function::new(
@ -592,10 +594,17 @@ where
} }
TokenKind::NullLiteral => (Sym::NULL).into(), TokenKind::NullLiteral => (Sym::NULL).into(),
TokenKind::BooleanLiteral(bool) => match bool { TokenKind::BooleanLiteral(bool) => match bool {
true => (interner.get_or_intern_static("true", utf16!("true"))).into(), true => Sym::TRUE.into(),
false => (interner.get_or_intern_static("false", utf16!("false"))).into(), false => Sym::FALSE.into(),
}, },
_ => return Err(Error::AbruptEnd), _ => {
return Err(Error::expected(
vec!["property name".to_owned()],
token.to_string(interner),
token.span(),
"property name",
))
}
}; };
cursor.advance(interner); cursor.advance(interner);
Ok(name) Ok(name)
@ -786,6 +795,7 @@ where
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
let method = MethodDefinition::Generator(Generator::new( let method = MethodDefinition::Generator(Generator::new(
@ -901,6 +911,7 @@ where
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
let method = MethodDefinition::AsyncGenerator(AsyncGenerator::new( let method = MethodDefinition::AsyncGenerator(AsyncGenerator::new(
@ -993,6 +1004,7 @@ where
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
let method = MethodDefinition::Async(AsyncFunction::new( let method = MethodDefinition::Async(AsyncFunction::new(

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

@ -1,4 +1,4 @@
use crate::parser::tests::{check_invalid, check_script_parser}; use crate::parser::tests::{check_invalid_script, check_script_parser};
use boa_ast::{ use boa_ast::{
declaration::{LexicalDeclaration, Variable}, declaration::{LexicalDeclaration, Variable},
expression::{ expression::{
@ -12,7 +12,7 @@ use boa_ast::{
property::{MethodDefinition, PropertyDefinition, PropertyName}, property::{MethodDefinition, PropertyDefinition, PropertyName},
Declaration, StatementList, Declaration, StatementList,
}; };
use boa_interner::Interner; use boa_interner::{Interner, Sym};
use boa_macros::utf16; use boa_macros::utf16;
/// Checks object literal parsing. /// Checks object literal parsing.
@ -239,7 +239,7 @@ fn check_object_short_function_get() {
let interner = &mut Interner::default(); let interner = &mut Interner::default();
let object_properties = vec![PropertyDefinition::MethodDefinition( let object_properties = vec![PropertyDefinition::MethodDefinition(
interner.get_or_intern_static("get", utf16!("get")).into(), Sym::GET.into(),
MethodDefinition::Ordinary(Function::new( MethodDefinition::Ordinary(Function::new(
Some(interner.get_or_intern_static("get", utf16!("get")).into()), Some(interner.get_or_intern_static("get", utf16!("get")).into()),
FormalParameterList::default(), FormalParameterList::default(),
@ -270,7 +270,7 @@ fn check_object_short_function_set() {
let interner = &mut Interner::default(); let interner = &mut Interner::default();
let object_properties = vec![PropertyDefinition::MethodDefinition( let object_properties = vec![PropertyDefinition::MethodDefinition(
interner.get_or_intern_static("set", utf16!("set")).into(), Sym::SET.into(),
MethodDefinition::Ordinary(Function::new( MethodDefinition::Ordinary(Function::new(
Some(interner.get_or_intern_static("set", utf16!("set")).into()), Some(interner.get_or_intern_static("set", utf16!("set")).into()),
FormalParameterList::default(), FormalParameterList::default(),
@ -485,7 +485,7 @@ fn check_async_generator_method() {
#[test] #[test]
fn check_async_method_lineterminator() { fn check_async_method_lineterminator() {
check_invalid( check_invalid_script(
"const x = { "const x = {
async async
dive(){} dive(){}
@ -496,7 +496,7 @@ fn check_async_method_lineterminator() {
#[test] #[test]
fn check_async_gen_method_lineterminator() { fn check_async_gen_method_lineterminator() {
check_invalid( check_invalid_script(
"const x = { "const x = {
async async
* vroom() {} * vroom() {}

10
boa_parser/src/parser/expression/tests.rs

@ -1,4 +1,4 @@
use crate::parser::tests::{check_invalid, check_script_parser}; use crate::parser::tests::{check_invalid_script, check_script_parser};
use boa_ast::{ use boa_ast::{
declaration::{LexicalDeclaration, Variable}, declaration::{LexicalDeclaration, Variable},
expression::{ expression::{
@ -684,10 +684,10 @@ fn check_logical_expressions() {
interner, interner,
); );
check_invalid("a ?? b && c"); check_invalid_script("a ?? b && c");
check_invalid("a && b ?? c"); check_invalid_script("a && b ?? c");
check_invalid("a ?? b || c"); check_invalid_script("a ?? b || c");
check_invalid("a || b ?? c"); check_invalid_script("a || b ?? c");
} }
macro_rules! check_non_reserved_identifier { macro_rules! check_non_reserved_identifier {

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

@ -1,4 +1,4 @@
use crate::parser::tests::{check_invalid, check_script_parser}; use crate::parser::tests::{check_invalid_script, check_script_parser};
use boa_ast::{ use boa_ast::{
declaration::{LexicalDeclaration, Variable}, declaration::{LexicalDeclaration, Variable},
expression::{ expression::{
@ -81,7 +81,7 @@ fn check_duplicates_strict_off() {
/// Checks if duplicate parameter names are an error with strict mode on. /// Checks if duplicate parameter names are an error with strict mode on.
#[test] #[test]
fn check_duplicates_strict_on() { fn check_duplicates_strict_on() {
check_invalid("'use strict'; function foo(a, a) {}"); check_invalid_script("'use strict'; function foo(a, a) {}");
} }
/// Checks basic function declaration parsing with automatic semicolon insertion. /// Checks basic function declaration parsing with automatic semicolon insertion.

149
boa_parser/src/parser/mod.rs

@ -21,9 +21,10 @@ use boa_ast::{
expression::Identifier, expression::Identifier,
function::FormalParameterList, function::FormalParameterList,
operations::{ operations::{
contains, top_level_lexically_declared_names, top_level_var_declared_names, ContainsSymbol, contains, lexically_declared_names, top_level_lexically_declared_names,
top_level_var_declared_names, var_declared_names, ContainsSymbol,
}, },
Position, StatementList, ModuleItemList, Position, StatementList,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
@ -130,10 +131,25 @@ impl<'a, R: Read> Parser<'a, R> {
/// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-Script /// [spec]: https://tc39.es/ecma262/#prod-Script
pub fn parse_all(&mut self, interner: &mut Interner) -> ParseResult<StatementList> { pub fn parse_script(&mut self, interner: &mut Interner) -> ParseResult<StatementList> {
Script::new(false).parse(&mut self.cursor, interner) Script::new(false).parse(&mut self.cursor, interner)
} }
/// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation.
/// The resulting `ModuleItemList` 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-Module
pub fn parse_module(&mut self, interner: &mut Interner) -> ParseResult<ModuleItemList>
where
R: Read,
{
Module.parse(&mut self.cursor, interner)
}
/// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec] /// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec]
/// ///
/// Parses the source text input of an `eval` call. /// Parses the source text input of an `eval` call.
@ -232,7 +248,6 @@ where
.parse(cursor, interner)?; .parse(cursor, interner)?;
// It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries. // It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries.
// It is a Syntax Error if any element of the LexicallyDeclaredNames of ScriptBody also occurs in the VarDeclaredNames of ScriptBody.
let mut lexical_names = FxHashSet::default(); let mut lexical_names = FxHashSet::default();
for name in top_level_lexically_declared_names(&statement_list) { for name in top_level_lexically_declared_names(&statement_list) {
if !lexical_names.insert(name) { if !lexical_names.insert(name) {
@ -243,6 +258,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 top_level_var_declared_names(&statement_list) {
if lexical_names.contains(&name) { if lexical_names.contains(&name) {
return Err(Error::general( return Err(Error::general(
@ -314,6 +330,13 @@ where
Position::new(1, 1), Position::new(1, 1),
)); ));
} }
// TODO:
// It is a Syntax Error if ContainsDuplicateLabels of StatementList with argument « » is true.
// It is a Syntax Error if ContainsUndefinedBreakTarget of StatementList with argument « » is true.
// It is a Syntax Error if ContainsUndefinedContinueTarget of StatementList with arguments « » and « » is true.
// It is a Syntax Error if AllPrivateIdentifiersValid of StatementList with argument « » is false unless the
// source text containing ScriptBody is eval code that is being processed by a direct eval.
} }
Ok(body) Ok(body)
@ -325,13 +348,17 @@ fn name_in_lexically_declared_names(
bound_names: &[Identifier], bound_names: &[Identifier],
lexical_names: &[Identifier], lexical_names: &[Identifier],
position: Position, position: Position,
interner: &Interner,
) -> ParseResult<()> { ) -> ParseResult<()> {
for name in bound_names { for name in bound_names {
if lexical_names.contains(name) { if lexical_names.contains(name) {
return Err(Error::General { return Err(Error::general(
message: "formal parameter declared in lexically declared names", format!(
"formal parameter `{}` declared in lexically declared names",
interner.resolve_expect(name.sym())
),
position, position,
}); ));
} }
} }
Ok(()) Ok(())
@ -348,3 +375,111 @@ impl<T> OrAbrupt<T> for ParseResult<Option<T>> {
self?.ok_or(Error::AbruptEnd) self?.ok_or(Error::AbruptEnd)
} }
} }
/// Parses a full module.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-Module
#[derive(Debug, Clone, Copy)]
struct Module;
impl<R> TokenParser<R> for Module
where
R: Read,
{
type Output = ModuleItemList;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
cursor.set_module_mode();
let items = if cursor.peek(0, interner)?.is_some() {
self::statement::ModuleItemList.parse(cursor, interner)?
} else {
return Ok(Vec::new().into());
};
// It is a Syntax Error if the LexicallyDeclaredNames of ModuleItemList contains any duplicate entries.
let mut bindings = FxHashSet::default();
for name in lexically_declared_names(&items) {
if !bindings.insert(name) {
return Err(Error::general(
format!(
"lexical name `{}` declared multiple times",
interner.resolve_expect(name.sym())
),
Position::new(1, 1),
));
}
}
// 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) {
if !bindings.insert(name) {
return Err(Error::general(
format!(
"lexical name `{}` declared multiple times",
interner.resolve_expect(name.sym())
),
Position::new(1, 1),
));
}
}
// 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() {
if !exported_names.insert(name) {
return Err(Error::general(
format!(
"exported name `{}` declared multiple times",
interner.resolve_expect(name)
),
Position::new(1, 1),
));
}
}
}
// 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() {
if !bindings.contains(&name) {
return Err(Error::general(
format!(
"could not find the exported binding `{}` in the declared names of the module",
interner.resolve_expect(name.sym())
),
Position::new(1, 1),
));
}
}
// It is a Syntax Error if ModuleItemList Contains super.
if contains(&items, ContainsSymbol::Super) {
return Err(Error::general(
"module cannot contain `super` on the top-level",
Position::new(1, 1),
));
}
// It is a Syntax Error if ModuleItemList Contains NewTarget.
if contains(&items, 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.
// It is a Syntax Error if AllPrivateIdentifiersValid of ModuleItemList with argument « » is false.
Ok(items)
}
}

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

@ -1,7 +1,5 @@
//! Block statement parsing tests. //! Block statement parsing tests.
use std::convert::TryInto;
use crate::parser::tests::check_script_parser; use crate::parser::tests::check_script_parser;
use boa_ast::{ use boa_ast::{
declaration::{VarDeclaration, Variable}, declaration::{VarDeclaration, Variable},

10
boa_parser/src/parser/statement/break_stm/tests.rs

@ -4,7 +4,7 @@ use boa_ast::{
statement::{Block, Break, WhileLoop}, statement::{Block, Break, WhileLoop},
Statement, StatementListItem, Statement, StatementListItem,
}; };
use boa_interner::Interner; use boa_interner::{Interner, Sym};
use boa_macros::utf16; use boa_macros::utf16;
#[test] #[test]
@ -114,9 +114,7 @@ fn reserved_label() {
vec![Statement::WhileLoop(WhileLoop::new( vec![Statement::WhileLoop(WhileLoop::new(
Literal::from(true).into(), Literal::from(true).into(),
Block::from(vec![StatementListItem::Statement(Statement::Break( Block::from(vec![StatementListItem::Statement(Statement::Break(
Break::new(Some( Break::new(Some(Sym::AWAIT)),
interner.get_or_intern_static("await", utf16!("await")),
)),
))]) ))])
.into(), .into(),
)) ))
@ -132,9 +130,7 @@ fn reserved_label() {
vec![Statement::WhileLoop(WhileLoop::new( vec![Statement::WhileLoop(WhileLoop::new(
Literal::from(true).into(), Literal::from(true).into(),
Block::from(vec![StatementListItem::Statement(Statement::Break( Block::from(vec![StatementListItem::Statement(Statement::Break(
Break::new(Some( Break::new(Some(Sym::YIELD)),
interner.get_or_intern_static("yield", utf16!("yield")),
)),
))]) ))])
.into(), .into(),
)) ))

10
boa_parser/src/parser/statement/continue_stm/tests.rs

@ -4,7 +4,7 @@ use boa_ast::{
statement::{Block, Continue, WhileLoop}, statement::{Block, Continue, WhileLoop},
Statement, StatementListItem, Statement, StatementListItem,
}; };
use boa_interner::Interner; use boa_interner::{Interner, Sym};
use boa_macros::utf16; use boa_macros::utf16;
#[test] #[test]
@ -114,9 +114,7 @@ fn reserved_label() {
vec![Statement::WhileLoop(WhileLoop::new( vec![Statement::WhileLoop(WhileLoop::new(
Literal::from(true).into(), Literal::from(true).into(),
Block::from(vec![StatementListItem::Statement(Statement::Continue( Block::from(vec![StatementListItem::Statement(Statement::Continue(
Continue::new(Some( Continue::new(Some(Sym::AWAIT)),
interner.get_or_intern_static("await", utf16!("await")),
)),
))]) ))])
.into(), .into(),
)) ))
@ -132,9 +130,7 @@ fn reserved_label() {
vec![Statement::WhileLoop(WhileLoop::new( vec![Statement::WhileLoop(WhileLoop::new(
Literal::from(true).into(), Literal::from(true).into(),
Block::from(vec![StatementListItem::Statement(Statement::Continue( Block::from(vec![StatementListItem::Statement(Statement::Continue(
Continue::new(Some( Continue::new(Some(Sym::YIELD)),
interner.get_or_intern_static("yield", utf16!("yield")),
)),
))]) ))])
.into(), .into(),
)) ))

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

@ -0,0 +1,316 @@
//! Export declaration parsing
//!
//! This parses `export` declarations.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript specification][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-exports
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
use crate::{
lexer::{token::ContainsEscapeSequence, TokenKind},
parser::{
cursor::Cursor,
expression::AssignmentExpression,
statement::{declaration::ClassDeclaration, variable::VariableStatement},
Error, OrAbrupt, ParseResult, TokenParser,
},
};
use boa_ast::{
declaration::{ExportDeclaration as AstExportDeclaration, ReExportKind},
Keyword, Punctuator,
};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use std::io::Read;
use super::{
hoistable::{AsyncFunctionDeclaration, AsyncGeneratorDeclaration, GeneratorDeclaration},
Declaration, FromClause, FunctionDeclaration,
};
/// Parses an export declaration.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ExportDeclaration
#[derive(Debug, Clone, Copy)]
pub(in crate::parser) struct ExportDeclaration;
impl<R> TokenParser<R> for ExportDeclaration
where
R: Read,
{
type Output = AstExportDeclaration;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("ExportDeclaration", "Parsing");
cursor.expect((Keyword::Export, false), "export declaration", interner)?;
let tok = cursor.peek(0, interner).or_abrupt()?;
let export_clause: Self::Output = match tok.kind() {
TokenKind::Punctuator(Punctuator::Mul) => {
cursor.advance(interner);
let next = cursor.peek(0, interner).or_abrupt()?;
match next.kind() {
TokenKind::IdentifierName((Sym::AS, _)) => {
cursor.advance(interner);
let tok = cursor.next(interner).or_abrupt()?;
let alias = match tok.kind() {
TokenKind::StringLiteral((export_name, _))
| TokenKind::IdentifierName((export_name, _)) => *export_name,
_ => {
return Err(Error::expected(
["identifier".to_owned(), "string literal".to_owned()],
tok.to_string(interner),
tok.span(),
"export declaration",
))
}
};
let specifier =
FromClause::new("export declaration").parse(cursor, interner)?;
AstExportDeclaration::ReExport {
kind: ReExportKind::Namespaced { name: Some(alias) },
specifier,
}
}
TokenKind::IdentifierName((Sym::FROM, _)) => {
let specifier =
FromClause::new("export declaration").parse(cursor, interner)?;
AstExportDeclaration::ReExport {
kind: ReExportKind::Namespaced { name: None },
specifier,
}
}
_ => {
return Err(Error::expected(
["as".to_owned(), "from".to_owned()],
next.to_string(interner),
next.span(),
"export declaration",
))
}
}
}
TokenKind::Punctuator(Punctuator::OpenBlock) => {
let names = NamedExports.parse(cursor, interner)?;
let next = cursor.peek(0, interner).or_abrupt()?;
if matches!(
next.kind(),
TokenKind::IdentifierName((Sym::FROM, ContainsEscapeSequence(false)))
) {
let specifier =
FromClause::new("export declaration").parse(cursor, interner)?;
AstExportDeclaration::ReExport {
kind: ReExportKind::Named { names },
specifier,
}
} else {
AstExportDeclaration::List(names)
}
}
TokenKind::Keyword((Keyword::Var, false)) => VariableStatement::new(false, true)
.parse(cursor, interner)
.map(AstExportDeclaration::VarStatement)?,
TokenKind::Keyword((Keyword::Default, _)) => {
cursor.advance(interner);
let tok = cursor.peek(0, interner).or_abrupt()?;
match tok.kind() {
TokenKind::Keyword((Keyword::Function, false)) => {
let next_token = cursor.peek(1, interner).or_abrupt()?;
if next_token.kind() == &TokenKind::Punctuator(Punctuator::Mul) {
AstExportDeclaration::DefaultGenerator(
GeneratorDeclaration::new(false, true, true)
.parse(cursor, interner)?,
)
} else {
AstExportDeclaration::DefaultFunction(
FunctionDeclaration::new(false, true, true)
.parse(cursor, interner)?,
)
}
}
TokenKind::Keyword((Keyword::Async, false)) => {
let next_token = cursor.peek(2, interner).or_abrupt()?;
if next_token.kind() == &TokenKind::Punctuator(Punctuator::Mul) {
AstExportDeclaration::DefaultAsyncGenerator(
AsyncGeneratorDeclaration::new(false, true, true)
.parse(cursor, interner)?,
)
} else {
AstExportDeclaration::DefaultAsyncFunction(
AsyncFunctionDeclaration::new(false, true, true)
.parse(cursor, interner)?,
)
}
}
TokenKind::Keyword((Keyword::Class, false)) => {
AstExportDeclaration::DefaultClassDeclaration(
ClassDeclaration::new(false, true, true).parse(cursor, interner)?,
)
}
_ => AstExportDeclaration::DefaultAssignmentExpression(
AssignmentExpression::new(None, true, false, true)
.parse(cursor, interner)?,
),
}
}
_ => AstExportDeclaration::Declaration(
Declaration::new(false, true).parse(cursor, interner)?,
),
};
Ok(export_clause)
}
}
/// Parses a named export list.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-NamedExports
#[derive(Debug, Clone, Copy)]
struct NamedExports;
impl<R> TokenParser<R> for NamedExports
where
R: Read,
{
type Output = Box<[boa_ast::declaration::ExportSpecifier]>;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
cursor.expect(Punctuator::OpenBlock, "export declaration", interner)?;
let mut list = Vec::new();
loop {
let tok = cursor.next(interner).or_abrupt()?;
match tok.kind() {
TokenKind::Punctuator(Punctuator::CloseBlock) => {
break;
}
TokenKind::Punctuator(Punctuator::Comma) => {
if list.is_empty() {
return Err(Error::expected(
[
Punctuator::CloseBlock.to_string(),
"string literal".to_owned(),
"identifier".to_owned(),
],
tok.to_string(interner),
tok.span(),
"export declaration",
));
}
}
TokenKind::StringLiteral(_) | TokenKind::IdentifierName(_) => {
list.push(ExportSpecifier.parse(cursor, interner)?);
}
_ => {
return Err(Error::expected(
[
Punctuator::CloseBlock.to_string(),
Punctuator::Comma.to_string(),
],
tok.to_string(interner),
tok.span(),
"export declaration",
));
}
}
}
Ok(list.into_boxed_slice())
}
}
/// Parses a module export name.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ModuleExportName
#[derive(Debug, Clone, Copy)]
pub(super) struct ModuleExportName;
impl<R> TokenParser<R> for ModuleExportName
where
R: Read,
{
type Output = Sym;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let tok = cursor.next(interner).or_abrupt()?;
match tok.kind() {
TokenKind::StringLiteral((ident, _)) => {
if interner.resolve_expect(*ident).utf8().is_none() {
return Err(Error::general(
"import specifiers don't allow unpaired surrogates",
tok.span().end(),
));
}
Ok(*ident)
}
TokenKind::IdentifierName((ident, _)) => Ok(*ident),
_ => Err(Error::expected(
["identifier".to_owned(), "string literal".to_owned()],
tok.to_string(interner),
tok.span(),
"export specifier parsing",
)),
}
}
}
/// Parses an export specifier.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ExportSpecifier
#[derive(Debug, Clone, Copy)]
struct ExportSpecifier;
impl<R> TokenParser<R> for ExportSpecifier
where
R: Read,
{
type Output = boa_ast::declaration::ExportSpecifier;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let inner_name = ModuleExportName.parse(cursor, interner)?;
if cursor
.next_if(TokenKind::identifier(Sym::AS), interner)?
.is_some()
{
let export_name = ModuleExportName.parse(cursor, interner)?;
Ok(boa_ast::declaration::ExportSpecifier::new(
export_name,
inner_name,
))
} else {
Ok(boa_ast::declaration::ExportSpecifier::new(
inner_name, inner_name,
))
}
}
}

4
boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/mod.rs

@ -18,7 +18,7 @@ use std::io::Read;
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
/// [spec]: https://tc39.es/ecma262/#prod-AsyncFunctionDeclaration /// [spec]: https://tc39.es/ecma262/#prod-AsyncFunctionDeclaration
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(super) struct AsyncFunctionDeclaration { pub(in crate::parser) struct AsyncFunctionDeclaration {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
is_default: AllowDefault, is_default: AllowDefault,
@ -26,7 +26,7 @@ pub(super) struct AsyncFunctionDeclaration {
impl AsyncFunctionDeclaration { impl AsyncFunctionDeclaration {
/// Creates a new `FunctionDeclaration` parser. /// Creates a new `FunctionDeclaration` parser.
pub(super) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self pub(in crate::parser) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
A: Into<AllowAwait>, A: Into<AllowAwait>,

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

@ -3,7 +3,7 @@ use boa_ast::{
function::{AsyncFunction, FormalParameterList}, function::{AsyncFunction, FormalParameterList},
Declaration, StatementList, Declaration, StatementList,
}; };
use boa_interner::Interner; use boa_interner::{Interner, Sym};
use boa_macros::utf16; use boa_macros::utf16;
/// Async function declaration parsing. /// Async function declaration parsing.
@ -34,11 +34,7 @@ fn async_function_declaration_keywords() {
check_script_parser( check_script_parser(
"async function yield() {}", "async function yield() {}",
vec![Declaration::AsyncFunction(AsyncFunction::new( vec![Declaration::AsyncFunction(AsyncFunction::new(
Some( Some(Sym::YIELD.into()),
interner
.get_or_intern_static("yield", utf16!("yield"))
.into(),
),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), StatementList::default(),
false, false,
@ -51,11 +47,7 @@ fn async_function_declaration_keywords() {
check_script_parser( check_script_parser(
"async function await() {}", "async function await() {}",
vec![Declaration::AsyncFunction(AsyncFunction::new( vec![Declaration::AsyncFunction(AsyncFunction::new(
Some( Some(Sym::AWAIT.into()),
interner
.get_or_intern_static("await", utf16!("await"))
.into(),
),
FormalParameterList::default(), FormalParameterList::default(),
StatementList::default(), StatementList::default(),
false, false,

4
boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/mod.rs

@ -21,7 +21,7 @@ use std::io::Read;
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorDeclaration /// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorDeclaration
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(super) struct AsyncGeneratorDeclaration { pub(in crate::parser) struct AsyncGeneratorDeclaration {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
is_default: AllowDefault, is_default: AllowDefault,
@ -29,7 +29,7 @@ pub(super) struct AsyncGeneratorDeclaration {
impl AsyncGeneratorDeclaration { impl AsyncGeneratorDeclaration {
/// Creates a new `AsyncGeneratorDeclaration` parser. /// Creates a new `AsyncGeneratorDeclaration` parser.
pub(super) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self pub(in crate::parser) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
A: Into<AllowAwait>, A: Into<AllowAwait>,

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

@ -24,7 +24,7 @@ use boa_ast::{
function::{self, Class, FormalParameterList, Function}, function::{self, Class, FormalParameterList, Function},
operations::{contains, contains_arguments, has_direct_super, ContainsSymbol}, operations::{contains, contains_arguments, has_direct_super, ContainsSymbol},
property::{ClassElementName, MethodDefinition}, property::{ClassElementName, MethodDefinition},
Declaration, Expression, Keyword, Punctuator, Expression, Keyword, Punctuator,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
use boa_macros::utf16; use boa_macros::utf16;
@ -40,7 +40,7 @@ use std::io::Read;
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class
/// [spec]: https://tc39.es/ecma262/#prod-ClassDeclaration /// [spec]: https://tc39.es/ecma262/#prod-ClassDeclaration
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(super) struct ClassDeclaration { pub(in crate::parser) struct ClassDeclaration {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
is_default: AllowDefault, is_default: AllowDefault,
@ -48,7 +48,7 @@ pub(super) struct ClassDeclaration {
impl ClassDeclaration { impl ClassDeclaration {
/// Creates a new `ClassDeclaration` parser. /// Creates a new `ClassDeclaration` parser.
pub(super) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self pub(in crate::parser) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
A: Into<AllowAwait>, A: Into<AllowAwait>,
@ -66,7 +66,7 @@ impl<R> TokenParser<R> for ClassDeclaration
where where
R: Read, R: Read,
{ {
type Output = Declaration; type Output = Class;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
cursor.expect((Keyword::Class, false), "class declaration", interner)?; cursor.expect((Keyword::Class, false), "class declaration", interner)?;
@ -93,15 +93,13 @@ where
}; };
cursor.set_strict_mode(strict); cursor.set_strict_mode(strict);
Ok(Declaration::Class(
ClassTail::new( ClassTail::new(
name, name,
has_binding_identifier, has_binding_identifier,
self.allow_yield, self.allow_yield,
self.allow_await, self.allow_await,
) )
.parse(cursor, interner)?, .parse(cursor, interner)
))
} }
} }

4
boa_parser/src/parser/statement/declaration/hoistable/generator_decl/mod.rs

@ -18,7 +18,7 @@ use std::io::Read;
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
/// [spec]: https://tc39.es/ecma262/#prod-GeneratorDeclaration /// [spec]: https://tc39.es/ecma262/#prod-GeneratorDeclaration
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(super) struct GeneratorDeclaration { pub(in crate::parser) struct GeneratorDeclaration {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
is_default: AllowDefault, is_default: AllowDefault,
@ -26,7 +26,7 @@ pub(super) struct GeneratorDeclaration {
impl GeneratorDeclaration { impl GeneratorDeclaration {
/// Creates a new `GeneratorDeclaration` parser. /// Creates a new `GeneratorDeclaration` parser.
pub(super) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self pub(in crate::parser) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
A: Into<AllowAwait>, A: Into<AllowAwait>,

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

@ -15,10 +15,6 @@ mod generator_decl;
pub(crate) mod class_decl; pub(crate) mod class_decl;
use self::{
async_function_decl::AsyncFunctionDeclaration, async_generator_decl::AsyncGeneratorDeclaration,
class_decl::ClassDeclaration, generator_decl::GeneratorDeclaration,
};
use crate::{ use crate::{
lexer::TokenKind, lexer::TokenKind,
parser::{ parser::{
@ -40,7 +36,11 @@ use boa_interner::{Interner, Sym};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use std::io::Read; use std::io::Read;
pub(in crate::parser) use function_decl::FunctionDeclaration; pub(in crate::parser) use self::{
async_function_decl::AsyncFunctionDeclaration, async_generator_decl::AsyncGeneratorDeclaration,
class_decl::ClassDeclaration, function_decl::FunctionDeclaration,
generator_decl::GeneratorDeclaration,
};
/// Hoistable declaration parsing. /// Hoistable declaration parsing.
/// ///
@ -49,7 +49,7 @@ pub(in crate::parser) use function_decl::FunctionDeclaration;
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-FunctionDeclaration /// [spec]: https://tc39.es/ecma262/#prod-FunctionDeclaration
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(super) struct HoistableDeclaration { pub(in crate::parser) struct HoistableDeclaration {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
is_default: AllowDefault, is_default: AllowDefault,
@ -57,7 +57,7 @@ pub(super) struct HoistableDeclaration {
impl HoistableDeclaration { impl HoistableDeclaration {
/// Creates a new `HoistableDeclaration` parser. /// Creates a new `HoistableDeclaration` parser.
pub(super) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self pub(in crate::parser) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
A: Into<AllowAwait>, A: Into<AllowAwait>,
@ -225,6 +225,7 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(
&bound_names(&params), &bound_names(&params),
&top_level_lexically_declared_names(&body), &top_level_lexically_declared_names(&body),
params_start_position, params_start_position,
interner,
)?; )?;
// It is a Syntax Error if FormalParameters Contains SuperProperty is true. // It is a Syntax Error if FormalParameters Contains SuperProperty is true.

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

@ -0,0 +1,327 @@
//! Import declaration parsing
//!
//! This parses `import` declarations.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript specification][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-imports
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
use crate::{
lexer::TokenKind,
parser::{
cursor::Cursor,
statement::{declaration::FromClause, BindingIdentifier},
Error, OrAbrupt, ParseResult, TokenParser,
},
};
use boa_ast::{
declaration::{
ImportDeclaration as AstImportDeclaration, ImportKind,
ImportSpecifier as AstImportSpecifier, ModuleSpecifier,
},
expression::Identifier,
Keyword, Punctuator,
};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use std::io::Read;
/// Parses an import declaration.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ImportDeclaration
#[derive(Debug, Clone, Copy)]
pub(in crate::parser) struct ImportDeclaration;
impl<R> TokenParser<R> for ImportDeclaration
where
R: Read,
{
type Output = AstImportDeclaration;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("ImportDeclaration", "Parsing");
cursor.expect((Keyword::Import, false), "import declaration", interner)?;
let tok = cursor.peek(0, interner).or_abrupt()?;
let import_clause = match tok.kind() {
TokenKind::StringLiteral((module_identifier, _)) => {
let module_identifier = *module_identifier;
cursor.advance(interner);
cursor.expect_semicolon("import declaration", interner)?;
return Ok(AstImportDeclaration::new(
None,
ImportKind::DefaultOrUnnamed,
ModuleSpecifier::new(module_identifier),
));
}
TokenKind::Punctuator(Punctuator::OpenBlock) => {
let list = NamedImports.parse(cursor, interner)?;
ImportClause::ImportList(None, list)
}
TokenKind::Punctuator(Punctuator::Mul) => {
let alias = NameSpaceImport.parse(cursor, interner)?;
ImportClause::Namespace(None, alias)
}
TokenKind::IdentifierName(_)
| TokenKind::Keyword((Keyword::Await | Keyword::Yield, _)) => {
let imported_binding = ImportedBinding.parse(cursor, interner)?;
let tok = cursor.peek(0, interner).or_abrupt()?;
match tok.kind() {
TokenKind::Punctuator(Punctuator::Comma) => {
cursor.advance(interner);
let tok = cursor.peek(0, interner).or_abrupt()?;
match tok.kind() {
TokenKind::Punctuator(Punctuator::OpenBlock) => {
let list = NamedImports.parse(cursor, interner)?;
ImportClause::ImportList(Some(imported_binding), list)
}
TokenKind::Punctuator(Punctuator::Mul) => {
let alias = NameSpaceImport.parse(cursor, interner)?;
ImportClause::Namespace(Some(imported_binding), alias)
}
_ => {
return Err(Error::expected(
[
Punctuator::OpenBlock.to_string(),
Punctuator::Mul.to_string(),
],
tok.to_string(interner),
tok.span(),
"import declaration",
))
}
}
}
_ => ImportClause::ImportList(Some(imported_binding), Box::default()),
}
}
_ => {
return Err(Error::expected(
[
Punctuator::OpenBlock.to_string(),
Punctuator::Mul.to_string(),
"identifier".to_owned(),
"string literal".to_owned(),
],
tok.to_string(interner),
tok.span(),
"import declaration",
))
}
};
let module_identifier = FromClause::new("import declaration").parse(cursor, interner)?;
Ok(import_clause.with_specifier(module_identifier))
}
}
/// Parses an imported binding
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ImportedBinding
#[derive(Debug, Clone, Copy)]
struct ImportedBinding;
impl<R> TokenParser<R> for ImportedBinding
where
R: Read,
{
type Output = Identifier;
#[inline]
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
BindingIdentifier::new(false, true).parse(cursor, interner)
}
}
/// Parses a named import list.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-NamedImports
#[derive(Debug, Clone, Copy)]
struct NamedImports;
impl<R> TokenParser<R> for NamedImports
where
R: Read,
{
type Output = Box<[AstImportSpecifier]>;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
cursor.expect(Punctuator::OpenBlock, "import declaration", interner)?;
let mut list = Vec::new();
loop {
let tok = cursor.next(interner).or_abrupt()?;
match tok.kind() {
TokenKind::Punctuator(Punctuator::CloseBlock) => {
break;
}
TokenKind::Punctuator(Punctuator::Comma) => {
if list.is_empty() {
return Err(Error::expected(
[
Punctuator::CloseBlock.to_string(),
"string literal".to_owned(),
"identifier".to_owned(),
],
tok.to_string(interner),
tok.span(),
"import declaration",
));
}
}
TokenKind::StringLiteral(_) | TokenKind::IdentifierName(_) => {
list.push(ImportSpecifier.parse(cursor, interner)?);
}
_ => {
return Err(Error::expected(
[
Punctuator::CloseBlock.to_string(),
Punctuator::Comma.to_string(),
],
tok.to_string(interner),
tok.span(),
"import declaration",
));
}
}
}
Ok(list.into_boxed_slice())
}
}
/// Parses an import clause.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ImportClause
#[derive(Debug, Clone)]
enum ImportClause {
Namespace(Option<Identifier>, Identifier),
ImportList(Option<Identifier>, Box<[AstImportSpecifier]>),
}
impl ImportClause {
#[inline]
#[allow(clippy::missing_const_for_fn)]
fn with_specifier(self, specifier: ModuleSpecifier) -> AstImportDeclaration {
match self {
Self::Namespace(default, binding) => {
AstImportDeclaration::new(default, ImportKind::Namespaced { binding }, specifier)
}
Self::ImportList(default, names) => {
if names.is_empty() {
AstImportDeclaration::new(default, ImportKind::DefaultOrUnnamed, specifier)
} else {
AstImportDeclaration::new(default, ImportKind::Named { names }, specifier)
}
}
}
}
}
/// Parses an import specifier.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ImportSpecifier
#[derive(Debug, Clone, Copy)]
struct ImportSpecifier;
impl<R> TokenParser<R> for ImportSpecifier
where
R: Read,
{
type Output = AstImportSpecifier;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let tok = cursor.next(interner).or_abrupt()?;
match tok.kind() {
TokenKind::StringLiteral((name, _)) => {
if interner.resolve_expect(*name).utf8().is_none() {
return Err(Error::general(
"import specifiers don't allow unpaired surrogates",
tok.span().end(),
));
}
cursor.expect(
TokenKind::identifier(Sym::AS),
"import declaration",
interner,
)?;
let binding = ImportedBinding.parse(cursor, interner)?;
Ok(AstImportSpecifier::new(binding, *name))
}
TokenKind::IdentifierName((name, _)) => {
if cursor
.next_if(TokenKind::identifier(Sym::AS), interner)?
.is_some()
{
let binding = ImportedBinding.parse(cursor, interner)?;
Ok(AstImportSpecifier::new(binding, *name))
} else {
Ok(AstImportSpecifier::new(Identifier::new(*name), *name))
}
}
_ => Err(Error::expected(
["string literal".to_owned(), "identifier".to_owned()],
tok.to_string(interner),
tok.span(),
"import declaration",
)),
}
}
}
/// Parses a namespace import
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-NameSpaceImport
#[derive(Debug, Clone, Copy)]
struct NameSpaceImport;
impl<R> TokenParser<R> for NameSpaceImport
where
R: Read,
{
type Output = Identifier;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
cursor.expect(Punctuator::Mul, "import declaration", interner)?;
cursor.expect(
TokenKind::identifier(Sym::AS),
"import declaration",
interner,
)?;
ImportedBinding.parse(cursor, interner)
}
}

2
boa_parser/src/parser/statement/declaration/lexical.rs

@ -21,7 +21,7 @@ use ast::operations::bound_names;
use boa_ast::{self as ast, declaration::Variable, pattern::Pattern, Keyword, Punctuator}; use boa_ast::{self as ast, declaration::Variable, pattern::Pattern, Keyword, Punctuator};
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use std::{convert::TryInto, io::Read}; use std::io::Read;
/// Parses a lexical declaration. /// Parses a lexical declaration.
/// ///

65
boa_parser/src/parser/statement/declaration/mod.rs

@ -7,22 +7,28 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements#Declarations //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements#Declarations
//! [spec]:https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement //! [spec]:https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
mod export;
mod hoistable; mod hoistable;
mod import;
mod lexical; mod lexical;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub(in crate::parser) use hoistable::class_decl::ClassTail; pub(in crate::parser) use self::{
pub(in crate::parser) use hoistable::FunctionDeclaration; export::ExportDeclaration,
use hoistable::HoistableDeclaration; hoistable::{
pub(in crate::parser) use lexical::LexicalDeclaration; class_decl::ClassTail, ClassDeclaration, FunctionDeclaration, HoistableDeclaration,
},
import::ImportDeclaration,
lexical::LexicalDeclaration,
};
use crate::{ use crate::{
lexer::TokenKind, lexer::TokenKind,
parser::{AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser}, parser::{AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser},
Error,
}; };
use boa_ast::{self as ast, Keyword}; use boa_ast::{self as ast, Keyword};
use boa_interner::Interner; use boa_interner::{Interner, Sym};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use std::io::Read; use std::io::Read;
@ -39,6 +45,8 @@ pub(super) struct Declaration {
} }
impl Declaration { impl Declaration {
/// Creates a new declaration parser.
#[inline]
pub(super) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self pub(super) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
@ -75,3 +83,48 @@ where
} }
} }
} }
/// Parses a `from` clause.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-FromClause
#[derive(Debug, Clone, Copy)]
struct FromClause {
context: &'static str,
}
impl FromClause {
/// Creates a new `from` clause parser
#[inline]
const fn new(context: &'static str) -> Self {
Self { context }
}
}
impl<R> TokenParser<R> for FromClause
where
R: Read,
{
type Output = ast::declaration::ModuleSpecifier;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("FromClause", "Parsing");
cursor.expect(TokenKind::identifier(Sym::FROM), self.context, interner)?;
let tok = cursor.next(interner).or_abrupt()?;
let TokenKind::StringLiteral((from, _)) = tok.kind() else {
return Err(Error::expected(
["string literal".to_owned()],
tok.to_string(interner),
tok.span(),
self.context,
))
};
Ok((*from).into())
}
}

32
boa_parser/src/parser/statement/declaration/tests.rs

@ -1,12 +1,10 @@
use std::convert::TryInto; use crate::parser::tests::{check_invalid_script, check_script_parser};
use crate::parser::tests::{check_invalid, check_script_parser};
use boa_ast::{ use boa_ast::{
declaration::{LexicalDeclaration, VarDeclaration, Variable}, declaration::{LexicalDeclaration, VarDeclaration, Variable},
expression::literal::Literal, expression::literal::Literal,
Declaration, Statement, Declaration, Statement,
}; };
use boa_interner::Interner; use boa_interner::{Interner, Sym};
use boa_macros::utf16; use boa_macros::utf16;
/// Checks `var` declaration parsing. /// Checks `var` declaration parsing.
@ -36,9 +34,7 @@ fn var_declaration_keywords() {
"var yield = 5;", "var yield = 5;",
vec![Statement::Var(VarDeclaration( vec![Statement::Var(VarDeclaration(
vec![Variable::from_identifier( vec![Variable::from_identifier(
interner Sym::YIELD.into(),
.get_or_intern_static("yield", utf16!("yield"))
.into(),
Some(Literal::from(5).into()), Some(Literal::from(5).into()),
)] )]
.try_into() .try_into()
@ -53,9 +49,7 @@ fn var_declaration_keywords() {
"var await = 5;", "var await = 5;",
vec![Statement::Var(VarDeclaration( vec![Statement::Var(VarDeclaration(
vec![Variable::from_identifier( vec![Variable::from_identifier(
interner Sym::AWAIT.into(),
.get_or_intern_static("await", utf16!("await"))
.into(),
Some(Literal::from(5).into()), Some(Literal::from(5).into()),
)] )]
.try_into() .try_into()
@ -160,9 +154,7 @@ fn let_declaration_keywords() {
"let yield = 5;", "let yield = 5;",
vec![Declaration::Lexical(LexicalDeclaration::Let( vec![Declaration::Lexical(LexicalDeclaration::Let(
vec![Variable::from_identifier( vec![Variable::from_identifier(
interner Sym::YIELD.into(),
.get_or_intern_static("yield", utf16!("yield"))
.into(),
Some(Literal::from(5).into()), Some(Literal::from(5).into()),
)] )]
.try_into() .try_into()
@ -177,9 +169,7 @@ fn let_declaration_keywords() {
"let await = 5;", "let await = 5;",
vec![Declaration::Lexical(LexicalDeclaration::Let( vec![Declaration::Lexical(LexicalDeclaration::Let(
vec![Variable::from_identifier( vec![Variable::from_identifier(
interner Sym::AWAIT.into(),
.get_or_intern_static("await", utf16!("await"))
.into(),
Some(Literal::from(5).into()), Some(Literal::from(5).into()),
)] )]
.try_into() .try_into()
@ -284,9 +274,7 @@ fn const_declaration_keywords() {
"const yield = 5;", "const yield = 5;",
vec![Declaration::Lexical(LexicalDeclaration::Const( vec![Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier( vec![Variable::from_identifier(
interner Sym::YIELD.into(),
.get_or_intern_static("yield", utf16!("yield"))
.into(),
Some(Literal::from(5).into()), Some(Literal::from(5).into()),
)] )]
.try_into() .try_into()
@ -301,9 +289,7 @@ fn const_declaration_keywords() {
"const await = 5;", "const await = 5;",
vec![Declaration::Lexical(LexicalDeclaration::Const( vec![Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier( vec![Variable::from_identifier(
interner Sym::AWAIT.into(),
.get_or_intern_static("await", utf16!("await"))
.into(),
Some(Literal::from(5).into()), Some(Literal::from(5).into()),
)] )]
.try_into() .try_into()
@ -336,7 +322,7 @@ fn const_declaration_no_spaces() {
/// Checks empty `const` declaration parsing. /// Checks empty `const` declaration parsing.
#[test] #[test]
fn empty_const_declaration() { fn empty_const_declaration() {
check_invalid("const a;"); check_invalid_script("const a;");
} }
/// Checks multiple `const` declarations. /// Checks multiple `const` declarations.

12
boa_parser/src/parser/statement/iteration/for_statement.rs

@ -303,16 +303,20 @@ fn initializer_to_iterable_loop_initializer(
ast::Expression::Identifier(ident) => Ok(IterableLoopInitializer::Identifier(ident)), ast::Expression::Identifier(ident) => Ok(IterableLoopInitializer::Identifier(ident)),
ast::Expression::ArrayLiteral(array) => array ast::Expression::ArrayLiteral(array) => array
.to_pattern(strict) .to_pattern(strict)
.ok_or(Error::General { .ok_or_else(|| {
message: "invalid array destructuring pattern in iterable loop initializer", Error::general(
"invalid array destructuring pattern in iterable loop initializer",
position, position,
)
}) })
.map(|arr| IterableLoopInitializer::Pattern(arr.into())), .map(|arr| IterableLoopInitializer::Pattern(arr.into())),
ast::Expression::ObjectLiteral(object) => object ast::Expression::ObjectLiteral(object) => object
.to_pattern(strict) .to_pattern(strict)
.ok_or(Error::General { .ok_or_else(|| {
message: "invalid object destructuring pattern in iterable loop initializer", Error::general(
"invalid object destructuring pattern in iterable loop initializer",
position, position,
)
}) })
.map(|obj| IterableLoopInitializer::Pattern(obj.into())), .map(|obj| IterableLoopInitializer::Pattern(obj.into())),
ast::Expression::PropertyAccess(access) => Ok(IterableLoopInitializer::Access(access)), ast::Expression::PropertyAccess(access) => Ok(IterableLoopInitializer::Access(access)),

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

@ -1,4 +1,4 @@
use crate::parser::tests::{check_invalid, check_script_parser}; use crate::parser::tests::{check_invalid_script, check_script_parser};
use boa_ast::{ use boa_ast::{
declaration::{VarDeclaration, Variable}, declaration::{VarDeclaration, Variable},
expression::{ expression::{
@ -251,11 +251,11 @@ fn do_while_spaces() {
/// Checks rejection of const bindings without init in for loops /// Checks rejection of const bindings without init in for loops
#[test] #[test]
fn reject_const_no_init_for_loop() { fn reject_const_no_init_for_loop() {
check_invalid("for (const h;;);"); check_invalid_script("for (const h;;);");
} }
/// Checks rejection of for await .. in loops /// Checks rejection of for await .. in loops
#[test] #[test]
fn reject_for_await_in_loop() { fn reject_for_await_in_loop() {
check_invalid("for await (x in [1,2,3]);"); check_invalid_script("for await (x in [1,2,3]);");
} }

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

@ -25,7 +25,7 @@ use self::{
block::BlockStatement, block::BlockStatement,
break_stm::BreakStatement, break_stm::BreakStatement,
continue_stm::ContinueStatement, continue_stm::ContinueStatement,
declaration::Declaration, declaration::{Declaration, ExportDeclaration, ImportDeclaration},
expression::ExpressionStatement, expression::ExpressionStatement,
if_stm::IfStatement, if_stm::IfStatement,
iteration::{DoWhileStatement, ForStatement, WhileStatement}, iteration::{DoWhileStatement, ForStatement, WhileStatement},
@ -37,7 +37,10 @@ use self::{
variable::VariableStatement, variable::VariableStatement,
}; };
use crate::{ use crate::{
lexer::{token::EscapeSequence, Error as LexError, InputElement, Token, TokenKind}, lexer::{
token::{ContainsEscapeSequence, EscapeSequence},
Error as LexError, InputElement, Token, TokenKind,
},
parser::{ parser::{
expression::{BindingIdentifier, Initializer, PropertyName}, expression::{BindingIdentifier, Initializer, PropertyName},
AllowAwait, AllowReturn, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser, AllowAwait, AllowReturn, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser,
@ -49,7 +52,7 @@ use boa_ast::{
pattern::{ArrayPattern, ArrayPatternElement, ObjectPatternElement}, pattern::{ArrayPattern, ArrayPatternElement, ObjectPatternElement},
Keyword, Punctuator, Keyword, Punctuator,
}; };
use boa_interner::Interner; use boa_interner::{Interner, Sym};
use boa_macros::utf16; use boa_macros::utf16;
use boa_profiler::Profiler; use boa_profiler::Profiler;
use std::io::Read; use std::io::Read;
@ -868,3 +871,67 @@ where
} }
} }
} }
/// Parses a module body
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ModuleBody
#[derive(Debug, Clone, Copy)]
pub(super) struct ModuleItemList;
impl<R> TokenParser<R> for ModuleItemList
where
R: Read,
{
type Output = boa_ast::ModuleItemList;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let mut list = Vec::new();
while cursor.peek(0, interner)?.is_some() {
list.push(ModuleItem.parse(cursor, interner)?);
}
Ok(list.into())
}
}
/// Parses a module item.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ModuleItem
struct ModuleItem;
impl<R> TokenParser<R> for ModuleItem
where
R: Read,
{
type Output = boa_ast::ModuleItem;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let tok = cursor.peek(0, interner).or_abrupt()?;
match tok.kind() {
TokenKind::IdentifierName((ident, ContainsEscapeSequence(false)))
if *ident == Sym::IMPORT =>
{
ImportDeclaration
.parse(cursor, interner)
.map(Self::Output::ImportDeclaration)
}
TokenKind::IdentifierName((ident, ContainsEscapeSequence(false)))
if *ident == Sym::EXPORT =>
{
ExportDeclaration
.parse(cursor, interner)
.map(Self::Output::ExportDeclaration)
}
_ => StatementListItem::new(false, true, false)
.parse(cursor, interner)
.map(Self::Output::StatementListItem),
}
}
}

14
boa_parser/src/parser/statement/switch/tests.rs

@ -1,4 +1,4 @@
use crate::parser::tests::{check_invalid, check_script_parser}; use crate::parser::tests::{check_invalid_script, check_script_parser};
use boa_ast::{ use boa_ast::{
declaration::{LexicalDeclaration, Variable}, declaration::{LexicalDeclaration, Variable},
expression::{access::SimplePropertyAccess, literal::Literal, Call, Identifier}, expression::{access::SimplePropertyAccess, literal::Literal, Call, Identifier},
@ -11,7 +11,7 @@ use boa_macros::utf16;
/// Checks parsing malformed switch with no closeblock. /// Checks parsing malformed switch with no closeblock.
#[test] #[test]
fn check_switch_no_closeblock() { fn check_switch_no_closeblock() {
check_invalid( check_invalid_script(
r#" r#"
let a = 10; let a = 10;
switch (a) { switch (a) {
@ -26,7 +26,7 @@ fn check_switch_no_closeblock() {
/// Checks parsing malformed switch in which a case is started but not finished. /// Checks parsing malformed switch in which a case is started but not finished.
#[test] #[test]
fn check_switch_case_unclosed() { fn check_switch_case_unclosed() {
check_invalid( check_invalid_script(
r#" r#"
let a = 10; let a = 10;
switch (a) { switch (a) {
@ -40,7 +40,7 @@ fn check_switch_case_unclosed() {
/// Checks parsing malformed switch with 2 defaults. /// Checks parsing malformed switch with 2 defaults.
#[test] #[test]
fn check_switch_two_default() { fn check_switch_two_default() {
check_invalid( check_invalid_script(
r#" r#"
let a = 10; let a = 10;
switch (a) { switch (a) {
@ -58,7 +58,7 @@ fn check_switch_two_default() {
/// Checks parsing malformed switch with no expression. /// Checks parsing malformed switch with no expression.
#[test] #[test]
fn check_switch_no_expr() { fn check_switch_no_expr() {
check_invalid( check_invalid_script(
r#" r#"
let a = 10; let a = 10;
switch { switch {
@ -73,7 +73,7 @@ fn check_switch_no_expr() {
/// Checks parsing malformed switch with an unknown label. /// Checks parsing malformed switch with an unknown label.
#[test] #[test]
fn check_switch_unknown_label() { fn check_switch_unknown_label() {
check_invalid( check_invalid_script(
r#" r#"
let a = 10; let a = 10;
switch (a) { switch (a) {
@ -88,7 +88,7 @@ fn check_switch_unknown_label() {
/// Checks parsing malformed switch with two defaults that are seperated by cases. /// Checks parsing malformed switch with two defaults that are seperated by cases.
#[test] #[test]
fn check_switch_seperated_defaults() { fn check_switch_seperated_defaults() {
check_invalid( check_invalid_script(
r#" r#"
let a = 10; let a = 10;
switch (a) { switch (a) {

18
boa_parser/src/parser/statement/try_stm/tests.rs

@ -1,6 +1,4 @@
use std::convert::TryInto; use crate::parser::tests::{check_invalid_script, check_script_parser};
use crate::parser::tests::{check_invalid, check_script_parser};
use boa_ast::{ use boa_ast::{
declaration::{VarDeclaration, Variable}, declaration::{VarDeclaration, Variable},
expression::{literal::Literal, Identifier}, expression::{literal::Literal, Identifier},
@ -265,35 +263,35 @@ fn check_catch_with_var_redeclaration() {
#[test] #[test]
fn check_inline_invalid_catch() { fn check_inline_invalid_catch() {
check_invalid("try {} catch"); check_invalid_script("try {} catch");
} }
#[test] #[test]
fn check_inline_invalid_catch_without_closing_paren() { fn check_inline_invalid_catch_without_closing_paren() {
check_invalid("try {} catch(e {}"); check_invalid_script("try {} catch(e {}");
} }
#[test] #[test]
fn check_inline_invalid_catch_parameter() { fn check_inline_invalid_catch_parameter() {
check_invalid("try {} catch(1) {}"); check_invalid_script("try {} catch(1) {}");
} }
#[test] #[test]
fn check_invalid_try_no_catch_finally() { fn check_invalid_try_no_catch_finally() {
check_invalid("try {} let a = 10;"); check_invalid_script("try {} let a = 10;");
} }
#[test] #[test]
fn check_invalid_catch_with_empty_paren() { fn check_invalid_catch_with_empty_paren() {
check_invalid("try {} catch() {}"); check_invalid_script("try {} catch() {}");
} }
#[test] #[test]
fn check_invalid_catch_with_duplicate_params() { fn check_invalid_catch_with_duplicate_params() {
check_invalid("try {} catch({ a, b: a }) {}"); check_invalid_script("try {} catch({ a, b: a }) {}");
} }
#[test] #[test]
fn check_invalid_catch_with_lexical_redeclaration() { fn check_invalid_catch_with_lexical_redeclaration() {
check_invalid("try {} catch(e) { let e = 'oh' }"); check_invalid_script("try {} catch(e) { let e = 'oh' }");
} }

6
boa_parser/src/parser/statement/variable/mod.rs

@ -11,7 +11,7 @@ use crate::{
}; };
use boa_ast::{ use boa_ast::{
declaration::{VarDeclaration, Variable}, declaration::{VarDeclaration, Variable},
Keyword, Punctuator, Statement, Keyword, Punctuator,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -51,7 +51,7 @@ impl<R> TokenParser<R> for VariableStatement
where where
R: Read, R: Read,
{ {
type Output = Statement; type Output = VarDeclaration;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("VariableStatement", "Parsing"); let _timer = Profiler::global().start_event("VariableStatement", "Parsing");
@ -62,7 +62,7 @@ where
cursor.expect_semicolon("variable statement", interner)?; cursor.expect_semicolon("variable statement", interner)?;
Ok(decl_list.into()) Ok(decl_list)
} }
} }

2
boa_parser/src/parser/tests/format/mod.rs

@ -33,7 +33,7 @@ fn test_formatting(source: &'static str) {
let source = Source::from_bytes(source); let source = Source::from_bytes(source);
let interner = &mut Interner::default(); let interner = &mut Interner::default();
let result = Parser::new(source) let result = Parser::new(source)
.parse_all(interner) .parse_script(interner)
.expect("parsing failed") .expect("parsing failed")
.to_interned_string(interner); .to_interned_string(interner);
if scenario != result { if scenario != result {

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

@ -37,7 +37,7 @@ where
{ {
assert_eq!( assert_eq!(
Parser::new(Source::from_bytes(js)) Parser::new(Source::from_bytes(js))
.parse_all(interner) .parse_script(interner)
.expect("failed to parse"), .expect("failed to parse"),
StatementList::from(expr.into()) StatementList::from(expr.into())
); );
@ -45,9 +45,9 @@ where
/// Checks that the given javascript string creates a parse error. /// Checks that the given javascript string creates a parse error.
#[track_caller] #[track_caller]
pub(super) fn check_invalid(js: &str) { pub(super) fn check_invalid_script(js: &str) {
assert!(Parser::new(Source::from_bytes(js)) assert!(Parser::new(Source::from_bytes(js))
.parse_all(&mut Interner::default()) .parse_script(&mut Interner::default())
.is_err()); .is_err());
} }

5
boa_tester/src/exec/js262.rs

@ -90,7 +90,8 @@ fn detach_array_buffer(_: &JsValue, args: &[JsValue], _: &mut Context<'_>) -> Js
fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> { fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
args.get(0).and_then(JsValue::as_string).map_or_else( args.get(0).and_then(JsValue::as_string).map_or_else(
|| Ok(JsValue::undefined()), || Ok(JsValue::undefined()),
|source_text| match context.parse(Source::from_bytes(&source_text.to_std_string_escaped())) |source_text| match context
.parse_script(Source::from_bytes(&source_text.to_std_string_escaped()))
{ {
// TODO: check strict // TODO: check strict
Err(e) => Err(JsNativeError::typ() Err(e) => Err(JsNativeError::typ()
@ -98,7 +99,7 @@ fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) ->
.into()), .into()),
// Calling eval here parses the code a second time. // 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. // TODO: We can fix this after we have have defined the public api for the vm executer.
Ok(_) => context.eval(Source::from_bytes(&source_text.to_std_string_escaped())), Ok(_) => context.eval_script(Source::from_bytes(&source_text.to_std_string_escaped())),
}, },
) )
} }

54
boa_tester/src/exec/mod.rs

@ -2,10 +2,10 @@
mod js262; mod js262;
use super::{ use crate::{
Harness, Outcome, Phase, SuiteResult, Test, TestFlags, TestOutcomeResult, TestResult, TestSuite, read::ErrorType, Harness, Outcome, Phase, SuiteResult, Test, TestFlags, TestOutcomeResult,
TestResult, TestSuite,
}; };
use crate::read::ErrorType;
use boa_engine::{ use boa_engine::{
context::ContextBuilder, job::SimpleJobQueue, native_function::NativeFunction, context::ContextBuilder, job::SimpleJobQueue, native_function::NativeFunction,
object::FunctionObjectBuilder, property::Attribute, Context, JsArgs, JsNativeErrorKind, object::FunctionObjectBuilder, property::Attribute, Context, JsArgs, JsNativeErrorKind,
@ -187,7 +187,11 @@ impl Test {
context.strict(strict); context.strict(strict);
// TODO: timeout // TODO: timeout
let value = match context.eval(source) { let value = match if self.is_module() {
context.eval_module(source)
} else {
context.eval_script(source)
} {
Ok(v) => v, Ok(v) => v,
Err(e) => return (false, format!("Uncaught {e}")), Err(e) => return (false, format!("Uncaught {e}")),
}; };
@ -213,14 +217,24 @@ impl Test {
let context = &mut Context::default(); let context = &mut Context::default();
context.strict(strict); context.strict(strict);
match context.parse(source) { if self.is_module() {
Ok(statement_list) => match context.compile(&statement_list) { match context.parse_module(source) {
Ok(module_item_list) => match context.compile_module(&module_item_list) {
Ok(_) => (false, "ModuleItemList compilation should fail".to_owned()),
Err(e) => (true, format!("Uncaught {e:?}")),
},
Err(e) => (true, format!("Uncaught {e}")),
}
} else {
match context.parse_script(source) {
Ok(statement_list) => match context.compile_script(&statement_list) {
Ok(_) => (false, "StatementList compilation should fail".to_owned()), Ok(_) => (false, "StatementList compilation should fail".to_owned()),
Err(e) => (true, format!("Uncaught {e:?}")), Err(e) => (true, format!("Uncaught {e:?}")),
}, },
Err(e) => (true, format!("Uncaught {e}")), Err(e) => (true, format!("Uncaught {e}")),
} }
} }
}
Outcome::Negative { Outcome::Negative {
phase: Phase::Resolution, phase: Phase::Resolution,
error_type: _, error_type: _,
@ -230,17 +244,28 @@ impl Test {
error_type, error_type,
} => { } => {
let context = &mut Context::default(); let context = &mut Context::default();
context.strict(strict);
if let Err(e) = self.set_up_env(harness, context, AsyncResult::default()) { if let Err(e) = self.set_up_env(harness, context, AsyncResult::default()) {
return (false, e); return (false, e);
} }
context.strict(strict); let code = if self.is_module() {
let code = match context match context
.parse(source) .parse_module(source)
.map_err(Into::into)
.and_then(|stmts| context.compile_module(&stmts))
{
Ok(code) => code,
Err(e) => return (false, format!("Uncaught {e}")),
}
} else {
match context
.parse_script(source)
.map_err(Into::into) .map_err(Into::into)
.and_then(|stmts| context.compile(&stmts)) .and_then(|stmts| context.compile_script(&stmts))
{ {
Ok(code) => code, Ok(code) => code,
Err(e) => return (false, format!("Uncaught {e}")), Err(e) => return (false, format!("Uncaught {e}")),
}
}; };
let e = match context.execute(code) { let e = match context.execute(code) {
@ -355,10 +380,10 @@ impl Test {
let sta = Source::from_reader(harness.sta.content.as_bytes(), Some(&harness.sta.path)); let sta = Source::from_reader(harness.sta.content.as_bytes(), Some(&harness.sta.path));
context context
.eval(assert) .eval_script(assert)
.map_err(|e| format!("could not run assert.js:\n{e}"))?; .map_err(|e| format!("could not run assert.js:\n{e}"))?;
context context
.eval(sta) .eval_script(sta)
.map_err(|e| format!("could not run sta.js:\n{e}"))?; .map_err(|e| format!("could not run sta.js:\n{e}"))?;
if self.flags.contains(TestFlags::ASYNC) { if self.flags.contains(TestFlags::ASYNC) {
@ -367,7 +392,7 @@ impl Test {
Some(&harness.doneprint_handle.path), Some(&harness.doneprint_handle.path),
); );
context context
.eval(dph) .eval_script(dph)
.map_err(|e| format!("could not run doneprintHandle.js:\n{e}"))?; .map_err(|e| format!("could not run doneprintHandle.js:\n{e}"))?;
} }
@ -377,7 +402,7 @@ impl Test {
.get(include_name) .get(include_name)
.ok_or_else(|| format!("could not find the {include_name} include file."))?; .ok_or_else(|| format!("could not find the {include_name} include file."))?;
let source = Source::from_reader(include.content.as_bytes(), Some(&include.path)); let source = Source::from_reader(include.content.as_bytes(), Some(&include.path));
context.eval(source).map_err(|e| { context.eval_script(source).map_err(|e| {
format!("could not run the harness `{include_name}`:\nUncaught {e}",) format!("could not run the harness `{include_name}`:\nUncaught {e}",)
})?; })?;
} }
@ -422,6 +447,7 @@ struct AsyncResult {
} }
impl Default for AsyncResult { impl Default for AsyncResult {
#[inline]
fn default() -> Self { fn default() -> Self {
Self { Self {
inner: Rc::new(RefCell::new(Ok(()))), inner: Rc::new(RefCell::new(Ok(()))),

8
boa_tester/src/main.rs

@ -407,9 +407,17 @@ impl Test {
} }
} }
/// Sets the test as ignored.
#[inline]
fn set_ignored(&mut self) { fn set_ignored(&mut self) {
self.ignored = true; self.ignored = true;
} }
/// Checks if this is a module test.
#[inline]
const fn is_module(&self) -> bool {
self.flags.contains(TestFlags::MODULE)
}
} }
/// An outcome for a test. /// An outcome for a test.

2
boa_wasm/src/lib.rs

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

2
fuzz/fuzz_targets/bytecompiler-implied.rs

@ -14,7 +14,7 @@ fn do_fuzz(original: FuzzSource) -> Corpus {
.instructions_remaining(0) .instructions_remaining(0)
.build(); .build();
let mut parser = Parser::new(Cursor::new(&original.source)); let mut parser = Parser::new(Cursor::new(&original.source));
if let Ok(parsed) = parser.parse_all(ctx.interner_mut()) { if let Ok(parsed) = parser.parse_script(ctx.interner_mut()) {
let _ = ctx.compile(&parsed); let _ = ctx.compile(&parsed);
Corpus::Keep Corpus::Keep
} else { } else {

4
fuzz/fuzz_targets/parser-idempotency.rs

@ -21,7 +21,7 @@ fn do_fuzz(mut data: FuzzData) -> Result<(), Box<dyn Error>> {
let before = data.interner.len(); let before = data.interner.len();
// For a variety of reasons, we may not actually produce valid code here (e.g., nameless function). // For a variety of reasons, we may not actually produce valid code here (e.g., nameless function).
// Fail fast and only make the next checks if we were valid. // Fail fast and only make the next checks if we were valid.
if let Ok(first) = parser.parse_all(&mut data.interner) { if let Ok(first) = parser.parse_script(&mut data.interner) {
let after_first = data.interner.len(); let after_first = data.interner.len();
let first_interned = first.to_interned_string(&data.interner); let first_interned = first.to_interned_string(&data.interner);
@ -38,7 +38,7 @@ fn do_fuzz(mut data: FuzzData) -> Result<(), Box<dyn Error>> {
// Now, we most assuredly should produce valid code. It has already gone through a first pass. // Now, we most assuredly should produce valid code. It has already gone through a first pass.
let second = parser let second = parser
.parse_all(&mut data.interner) .parse_script(&mut data.interner)
.expect("Could not parse the first-pass interned copy."); .expect("Could not parse the first-pass interned copy.");
let second_interned = second.to_interned_string(&data.interner); let second_interned = second.to_interned_string(&data.interner);
let after_second = data.interner.len(); let after_second = data.interner.len();

2
test_ignore.toml

@ -1,5 +1,5 @@
# Not implemented yet: # Not implemented yet:
flags = ["module"] flags = []
features = [ features = [
# Non-implemented features: # Non-implemented features:

Loading…
Cancel
Save