diff --git a/boa_ast/src/declaration/export.rs b/boa_ast/src/declaration/export.rs new file mode 100644 index 0000000000..b174a82b4d --- /dev/null +++ b/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, + }, + /// 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 + 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 + 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 + 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 + 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 + 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 + where + V: VisitorMut<'a>, + { + try_break!(visitor.visit_sym_mut(&mut self.alias)); + visitor.visit_sym_mut(&mut self.private_name) + } +} diff --git a/boa_ast/src/declaration/import.rs b/boa_ast/src/declaration/import.rs new file mode 100644 index 0000000000..0420c73839 --- /dev/null +++ b/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 + 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 + 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, + /// See [`ImportKind`]. + kind: ImportKind, + /// Module specifier. + specifier: ModuleSpecifier, +} + +impl ImportDeclaration { + /// Creates a new import declaration. + #[inline] + #[must_use] + pub const fn new( + default: Option, + 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 { + 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 + 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 + 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 + 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 + where + V: VisitorMut<'a>, + { + try_break!(visitor.visit_identifier_mut(&mut self.binding)); + visitor.visit_sym_mut(&mut self.export_name) + } +} diff --git a/boa_ast/src/declaration/mod.rs b/boa_ast/src/declaration/mod.rs index e25f5f1b08..95dca8940e 100644 --- a/boa_ast/src/declaration/mod.rs +++ b/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 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; +mod export; +mod import; mod variable; use crate::visitor::{VisitWith, Visitor, VisitorMut}; +pub use export::*; +pub use import::*; pub use variable::*; /// 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 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 + where + V: Visitor<'a>, + { + visitor.visit_sym(&self.module) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_sym_mut(&mut self.module) + } +} diff --git a/boa_ast/src/lib.rs b/boa_ast/src/lib.rs index 18dfb293ef..5d1dd7663b 100644 --- a/boa_ast/src/lib.rs +++ b/boa_ast/src/lib.rs @@ -102,6 +102,7 @@ pub mod declaration; pub mod expression; pub mod function; pub mod keyword; +pub mod module_item_list; pub mod operations; pub mod pattern; pub mod property; @@ -114,6 +115,7 @@ pub use self::{ declaration::Declaration, expression::Expression, keyword::Keyword, + module_item_list::{ModuleItem, ModuleItemList}, position::{Position, Span}, punctuator::Punctuator, statement::Statement, diff --git a/boa_ast/src/module_item_list/mod.rs b/boa_ast/src/module_item_list/mod.rs new file mode 100644 index 0000000000..25542d6192 --- /dev/null +++ b/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 { + #[derive(Debug)] + struct ExportedItemsVisitor<'vec>(&'vec mut Vec); + + impl<'ast> Visitor<'ast> for ExportedItemsVisitor<'_> { + type BreakTy = Infallible; + + fn visit_import_declaration( + &mut self, + _: &'ast ImportDeclaration, + ) -> ControlFlow { + ControlFlow::Continue(()) + } + fn visit_statement_list_item( + &mut self, + _: &'ast StatementListItem, + ) -> ControlFlow { + ControlFlow::Continue(()) + } + fn visit_export_specifier( + &mut self, + node: &'ast ExportSpecifier, + ) -> ControlFlow { + self.0.push(node.alias()); + ControlFlow::Continue(()) + } + fn visit_export_declaration( + &mut self, + node: &'ast ExportDeclaration, + ) -> ControlFlow { + 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 { + #[derive(Debug)] + struct ExportedBindingsVisitor<'vec>(&'vec mut FxHashSet); + + impl<'ast> Visitor<'ast> for ExportedBindingsVisitor<'_> { + type BreakTy = Infallible; + + fn visit_import_declaration( + &mut self, + _: &'ast ImportDeclaration, + ) -> ControlFlow { + ControlFlow::Continue(()) + } + fn visit_statement_list_item( + &mut self, + _: &'ast StatementListItem, + ) -> ControlFlow { + ControlFlow::Continue(()) + } + fn visit_export_specifier( + &mut self, + node: &'ast ExportSpecifier, + ) -> ControlFlow { + self.0.insert(Identifier::new(node.private_name())); + ControlFlow::Continue(()) + } + fn visit_export_declaration( + &mut self, + node: &'ast ExportDeclaration, + ) -> ControlFlow { + 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 From for ModuleItemList +where + T: Into>, +{ + #[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 + 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 + 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 + 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 + 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), + } + } +} diff --git a/boa_ast/src/operations.rs b/boa_ast/src/operations.rs index f4a7e65075..14ebb9a5a0 100644 --- a/boa_ast/src/operations.rs +++ b/boa_ast/src/operations.rs @@ -9,7 +9,7 @@ use boa_interner::Sym; use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ - declaration::VarDeclaration, + declaration::{ExportDeclaration, ImportDeclaration, VarDeclaration}, expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield}, function::{ 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. -trait IdentList { - fn add(&mut self, value: Identifier, function: bool); +pub(crate) trait IdentList { + fn add(&mut self, value: Sym, function: bool); } -impl IdentList for Vec { - fn add(&mut self, value: Identifier, _function: bool) { +impl IdentList for Vec { + fn add(&mut self, value: Sym, _function: bool) { self.push(value); } } +impl IdentList for Vec { + fn add(&mut self, value: Sym, _function: bool) { + self.push(Identifier::new(value)); + } +} + impl IdentList for Vec<(Identifier, bool)> { - fn add(&mut self, value: Identifier, function: bool) { - self.push((value, function)); + fn add(&mut self, value: Sym, function: bool) { + self.push((Identifier::new(value), function)); } } impl IdentList for FxHashSet { - fn add(&mut self, value: Identifier, _function: bool) { - self.insert(value); + fn add(&mut self, value: Sym, _function: bool) { + self.insert(Identifier::new(value)); } } /// The [`Visitor`] used to obtain the bound names of a node. #[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> { type BreakTy = Infallible; fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow { - self.0.add(*node, false); + self.0.add(node.sym(), false); ControlFlow::Continue(()) } @@ -337,41 +343,82 @@ impl<'ast, T: IdentList> Visitor<'ast> for BoundNamesVisitor<'_, T> { ControlFlow::Continue(()) } - // TODO: add "*default" for module default functions without name fn visit_function(&mut self, node: &'ast Function) -> ControlFlow { if let Some(ident) = node.name() { - self.0.add(ident, true); + self.0.add(ident.sym(), true); } ControlFlow::Continue(()) } fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow { if let Some(ident) = node.name() { - self.0.add(ident, false); + self.0.add(ident.sym(), false); } ControlFlow::Continue(()) } fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow { if let Some(ident) = node.name() { - self.0.add(ident, false); + self.0.add(ident.sym(), false); } ControlFlow::Continue(()) } fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow { if let Some(ident) = node.name() { - self.0.add(ident, false); + self.0.add(ident.sym(), false); } ControlFlow::Continue(()) } fn visit_class(&mut self, node: &'ast Class) -> ControlFlow { if let Some(ident) = node.name() { - self.0.add(ident, false); + self.0.add(ident.sym(), false); } ControlFlow::Continue(()) } + + fn visit_export_declaration( + &mut self, + node: &'ast ExportDeclaration, + ) -> ControlFlow { + 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. @@ -448,6 +495,21 @@ impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T> } ControlFlow::Continue(()) } + fn visit_import_declaration( + &mut self, + node: &'ast ImportDeclaration, + ) -> ControlFlow { + BoundNamesVisitor(self.0).visit_import_declaration(node) + } + fn visit_export_declaration( + &mut self, + node: &'ast ExportDeclaration, + ) -> ControlFlow { + if matches!(node, ExportDeclaration::VarStatement(_)) { + return ControlFlow::Continue(()); + } + BoundNamesVisitor(self.0).visit_export_declaration(node) + } // TODO: ScriptBody : StatementList // 1. Return TopLevelLexicallyDeclaredNames of StatementList. @@ -552,6 +614,25 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { node.visit_with(self) } + fn visit_import_declaration( + &mut self, + _: &'ast ImportDeclaration, + ) -> ControlFlow { + ControlFlow::Continue(()) + } + + fn visit_export_declaration( + &mut self, + node: &'ast ExportDeclaration, + ) -> ControlFlow { + match node { + ExportDeclaration::VarStatement(var) => { + BoundNamesVisitor(self.0).visit_var_declaration(var) + } + _ => ControlFlow::Continue(()), + } + } + // TODO: ScriptBody : StatementList // 1. Return TopLevelVarDeclaredNames of StatementList. // But we don't have that node yet. In the meantime, use `top_level_var_declared_names` directly. diff --git a/boa_ast/src/visitor.rs b/boa_ast/src/visitor.rs index 806579ed92..3654e57c82 100644 --- a/boa_ast/src/visitor.rs +++ b/boa_ast/src/visitor.rs @@ -7,7 +7,9 @@ use std::ops::ControlFlow; use crate::{ declaration::{ - Binding, Declaration, LexicalDeclaration, VarDeclaration, Variable, VariableList, + Binding, Declaration, ExportDeclaration, ExportSpecifier, ImportDeclaration, ImportKind, + ImportSpecifier, LexicalDeclaration, ModuleSpecifier, ReExportKind, VarDeclaration, + Variable, VariableList, }, expression::{ access::{ @@ -36,7 +38,7 @@ use crate::{ Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw, Try, }, - StatementList, StatementListItem, + ModuleItem, ModuleItemList, StatementList, StatementListItem, }; use boa_interner::Sym; @@ -194,6 +196,15 @@ node_ref! { ArrayPatternElement, PropertyAccessField, OptionalOperationKind, + ModuleItemList, + ModuleItem, + ModuleSpecifier, + ImportKind, + ImportDeclaration, + ImportSpecifier, + ReExportKind, + ExportDeclaration, + ExportSpecifier } /// Represents an AST visitor. @@ -282,6 +293,15 @@ pub trait Visitor<'ast>: Sized { define_visit!(visit_array_pattern_element, ArrayPatternElement); define_visit!(visit_property_access_field, PropertyAccessField); 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`. /// @@ -367,6 +387,15 @@ pub trait Visitor<'ast>: Sized { NodeRef::ArrayPatternElement(n) => self.visit_array_pattern_element(n), NodeRef::PropertyAccessField(n) => self.visit_property_access_field(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_property_access_field_mut, PropertyAccessField); 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`. /// @@ -542,6 +580,15 @@ pub trait VisitorMut<'ast>: Sized { NodeRefMut::ArrayPatternElement(n) => self.visit_array_pattern_element_mut(n), NodeRefMut::PropertyAccessField(n) => self.visit_property_access_field_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), } } } diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index 5a4fefb7de..f4418a547a 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -194,7 +194,7 @@ where S: AsRef<[u8]> + ?Sized, { 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}")) } @@ -232,8 +232,8 @@ fn generate_flowgraph( format: FlowgraphFormat, direction: Option, ) -> JsResult { - let ast = context.parse(Source::from_bytes(src))?; - let code = context.compile(&ast)?; + let ast = context.parse_script(Source::from_bytes(src))?; + let code = context.compile_script(&ast)?; let direction = match direction { Some(FlowgraphDirection::TopToBottom) | None => Direction::TopToBottom, @@ -281,7 +281,7 @@ fn main() -> Result<(), io::Error> { Err(v) => eprintln!("Uncaught {v}"), } } else { - match context.eval(Source::from_bytes(&buffer)) { + match context.eval_script(Source::from_bytes(&buffer)) { Ok(v) => println!("{}", v.display()), Err(v) => eprintln!("Uncaught {v}"), } @@ -338,7 +338,7 @@ fn main() -> Result<(), io::Error> { Err(v) => eprintln!("Uncaught {v}"), } } else { - match context.eval(Source::from_bytes(line.trim_end())) { + match context.eval_script(Source::from_bytes(line.trim_end())) { Ok(v) => { println!("{}", v.display()); } diff --git a/boa_engine/benches/full.rs b/boa_engine/benches/full.rs index f9a79b846d..3fe62e7940 100644 --- a/boa_engine/benches/full.rs +++ b/boa_engine/benches/full.rs @@ -25,7 +25,7 @@ macro_rules! full_benchmarks { static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); let mut context = Context::default(); 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")); 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| { 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")); let mut context = Context::default(); - let statement_list = context.parse(Source::from_bytes(CODE)).expect("parsing failed"); - let code_block = context.compile(&statement_list).unwrap(); + let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed"); + let code_block = context.compile_script(&statement_list).unwrap(); c.bench_function(concat!($id, " (Execution)"), move |b| { b.iter(|| { context.execute(black_box(code_block.clone())).unwrap() diff --git a/boa_engine/src/builtins/array/tests.rs b/boa_engine/src/builtins/array/tests.rs index 91e865e1c7..5501a1b30d 100644 --- a/boa_engine/src/builtins/array/tests.rs +++ b/boa_engine/src/builtins/array/tests.rs @@ -12,62 +12,64 @@ fn is_array() { var new_arr = new Array(); var many = ["a", "b", "c"]; "#; - context.eval(Source::from_bytes(init)).unwrap(); + context.eval_script(Source::from_bytes(init)).unwrap(); assert_eq!( context - .eval(Source::from_bytes("Array.isArray(empty)")) + .eval_script(Source::from_bytes("Array.isArray(empty)")) .unwrap(), JsValue::new(true) ); assert_eq!( context - .eval(Source::from_bytes("Array.isArray(new_arr)")) + .eval_script(Source::from_bytes("Array.isArray(new_arr)")) .unwrap(), JsValue::new(true) ); assert_eq!( context - .eval(Source::from_bytes("Array.isArray(many)")) + .eval_script(Source::from_bytes("Array.isArray(many)")) .unwrap(), JsValue::new(true) ); assert_eq!( context - .eval(Source::from_bytes("Array.isArray([1, 2, 3])")) + .eval_script(Source::from_bytes("Array.isArray([1, 2, 3])")) .unwrap(), JsValue::new(true) ); assert_eq!( context - .eval(Source::from_bytes("Array.isArray([])")) + .eval_script(Source::from_bytes("Array.isArray([])")) .unwrap(), JsValue::new(true) ); assert_eq!( context - .eval(Source::from_bytes("Array.isArray({})")) + .eval_script(Source::from_bytes("Array.isArray({})")) .unwrap(), JsValue::new(false) ); assert_eq!( context - .eval(Source::from_bytes("Array.isArray(new Array)")) + .eval_script(Source::from_bytes("Array.isArray(new Array)")) .unwrap(), JsValue::new(true) ); assert_eq!( - context.eval(Source::from_bytes("Array.isArray()")).unwrap(), + context + .eval_script(Source::from_bytes("Array.isArray()")) + .unwrap(), JsValue::new(false) ); assert_eq!( context - .eval(Source::from_bytes("Array.isArray({ constructor: Array })")) + .eval_script(Source::from_bytes("Array.isArray({ constructor: Array })")) .unwrap(), JsValue::new(false) ); assert_eq!( context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( "Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })" )) .unwrap(), @@ -75,13 +77,13 @@ fn is_array() { ); assert_eq!( context - .eval(Source::from_bytes("Array.isArray(17)")) + .eval_script(Source::from_bytes("Array.isArray(17)")) .unwrap(), JsValue::new(false) ); assert_eq!( context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( "Array.isArray({ __proto__: Array.prototype })" )) .unwrap(), @@ -89,7 +91,7 @@ fn is_array() { ); assert_eq!( context - .eval(Source::from_bytes("Array.isArray({ length: 0 })")) + .eval_script(Source::from_bytes("Array.isArray({ length: 0 })")) .unwrap(), JsValue::new(false) ); @@ -100,66 +102,66 @@ fn of() { let mut context = Context::default(); assert_eq!( context - .eval(Source::from_bytes("Array.of(1, 2, 3)")) + .eval_script(Source::from_bytes("Array.of(1, 2, 3)")) .unwrap() .to_string(&mut context) .unwrap(), context - .eval(Source::from_bytes("[1, 2, 3]")) + .eval_script(Source::from_bytes("[1, 2, 3]")) .unwrap() .to_string(&mut context) .unwrap() ); assert_eq!( context - .eval(Source::from_bytes("Array.of(1, 'a', [], undefined, null)")) + .eval_script(Source::from_bytes("Array.of(1, 'a', [], undefined, null)")) .unwrap() .to_string(&mut context) .unwrap(), context - .eval(Source::from_bytes("[1, 'a', [], undefined, null]")) + .eval_script(Source::from_bytes("[1, 'a', [], undefined, null]")) .unwrap() .to_string(&mut context) .unwrap() ); assert_eq!( context - .eval(Source::from_bytes("Array.of()")) + .eval_script(Source::from_bytes("Array.of()")) .unwrap() .to_string(&mut context) .unwrap(), context - .eval(Source::from_bytes("[]")) + .eval_script(Source::from_bytes("[]")) .unwrap() .to_string(&mut context) .unwrap() ); context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( r#"let a = Array.of.call(Date, "a", undefined, 3);"#, )) .unwrap(); assert_eq!( context - .eval(Source::from_bytes("a instanceof Date")) + .eval_script(Source::from_bytes("a instanceof Date")) .unwrap(), JsValue::new(true) ); assert_eq!( - context.eval(Source::from_bytes("a[0]")).unwrap(), + context.eval_script(Source::from_bytes("a[0]")).unwrap(), JsValue::new("a") ); assert_eq!( - context.eval(Source::from_bytes("a[1]")).unwrap(), + context.eval_script(Source::from_bytes("a[1]")).unwrap(), JsValue::undefined() ); assert_eq!( - context.eval(Source::from_bytes("a[2]")).unwrap(), + context.eval_script(Source::from_bytes("a[2]")).unwrap(), JsValue::new(3) ); assert_eq!( - context.eval(Source::from_bytes("a.length")).unwrap(), + context.eval_script(Source::from_bytes("a.length")).unwrap(), JsValue::new(3) ); } @@ -171,31 +173,31 @@ fn concat() { var empty = []; var one = [1]; "#; - context.eval(Source::from_bytes(init)).unwrap(); + context.eval_script(Source::from_bytes(init)).unwrap(); // Empty ++ Empty let ee = context - .eval(Source::from_bytes("empty.concat(empty)")) + .eval_script(Source::from_bytes("empty.concat(empty)")) .unwrap() .display() .to_string(); assert_eq!(ee, "[]"); // Empty ++ NonEmpty let en = context - .eval(Source::from_bytes("empty.concat(one)")) + .eval_script(Source::from_bytes("empty.concat(one)")) .unwrap() .display() .to_string(); assert_eq!(en, "[ 1 ]"); // NonEmpty ++ Empty let ne = context - .eval(Source::from_bytes("one.concat(empty)")) + .eval_script(Source::from_bytes("one.concat(empty)")) .unwrap() .display() .to_string(); assert_eq!(ne, "[ 1 ]"); // NonEmpty ++ NonEmpty let nn = context - .eval(Source::from_bytes("one.concat(one)")) + .eval_script(Source::from_bytes("one.concat(one)")) .unwrap() .display() .to_string(); diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index bdc86a5848..92a328a72f 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/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. let mut parser = Parser::new(Source::from_bytes(&script_string)); 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 unfiltered = context.execute(code_block)?; diff --git a/boa_engine/src/builtins/object/tests.rs b/boa_engine/src/builtins/object/tests.rs index 1d094c636a..866ae123f5 100644 --- a/boa_engine/src/builtins/object/tests.rs +++ b/boa_engine/src/builtins/object/tests.rs @@ -252,7 +252,7 @@ fn get_own_property_descriptor_1_arg_returns_undefined() { Object.getOwnPropertyDescriptor(obj) "#; assert_eq!( - context.eval(Source::from_bytes(code)).unwrap(), + context.eval_script(Source::from_bytes(code)).unwrap(), JsValue::undefined() ); } @@ -324,7 +324,7 @@ fn object_is_prototype_of() { "#; assert_eq!( - context.eval(Source::from_bytes(init)).unwrap(), + context.eval_script(Source::from_bytes(init)).unwrap(), JsValue::new(true) ); } diff --git a/boa_engine/src/builtins/promise/tests.rs b/boa_engine/src/builtins/promise/tests.rs index 0e86aa60af..2cc340a646 100644 --- a/boa_engine/src/builtins/promise/tests.rs +++ b/boa_engine/src/builtins/promise/tests.rs @@ -15,7 +15,7 @@ fn promise() { count += 1; 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)); context.run_jobs(); let after_completion = forward(&mut context, "count"); diff --git a/boa_engine/src/builtins/weak/weak_ref.rs b/boa_engine/src/builtins/weak/weak_ref.rs index 5d046fd749..527d24bd42 100644 --- a/boa_engine/src/builtins/weak/weak_ref.rs +++ b/boa_engine/src/builtins/weak/weak_ref.rs @@ -150,7 +150,7 @@ mod tests { let context = &mut Context::default(); assert!(context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( r#" var ptr; { @@ -167,7 +167,7 @@ mod tests { assert_eq!( context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( r#" ptr.deref() "# diff --git a/boa_engine/src/bytecompiler/class.rs b/boa_engine/src/bytecompiler/class.rs index 6942b2716f..7cbc9fb8b6 100644 --- a/boa_engine/src/bytecompiler/class.rs +++ b/boa_engine/src/bytecompiler/class.rs @@ -97,7 +97,7 @@ impl ByteCompiler<'_, '_> { } else { None }; - compiler.create_decls(expr.body(), false); + compiler.create_script_decls(expr.body(), false); compiler.compile_statement_list(expr.body(), false, false)?; if let Some(env_label) = env_label { let (num_bindings, compile_environment) = @@ -445,7 +445,7 @@ impl ByteCompiler<'_, '_> { .context .create_immutable_binding(class_name.into(), 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)?; let (num_bindings, compile_environment) = compiler.context.pop_compile_time_environment(); diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs index fb42e90f0b..ed28e53eb0 100644 --- a/boa_engine/src/bytecompiler/function.rs +++ b/boa_engine/src/bytecompiler/function.rs @@ -199,7 +199,7 @@ impl FunctionCompiler { compiler.emit_opcode(Opcode::Yield); } - compiler.create_decls(body, false); + compiler.create_script_decls(body, false); compiler.compile_statement_list(body, false, false)?; if let Some(env_label) = env_label { diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 899158677f..1551f63685 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -5,6 +5,7 @@ mod declaration; mod expression; mod function; mod jump_control; +mod module; mod statement; use crate::{ @@ -703,7 +704,7 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { self.context.push_compile_time_environment(strict); 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 { let expr_index = list @@ -1271,7 +1272,12 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { 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() { self.create_decls_from_stmt_list_item(node, configurable_globals); } diff --git a/boa_engine/src/bytecompiler/module.rs b/boa_engine/src/bytecompiler/module.rs new file mode 100644 index 0000000000..f5a2142959 --- /dev/null +++ b/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) + } + } + } +} diff --git a/boa_engine/src/bytecompiler/statement/block.rs b/boa_engine/src/bytecompiler/statement/block.rs index 512f5d3f77..00e9fa9e2b 100644 --- a/boa_engine/src/bytecompiler/statement/block.rs +++ b/boa_engine/src/bytecompiler/statement/block.rs @@ -13,7 +13,7 @@ impl ByteCompiler<'_, '_> { self.context.push_compile_time_environment(false); 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)?; let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); diff --git a/boa_engine/src/bytecompiler/statement/switch.rs b/boa_engine/src/bytecompiler/statement/switch.rs index a609f26e76..c4c988340d 100644 --- a/boa_engine/src/bytecompiler/statement/switch.rs +++ b/boa_engine/src/bytecompiler/statement/switch.rs @@ -12,7 +12,7 @@ impl ByteCompiler<'_, '_> { self.context.push_compile_time_environment(false); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); 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); @@ -35,7 +35,7 @@ impl ByteCompiler<'_, '_> { self.patch_jump(exit); 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)?; } diff --git a/boa_engine/src/bytecompiler/statement/try.rs b/boa_engine/src/bytecompiler/statement/try.rs index 9983f4ac1b..f3641bf434 100644 --- a/boa_engine/src/bytecompiler/statement/try.rs +++ b/boa_engine/src/bytecompiler/statement/try.rs @@ -19,7 +19,7 @@ impl ByteCompiler<'_, '_> { self.context.push_compile_time_environment(false); 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)?; let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); @@ -58,7 +58,7 @@ impl ByteCompiler<'_, '_> { 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( catch.block().statement_list(), use_expr, @@ -96,7 +96,7 @@ impl ByteCompiler<'_, '_> { self.context.push_compile_time_environment(false); 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( finally.block().statement_list(), false, diff --git a/boa_engine/src/context/hooks.rs b/boa_engine/src/context/hooks.rs index 44f471caba..f4b2802b89 100644 --- a/boa_engine/src/context/hooks.rs +++ b/boa_engine/src/context/hooks.rs @@ -32,7 +32,7 @@ use super::intrinsics::Intrinsics; /// } /// let hooks = Hooks; // Can have additional state. /// 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"); /// ``` /// diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 385f034ead..e15ff9e27d 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -28,8 +28,7 @@ use crate::{ vm::{CallFrame, CodeBlock, Vm}, JsResult, JsValue, Source, }; - -use boa_ast::StatementList; +use boa_ast::{ModuleItemList, StatementList}; use boa_gc::Gc; use boa_interner::{Interner, Sym}; use boa_parser::{Error as ParseError, Parser}; @@ -65,7 +64,7 @@ use boa_profiler::Profiler; /// let mut context = Context::default(); /// /// // 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. /// let arg = ObjectInitializer::new(&mut context) @@ -73,7 +72,7 @@ use boa_profiler::Profiler; /// .build(); /// 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)) /// ``` @@ -141,7 +140,7 @@ impl Context<'_> { 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. /// /// # Examples @@ -150,7 +149,7 @@ impl Context<'_> { /// let mut context = Context::default(); /// /// 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_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`] /// on the context or [`JobQueue::run_jobs`] on the provided queue to run them. #[allow(clippy::unit_arg, clippy::drop_copy)] - pub fn eval(&mut self, src: Source<'_, R>) -> JsResult { - let main_timer = Profiler::global().start_event("Evaluation", "Main"); + pub fn eval_script(&mut self, src: Source<'_, R>) -> JsResult { + let main_timer = Profiler::global().start_event("Script evaluation", "Main"); + + let script = self.parse_script(src)?; + let code_block = self.compile_script(&script)?; + let result = self.execute(code_block); + + // 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(&mut self, src: Source<'_, R>) -> JsResult { + let main_timer = Profiler::global().start_event("Module evaluation", "Main"); - let script = self.parse(src)?; - let code_block = self.compile(&script)?; + let module_item_list = self.parse_module(src)?; + let code_block = self.compile_module(&module_item_list)?; let result = self.execute(code_block); // The main_timer needs to be dropped before the Profiler is. @@ -174,30 +203,54 @@ impl Context<'_> { } /// Parse the given source script. - pub fn parse(&mut self, src: Source<'_, R>) -> Result { - let _timer = Profiler::global().start_event("Parsing", "Main"); + pub fn parse_script( + &mut self, + src: Source<'_, R>, + ) -> Result { + let _timer = Profiler::global().start_event("Script parsing", "Main"); let mut parser = Parser::new(src); if self.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. - pub fn compile(&mut self, statement_list: &StatementList) -> JsResult> { - let _timer = Profiler::global().start_event("Compilation", "Main"); + /// Parse the given source script. + pub fn parse_module( + &mut self, + src: Source<'_, R>, + ) -> Result { + 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> { + let _timer = Profiler::global().start_event("Script compilation", "Main"); 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)?; 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> { + 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. /// /// Since this function receives a `Gc`, cloning the code is very cheap, since it's /// just a pointer copy. Therefore, if you'd like to execute the same `CodeBlock` multiple /// times, there is no need to re-compile it, and you can just call `clone()` on the - /// `Gc` returned by the [`Self::compile()`] function. + /// `Gc` 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`] /// on the context or [`JobQueue::run_jobs`] on the provided queue to run them. @@ -373,7 +426,7 @@ impl Context<'_> { self.vm.trace = trace; } - /// Executes all code in strict mode. + /// Changes the strictness mode of the context. pub fn strict(&mut self, strict: bool) { self.strict = strict; } @@ -417,7 +470,7 @@ impl Context<'_> { ) -> JsResult> { let _timer = Profiler::global().start_event("Compilation", "Main"); 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)?; Ok(Gc::new(compiler.finish())) } diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index b896965b5b..43281e9a5e 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -189,7 +189,7 @@ where S: AsRef<[u8]> + ?Sized, { context - .eval(Source::from_bytes(src)) + .eval_script(Source::from_bytes(src)) .map_or_else(|e| format!("Uncaught {e}"), |v| v.display().to_string()) } @@ -207,7 +207,7 @@ pub(crate) fn forward_val + ?Sized>( 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. drop(main_timer); @@ -219,7 +219,7 @@ pub(crate) fn forward_val + ?Sized>( /// Create a clean Context and execute the code #[cfg(test)] pub(crate) fn exec + ?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(), Err(error) => error.to_string(), } diff --git a/boa_engine/src/tests.rs b/boa_engine/src/tests.rs index e5d0384a20..6b75f866e3 100644 --- a/boa_engine/src/tests.rs +++ b/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] diff --git a/boa_engine/src/value/serde_json.rs b/boa_engine/src/value/serde_json.rs index 7f49947d23..c6d271f6a8 100644 --- a/boa_engine/src/value/serde_json.rs +++ b/boa_engine/src/value/serde_json.rs @@ -227,7 +227,7 @@ mod tests { let mut context = Context::default(); let add = context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( r#" 1000000 + 500 "#, @@ -237,7 +237,7 @@ mod tests { assert_eq!(add, 1_000_500); let sub = context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( r#" 1000000 - 500 "#, @@ -247,7 +247,7 @@ mod tests { assert_eq!(sub, 999_500); let mult = context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( r#" 1000000 * 500 "#, @@ -257,7 +257,7 @@ mod tests { assert_eq!(mult, 500_000_000); let div = context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( r#" 1000000 / 500 "#, @@ -267,7 +267,7 @@ mod tests { assert_eq!(div, 2000); let rem = context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( r#" 233894 % 500 "#, @@ -277,7 +277,7 @@ mod tests { assert_eq!(rem, 394); let pow = context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( r#" 36 ** 5 "#, diff --git a/boa_engine/src/vm/tests.rs b/boa_engine/src/vm/tests.rs index d1dd2cca2b..45f1c5546d 100644 --- a/boa_engine/src/vm/tests.rs +++ b/boa_engine/src/vm/tests.rs @@ -47,7 +47,7 @@ fn try_catch_finally_from_init() { assert_eq!( Context::default() - .eval(Source::from_bytes(source)) + .eval_script(Source::from_bytes(source)) .unwrap_err() .as_opaque() .unwrap(), @@ -70,7 +70,9 @@ fn multiple_catches() { "#; assert_eq!( - Context::default().eval(Source::from_bytes(source)).unwrap(), + Context::default() + .eval_script(Source::from_bytes(source)) + .unwrap(), JsValue::Undefined ); } @@ -89,7 +91,9 @@ fn use_last_expr_try_block() { "#; assert_eq!( - Context::default().eval(Source::from_bytes(source)).unwrap(), + Context::default() + .eval_script(Source::from_bytes(source)) + .unwrap(), JsValue::from("Hello!") ); } @@ -107,7 +111,9 @@ fn use_last_expr_catch_block() { "#; assert_eq!( - Context::default().eval(Source::from_bytes(source)).unwrap(), + Context::default() + .eval_script(Source::from_bytes(source)) + .unwrap(), JsValue::from("Hello!") ); } @@ -123,7 +129,9 @@ fn no_use_last_expr_finally_block() { "#; assert_eq!( - Context::default().eval(Source::from_bytes(source)).unwrap(), + Context::default() + .eval_script(Source::from_bytes(source)) + .unwrap(), JsValue::undefined() ); } @@ -142,7 +150,9 @@ fn finally_block_binding_env() { "#; assert_eq!( - Context::default().eval(Source::from_bytes(source)).unwrap(), + Context::default() + .eval_script(Source::from_bytes(source)) + .unwrap(), JsValue::from("Hey hey people") ); } @@ -161,7 +171,9 @@ fn run_super_method_in_object() { "#; assert_eq!( - Context::default().eval(Source::from_bytes(source)).unwrap(), + Context::default() + .eval_script(Source::from_bytes(source)) + .unwrap(), JsValue::from("super") ); } @@ -187,7 +199,9 @@ fn get_reference_by_super() { "#; assert_eq!( - Context::default().eval(Source::from_bytes(source)).unwrap(), + Context::default() + .eval_script(Source::from_bytes(source)) + .unwrap(), JsValue::from("ab") ); } diff --git a/boa_examples/src/bin/classes.rs b/boa_examples/src/bin/classes.rs index 4cb5b0266c..3782ba87d9 100644 --- a/boa_examples/src/bin/classes.rs +++ b/boa_examples/src/bin/classes.rs @@ -134,7 +134,7 @@ fn main() { // Having done all of that, we can execute Javascript code with `eval`, // and access the `Person` class defined in Rust! context - .eval(Source::from_bytes( + .eval_script(Source::from_bytes( r" let person = new Person('John', 19); person.sayHello(); diff --git a/boa_examples/src/bin/closures.rs b/boa_examples/src/bin/closures.rs index 7b396cba80..bcba8fe508 100644 --- a/boa_examples/src/bin/closures.rs +++ b/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 // inside Javascript! @@ -117,13 +120,13 @@ fn main() -> Result<(), JsError> { ); assert_eq!( - context.eval(Source::from_bytes("createMessage()"))?, + context.eval_script(Source::from_bytes("createMessage()"))?, "message from `Boa dev`: Hello!".into() ); // The data mutates between calls 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() ); @@ -167,7 +170,7 @@ fn main() -> Result<(), JsError> { ); // 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 .as_object() .cloned() @@ -178,7 +181,7 @@ fn main() -> Result<(), JsError> { assert_eq!(array.get(1, &mut context)?, JsValue::undefined()); // First call should return the array `[0, 1]`. - let result = context.eval(Source::from_bytes("enumerate()"))?; + let result = context.eval_script(Source::from_bytes("enumerate()"))?; let object = result .as_object() .cloned() diff --git a/boa_examples/src/bin/commuter_visitor.rs b/boa_examples/src/bin/commuter_visitor.rs index 6f1a59c7ab..d266682a69 100644 --- a/boa_examples/src/bin/commuter_visitor.rs +++ b/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()); 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(); diff --git a/boa_examples/src/bin/loadfile.rs b/boa_examples/src/bin/loadfile.rs index 72a0784bd0..07492b0cd8 100644 --- a/boa_examples/src/bin/loadfile.rs +++ b/boa_examples/src/bin/loadfile.rs @@ -13,7 +13,7 @@ fn main() { // Instantiate the execution context let mut context = Context::default(); // Parse the source code - match context.eval(src) { + match context.eval_script(src) { Ok(res) => { println!( "{}", diff --git a/boa_examples/src/bin/loadstring.rs b/boa_examples/src/bin/loadstring.rs index 4894f9625f..cd394df6f8 100644 --- a/boa_examples/src/bin/loadstring.rs +++ b/boa_examples/src/bin/loadstring.rs @@ -9,7 +9,7 @@ fn main() { let mut context = Context::default(); // Parse the source code - match context.eval(Source::from_bytes(js_code)) { + match context.eval_script(Source::from_bytes(js_code)) { Ok(res) => { println!( "{}", diff --git a/boa_examples/src/bin/modulehandler.rs b/boa_examples/src/bin/modulehandler.rs index f3214b6eee..6b63b25b28 100644 --- a/boa_examples/src/bin/modulehandler.rs +++ b/boa_examples/src/bin/modulehandler.rs @@ -31,7 +31,8 @@ fn main() { // Instantiating the engine with the execution context // 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 @@ -52,7 +53,8 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult", + ("
", MAIN), "raw", "anonymous", "async", "of", "target", + "as", + "from", "__proto__", "name", "await", + ("*default*", DEFAULT_EXPORT) } diff --git a/boa_macros/src/lib.rs b/boa_macros/src/lib.rs index e8b61776d4..cb7c68b223 100644 --- a/boa_macros/src/lib.rs +++ b/boa_macros/src/lib.rs @@ -59,20 +59,76 @@ use proc_macro::TokenStream; use proc_macro2::Ident; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, punctuated::Punctuated, - LitStr, Token, + Expr, ExprLit, Lit, LitStr, Token, }; use synstructure::{decl_derive, AddBounds, Structure}; -struct Syms(Vec); +struct Static { + literal: LitStr, + ident: Ident, +} + +impl Parse for Static { + fn parse(input: ParseStream<'_>) -> syn::Result { + 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.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); impl Parse for Syms { fn parse(input: ParseStream<'_>) -> syn::Result { - let parsed = Punctuated::::parse_terminated(input)?; + let parsed = Punctuated::::parse_terminated(input)?; let literals = parsed.into_iter().collect(); Ok(Self(literals)) } @@ -84,22 +140,15 @@ pub fn static_syms(input: TokenStream) -> TokenStream { let literals = parse_macro_input!(input as Syms).0; let consts = literals.iter().enumerate().map(|(mut idx, lit)| { - let ident = lit.value(); - let (doc, ident) = match &*ident { - "" => ( - String::from("Symbol for the empty string."), - String::from("EMPTY_STRING"), - ), - "
" => ( - String::from("Symbol for the `
` string."), - String::from("MAIN"), - ), - ident => ( - format!("Symbol for the `{ident}` string.",), - ident.to_uppercase(), - ), - }; - let ident = Ident::new(&ident, lit.span()); + let doc = format!( + "Symbol for the \"{}\" string.", + lit.literal + .value() + .replace('<', r"\<") + .replace('>', r"\>") + .replace('*', r"\*") + ); + let ident = &lit.ident; idx += 1; quote! { #[doc = #doc] @@ -107,6 +156,8 @@ pub fn static_syms(input: TokenStream) -> TokenStream { } }); + let literals = literals.iter().map(|lit| &lit.literal).collect::>(); + let caches = quote! { type Set = ::indexmap::IndexSet>; diff --git a/boa_parser/src/error.rs b/boa_parser/src/error.rs index b7f3eb1920..26a26056c1 100644 --- a/boa_parser/src/error.rs +++ b/boa_parser/src/error.rs @@ -66,7 +66,7 @@ pub enum Error { /// Catch all General Error General { /// The error message. - message: &'static str, + message: Box, /// Position of the source code where the error occurred. position: Position, @@ -115,22 +115,29 @@ impl Error { } /// Creates a "general" parsing error. - pub(crate) const fn general(message: &'static str, position: Position) -> Self { - Self::General { message, position } + pub(crate) fn general(message: S, position: Position) -> Self + where + S: Into>, + { + 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. - 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 { - 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 } } /// 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 { - 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, } } diff --git a/boa_parser/src/lexer/error.rs b/boa_parser/src/lexer/error.rs index 5f569c7dc1..e3547793fb 100644 --- a/boa_parser/src/lexer/error.rs +++ b/boa_parser/src/lexer/error.rs @@ -33,6 +33,7 @@ impl From for Error { impl Error { /// Creates a new syntax error. + #[inline] pub(super) fn syntax(err: M, pos: P) -> Self where M: Into>, diff --git a/boa_parser/src/lexer/token.rs b/boa_parser/src/lexer/token.rs index 34111c095a..5db6161b31 100644 --- a/boa_parser/src/lexer/token.rs +++ b/boa_parser/src/lexer/token.rs @@ -48,6 +48,7 @@ impl Token { } /// Converts the token to a `String`. + #[inline] pub(crate) fn to_string(&self, interner: &Interner) -> String { self.kind.to_string(interner) } @@ -198,6 +199,7 @@ impl TokenKind { } /// Creates a `NumericLiteral` token kind. + #[must_use] pub fn numeric_literal(lit: L) -> Self where L: Into, diff --git a/boa_parser/src/parser/expression/assignment/arrow_function.rs b/boa_parser/src/parser/expression/assignment/arrow_function.rs index dc4f42a941..12ab91bcac 100644 --- a/boa_parser/src/parser/expression/assignment/arrow_function.rs +++ b/boa_parser/src/parser/expression/assignment/arrow_function.rs @@ -161,6 +161,7 @@ where &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; Ok(ast::function::ArrowFunction::new(self.name, params, body)) diff --git a/boa_parser/src/parser/expression/assignment/async_arrow_function.rs b/boa_parser/src/parser/expression/assignment/async_arrow_function.rs index c1cc307b53..c3120bf7a9 100644 --- a/boa_parser/src/parser/expression/assignment/async_arrow_function.rs +++ b/boa_parser/src/parser/expression/assignment/async_arrow_function.rs @@ -147,6 +147,7 @@ where &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; Ok(ast::function::AsyncArrowFunction::new( diff --git a/boa_parser/src/parser/expression/assignment/mod.rs b/boa_parser/src/parser/expression/assignment/mod.rs index 90ebf72b00..4c830fd424 100644 --- a/boa_parser/src/parser/expression/assignment/mod.rs +++ b/boa_parser/src/parser/expression/assignment/mod.rs @@ -228,6 +228,7 @@ where &bound_names(¶meters), &top_level_lexically_declared_names(&body), position, + interner, )?; return Ok(boa_ast::function::ArrowFunction::new(self.name, parameters, body).into()); diff --git a/boa_parser/src/parser/expression/identifiers.rs b/boa_parser/src/parser/expression/identifiers.rs index 658650d245..a3618dae38 100644 --- a/boa_parser/src/parser/expression/identifiers.rs +++ b/boa_parser/src/parser/expression/identifiers.rs @@ -29,6 +29,7 @@ pub(in crate::parser) struct IdentifierReference { impl IdentifierReference { /// Creates a new `IdentifierReference` parser. + #[inline] pub(in crate::parser) fn new(allow_yield: Y, allow_await: A) -> Self where Y: Into, @@ -82,6 +83,7 @@ pub(in crate::parser) struct BindingIdentifier { impl BindingIdentifier { /// Creates a new `BindingIdentifier` parser. + #[inline] pub(in crate::parser) fn new(allow_yield: Y, allow_await: A) -> Self where Y: Into, diff --git a/boa_parser/src/parser/expression/left_hand_side/optional/tests.rs b/boa_parser/src/parser/expression/left_hand_side/optional/tests.rs index c3c997f1f6..cce4ce68e6 100644 --- a/boa_parser/src/parser/expression/left_hand_side/optional/tests.rs +++ b/boa_parser/src/parser/expression/left_hand_side/optional/tests.rs @@ -1,7 +1,5 @@ -use boa_interner::Interner; -use boa_macros::utf16; +use crate::parser::tests::{check_invalid_script, check_script_parser}; -use crate::parser::tests::{check_invalid, check_script_parser}; use boa_ast::{ expression::{ access::PropertyAccessField, literal::Literal, Identifier, Optional, OptionalOperation, @@ -9,6 +7,8 @@ use boa_ast::{ }, Expression, Statement, }; +use boa_interner::Interner; +use boa_macros::utf16; #[test] fn simple() { @@ -81,9 +81,9 @@ fn complex_chain() { #[test] fn reject_templates() { - check_invalid("console.log?.`Hello`"); - check_invalid("console?.log`Hello`"); - check_invalid( + check_invalid_script("console.log?.`Hello`"); + check_invalid_script("console?.log`Hello`"); + check_invalid_script( r#" const a = console?.log `Hello`"#, diff --git a/boa_parser/src/parser/expression/mod.rs b/boa_parser/src/parser/expression/mod.rs index 1d8c8148af..4f304c28dc 100644 --- a/boa_parser/src/parser/expression/mod.rs +++ b/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. -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() { Sym::ARGUMENTS => Err(Error::general( - "unexpected identifier 'arguments' in strict mode", + "unexpected identifier `arguments` in strict mode", position, )), Sym::EVAL => Err(Error::general( - "unexpected identifier 'eval' in strict mode", + "unexpected identifier `eval` in strict mode", position, )), _ => Ok(()), diff --git a/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs b/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs index de837ad361..0a43bbdccd 100644 --- a/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs @@ -142,6 +142,7 @@ where &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; let function = AsyncFunction::new(name.or(self.name), params, body, name.is_some()); diff --git a/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs b/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs index 4d0703a9db..f756517154 100644 --- a/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs @@ -179,6 +179,7 @@ where &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; let function = AsyncGenerator::new(name.or(self.name), params, body, name.is_some()); diff --git a/boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs b/boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs index ad0076da5f..2db94ede44 100644 --- a/boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs +++ b/boa_parser/src/parser/expression/primary/async_generator_expression/tests.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use crate::parser::tests::check_script_parser; use boa_ast::{ declaration::{LexicalDeclaration, Variable}, diff --git a/boa_parser/src/parser/expression/primary/function_expression/mod.rs b/boa_parser/src/parser/expression/primary/function_expression/mod.rs index 55f8edc4fc..4e7d8ed788 100644 --- a/boa_parser/src/parser/expression/primary/function_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/function_expression/mod.rs @@ -137,6 +137,7 @@ where &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; let function = diff --git a/boa_parser/src/parser/expression/primary/generator_expression/mod.rs b/boa_parser/src/parser/expression/primary/generator_expression/mod.rs index 4c28249639..af33bd6793 100644 --- a/boa_parser/src/parser/expression/primary/generator_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/generator_expression/mod.rs @@ -145,6 +145,7 @@ where &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; // It is a Syntax Error if FormalParameters Contains YieldExpression is true. diff --git a/boa_parser/src/parser/expression/primary/object_initializer/mod.rs b/boa_parser/src/parser/expression/primary/object_initializer/mod.rs index 074e27ee7a..ef63c74f93 100644 --- a/boa_parser/src/parser/expression/primary/object_initializer/mod.rs +++ b/boa_parser/src/parser/expression/primary/object_initializer/mod.rs @@ -436,6 +436,7 @@ where &bound_names(¶meters), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; let name = property_name.literal().map(|name| { @@ -511,6 +512,7 @@ where &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; let method = MethodDefinition::Ordinary(Function::new( @@ -592,10 +594,17 @@ where } TokenKind::NullLiteral => (Sym::NULL).into(), TokenKind::BooleanLiteral(bool) => match bool { - true => (interner.get_or_intern_static("true", utf16!("true"))).into(), - false => (interner.get_or_intern_static("false", utf16!("false"))).into(), + true => Sym::TRUE.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); Ok(name) @@ -786,6 +795,7 @@ where &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; let method = MethodDefinition::Generator(Generator::new( @@ -901,6 +911,7 @@ where &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; let method = MethodDefinition::AsyncGenerator(AsyncGenerator::new( @@ -993,6 +1004,7 @@ where &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; let method = MethodDefinition::Async(AsyncFunction::new( diff --git a/boa_parser/src/parser/expression/primary/object_initializer/tests.rs b/boa_parser/src/parser/expression/primary/object_initializer/tests.rs index faf4225332..af54d60f07 100644 --- a/boa_parser/src/parser/expression/primary/object_initializer/tests.rs +++ b/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::{ declaration::{LexicalDeclaration, Variable}, expression::{ @@ -12,7 +12,7 @@ use boa_ast::{ property::{MethodDefinition, PropertyDefinition, PropertyName}, Declaration, StatementList, }; -use boa_interner::Interner; +use boa_interner::{Interner, Sym}; use boa_macros::utf16; /// Checks object literal parsing. @@ -239,7 +239,7 @@ fn check_object_short_function_get() { let interner = &mut Interner::default(); let object_properties = vec![PropertyDefinition::MethodDefinition( - interner.get_or_intern_static("get", utf16!("get")).into(), + Sym::GET.into(), MethodDefinition::Ordinary(Function::new( Some(interner.get_or_intern_static("get", utf16!("get")).into()), FormalParameterList::default(), @@ -270,7 +270,7 @@ fn check_object_short_function_set() { let interner = &mut Interner::default(); let object_properties = vec![PropertyDefinition::MethodDefinition( - interner.get_or_intern_static("set", utf16!("set")).into(), + Sym::SET.into(), MethodDefinition::Ordinary(Function::new( Some(interner.get_or_intern_static("set", utf16!("set")).into()), FormalParameterList::default(), @@ -485,7 +485,7 @@ fn check_async_generator_method() { #[test] fn check_async_method_lineterminator() { - check_invalid( + check_invalid_script( "const x = { async dive(){} @@ -496,7 +496,7 @@ fn check_async_method_lineterminator() { #[test] fn check_async_gen_method_lineterminator() { - check_invalid( + check_invalid_script( "const x = { async * vroom() {} diff --git a/boa_parser/src/parser/expression/tests.rs b/boa_parser/src/parser/expression/tests.rs index 100ecb956b..0b9a2ae464 100644 --- a/boa_parser/src/parser/expression/tests.rs +++ b/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::{ declaration::{LexicalDeclaration, Variable}, expression::{ @@ -684,10 +684,10 @@ fn check_logical_expressions() { interner, ); - check_invalid("a ?? b && c"); - check_invalid("a && b ?? c"); - check_invalid("a ?? b || c"); - check_invalid("a || b ?? c"); + check_invalid_script("a ?? b && c"); + check_invalid_script("a && b ?? c"); + check_invalid_script("a ?? b || c"); + check_invalid_script("a || b ?? c"); } macro_rules! check_non_reserved_identifier { diff --git a/boa_parser/src/parser/function/tests.rs b/boa_parser/src/parser/function/tests.rs index f607d9fb9d..0c4140c7e7 100644 --- a/boa_parser/src/parser/function/tests.rs +++ b/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::{ declaration::{LexicalDeclaration, Variable}, expression::{ @@ -81,7 +81,7 @@ fn check_duplicates_strict_off() { /// Checks if duplicate parameter names are an error with strict mode on. #[test] 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. diff --git a/boa_parser/src/parser/mod.rs b/boa_parser/src/parser/mod.rs index c9c5bc5e67..acd177c88a 100644 --- a/boa_parser/src/parser/mod.rs +++ b/boa_parser/src/parser/mod.rs @@ -21,9 +21,10 @@ use boa_ast::{ expression::Identifier, function::FormalParameterList, 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 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. /// /// [spec]: https://tc39.es/ecma262/#prod-Script - pub fn parse_all(&mut self, interner: &mut Interner) -> ParseResult { + pub fn parse_script(&mut self, interner: &mut Interner) -> ParseResult { 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 + where + R: Read, + { + Module.parse(&mut self.cursor, interner) + } + /// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec] /// /// Parses the source text input of an `eval` call. @@ -232,7 +248,6 @@ where .parse(cursor, interner)?; // 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(); for name in top_level_lexically_declared_names(&statement_list) { 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) { if lexical_names.contains(&name) { return Err(Error::general( @@ -314,6 +330,13 @@ where 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) @@ -325,13 +348,17 @@ fn name_in_lexically_declared_names( bound_names: &[Identifier], lexical_names: &[Identifier], position: Position, + interner: &Interner, ) -> ParseResult<()> { for name in bound_names { if lexical_names.contains(name) { - return Err(Error::General { - message: "formal parameter declared in lexically declared names", + return Err(Error::general( + format!( + "formal parameter `{}` declared in lexically declared names", + interner.resolve_expect(name.sym()) + ), position, - }); + )); } } Ok(()) @@ -348,3 +375,111 @@ impl OrAbrupt for ParseResult> { 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 TokenParser for Module +where + R: Read, +{ + type Output = ModuleItemList; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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) + } +} diff --git a/boa_parser/src/parser/statement/block/tests.rs b/boa_parser/src/parser/statement/block/tests.rs index 2312db7115..743415dc80 100644 --- a/boa_parser/src/parser/statement/block/tests.rs +++ b/boa_parser/src/parser/statement/block/tests.rs @@ -1,7 +1,5 @@ //! Block statement parsing tests. -use std::convert::TryInto; - use crate::parser::tests::check_script_parser; use boa_ast::{ declaration::{VarDeclaration, Variable}, diff --git a/boa_parser/src/parser/statement/break_stm/tests.rs b/boa_parser/src/parser/statement/break_stm/tests.rs index 1c39ea3db7..62376923f2 100644 --- a/boa_parser/src/parser/statement/break_stm/tests.rs +++ b/boa_parser/src/parser/statement/break_stm/tests.rs @@ -4,7 +4,7 @@ use boa_ast::{ statement::{Block, Break, WhileLoop}, Statement, StatementListItem, }; -use boa_interner::Interner; +use boa_interner::{Interner, Sym}; use boa_macros::utf16; #[test] @@ -114,9 +114,7 @@ fn reserved_label() { vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(Some( - interner.get_or_intern_static("await", utf16!("await")), - )), + Break::new(Some(Sym::AWAIT)), ))]) .into(), )) @@ -132,9 +130,7 @@ fn reserved_label() { vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(Some( - interner.get_or_intern_static("yield", utf16!("yield")), - )), + Break::new(Some(Sym::YIELD)), ))]) .into(), )) diff --git a/boa_parser/src/parser/statement/continue_stm/tests.rs b/boa_parser/src/parser/statement/continue_stm/tests.rs index e663cea821..9a95141109 100644 --- a/boa_parser/src/parser/statement/continue_stm/tests.rs +++ b/boa_parser/src/parser/statement/continue_stm/tests.rs @@ -4,7 +4,7 @@ use boa_ast::{ statement::{Block, Continue, WhileLoop}, Statement, StatementListItem, }; -use boa_interner::Interner; +use boa_interner::{Interner, Sym}; use boa_macros::utf16; #[test] @@ -114,9 +114,7 @@ fn reserved_label() { vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), Block::from(vec![StatementListItem::Statement(Statement::Continue( - Continue::new(Some( - interner.get_or_intern_static("await", utf16!("await")), - )), + Continue::new(Some(Sym::AWAIT)), ))]) .into(), )) @@ -132,9 +130,7 @@ fn reserved_label() { vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), Block::from(vec![StatementListItem::Statement(Statement::Continue( - Continue::new(Some( - interner.get_or_intern_static("yield", utf16!("yield")), - )), + Continue::new(Some(Sym::YIELD)), ))]) .into(), )) diff --git a/boa_parser/src/parser/statement/declaration/export.rs b/boa_parser/src/parser/statement/declaration/export.rs new file mode 100644 index 0000000000..bd1cddfbcc --- /dev/null +++ b/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 TokenParser for ExportDeclaration +where + R: Read, +{ + type Output = AstExportDeclaration; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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 TokenParser for NamedExports +where + R: Read, +{ + type Output = Box<[boa_ast::declaration::ExportSpecifier]>; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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 TokenParser for ModuleExportName +where + R: Read, +{ + type Output = Sym; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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 TokenParser for ExportSpecifier +where + R: Read, +{ + type Output = boa_ast::declaration::ExportSpecifier; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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, + )) + } + } +} diff --git a/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/mod.rs b/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/mod.rs index df909053d7..ea221a9f03 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/mod.rs +++ b/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 /// [spec]: https://tc39.es/ecma262/#prod-AsyncFunctionDeclaration #[derive(Debug, Clone, Copy)] -pub(super) struct AsyncFunctionDeclaration { +pub(in crate::parser) struct AsyncFunctionDeclaration { allow_yield: AllowYield, allow_await: AllowAwait, is_default: AllowDefault, @@ -26,7 +26,7 @@ pub(super) struct AsyncFunctionDeclaration { impl AsyncFunctionDeclaration { /// Creates a new `FunctionDeclaration` parser. - pub(super) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self + pub(in crate::parser) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self where Y: Into, A: Into, diff --git a/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs b/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs index a32c24d20e..2cc0f15f03 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs @@ -3,7 +3,7 @@ use boa_ast::{ function::{AsyncFunction, FormalParameterList}, Declaration, StatementList, }; -use boa_interner::Interner; +use boa_interner::{Interner, Sym}; use boa_macros::utf16; /// Async function declaration parsing. @@ -34,11 +34,7 @@ fn async_function_declaration_keywords() { check_script_parser( "async function yield() {}", vec![Declaration::AsyncFunction(AsyncFunction::new( - Some( - interner - .get_or_intern_static("yield", utf16!("yield")) - .into(), - ), + Some(Sym::YIELD.into()), FormalParameterList::default(), StatementList::default(), false, @@ -51,11 +47,7 @@ fn async_function_declaration_keywords() { check_script_parser( "async function await() {}", vec![Declaration::AsyncFunction(AsyncFunction::new( - Some( - interner - .get_or_intern_static("await", utf16!("await")) - .into(), - ), + Some(Sym::AWAIT.into()), FormalParameterList::default(), StatementList::default(), false, diff --git a/boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/mod.rs b/boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/mod.rs index 2cbabd4f7e..986dab02d6 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/async_generator_decl/mod.rs +++ b/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 #[derive(Debug, Clone, Copy)] -pub(super) struct AsyncGeneratorDeclaration { +pub(in crate::parser) struct AsyncGeneratorDeclaration { allow_yield: AllowYield, allow_await: AllowAwait, is_default: AllowDefault, @@ -29,7 +29,7 @@ pub(super) struct AsyncGeneratorDeclaration { impl AsyncGeneratorDeclaration { /// Creates a new `AsyncGeneratorDeclaration` parser. - pub(super) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self + pub(in crate::parser) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self where Y: Into, A: Into, diff --git a/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs b/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs index 503e955bea..deaa12066e 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -24,7 +24,7 @@ use boa_ast::{ function::{self, Class, FormalParameterList, Function}, operations::{contains, contains_arguments, has_direct_super, ContainsSymbol}, property::{ClassElementName, MethodDefinition}, - Declaration, Expression, Keyword, Punctuator, + Expression, Keyword, Punctuator, }; use boa_interner::{Interner, Sym}; 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 /// [spec]: https://tc39.es/ecma262/#prod-ClassDeclaration #[derive(Debug, Clone, Copy)] -pub(super) struct ClassDeclaration { +pub(in crate::parser) struct ClassDeclaration { allow_yield: AllowYield, allow_await: AllowAwait, is_default: AllowDefault, @@ -48,7 +48,7 @@ pub(super) struct ClassDeclaration { impl ClassDeclaration { /// Creates a new `ClassDeclaration` parser. - pub(super) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self + pub(in crate::parser) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self where Y: Into, A: Into, @@ -66,7 +66,7 @@ impl TokenParser for ClassDeclaration where R: Read, { - type Output = Declaration; + type Output = Class; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { cursor.expect((Keyword::Class, false), "class declaration", interner)?; @@ -93,15 +93,13 @@ where }; cursor.set_strict_mode(strict); - Ok(Declaration::Class( - ClassTail::new( - name, - has_binding_identifier, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner)?, - )) + ClassTail::new( + name, + has_binding_identifier, + self.allow_yield, + self.allow_await, + ) + .parse(cursor, interner) } } diff --git a/boa_parser/src/parser/statement/declaration/hoistable/generator_decl/mod.rs b/boa_parser/src/parser/statement/declaration/hoistable/generator_decl/mod.rs index 0dcf3747c2..ae942e3c73 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/generator_decl/mod.rs +++ b/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* /// [spec]: https://tc39.es/ecma262/#prod-GeneratorDeclaration #[derive(Debug, Clone, Copy)] -pub(super) struct GeneratorDeclaration { +pub(in crate::parser) struct GeneratorDeclaration { allow_yield: AllowYield, allow_await: AllowAwait, is_default: AllowDefault, @@ -26,7 +26,7 @@ pub(super) struct GeneratorDeclaration { impl GeneratorDeclaration { /// Creates a new `GeneratorDeclaration` parser. - pub(super) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self + pub(in crate::parser) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self where Y: Into, A: Into, diff --git a/boa_parser/src/parser/statement/declaration/hoistable/mod.rs b/boa_parser/src/parser/statement/declaration/hoistable/mod.rs index 44e15b4170..b5d8bfe820 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/mod.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/mod.rs @@ -15,10 +15,6 @@ mod generator_decl; pub(crate) mod class_decl; -use self::{ - async_function_decl::AsyncFunctionDeclaration, async_generator_decl::AsyncGeneratorDeclaration, - class_decl::ClassDeclaration, generator_decl::GeneratorDeclaration, -}; use crate::{ lexer::TokenKind, parser::{ @@ -40,7 +36,11 @@ use boa_interner::{Interner, Sym}; use boa_profiler::Profiler; 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. /// @@ -49,7 +49,7 @@ pub(in crate::parser) use function_decl::FunctionDeclaration; /// /// [spec]: https://tc39.es/ecma262/#prod-FunctionDeclaration #[derive(Debug, Clone, Copy)] -pub(super) struct HoistableDeclaration { +pub(in crate::parser) struct HoistableDeclaration { allow_yield: AllowYield, allow_await: AllowAwait, is_default: AllowDefault, @@ -57,7 +57,7 @@ pub(super) struct HoistableDeclaration { impl HoistableDeclaration { /// Creates a new `HoistableDeclaration` parser. - pub(super) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self + pub(in crate::parser) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self where Y: Into, A: Into, @@ -225,6 +225,7 @@ fn parse_callable_declaration( &bound_names(¶ms), &top_level_lexically_declared_names(&body), params_start_position, + interner, )?; // It is a Syntax Error if FormalParameters Contains SuperProperty is true. diff --git a/boa_parser/src/parser/statement/declaration/import.rs b/boa_parser/src/parser/statement/declaration/import.rs new file mode 100644 index 0000000000..6824188d56 --- /dev/null +++ b/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 TokenParser for ImportDeclaration +where + R: Read, +{ + type Output = AstImportDeclaration; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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 TokenParser for ImportedBinding +where + R: Read, +{ + type Output = Identifier; + + #[inline] + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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 TokenParser for NamedImports +where + R: Read, +{ + type Output = Box<[AstImportSpecifier]>; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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), + ImportList(Option, 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 TokenParser for ImportSpecifier +where + R: Read, +{ + type Output = AstImportSpecifier; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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 TokenParser for NameSpaceImport +where + R: Read, +{ + type Output = Identifier; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + cursor.expect(Punctuator::Mul, "import declaration", interner)?; + cursor.expect( + TokenKind::identifier(Sym::AS), + "import declaration", + interner, + )?; + + ImportedBinding.parse(cursor, interner) + } +} diff --git a/boa_parser/src/parser/statement/declaration/lexical.rs b/boa_parser/src/parser/statement/declaration/lexical.rs index c7f820aa14..ff0e53ce8e 100644 --- a/boa_parser/src/parser/statement/declaration/lexical.rs +++ b/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_interner::{Interner, Sym}; use boa_profiler::Profiler; -use std::{convert::TryInto, io::Read}; +use std::io::Read; /// Parses a lexical declaration. /// diff --git a/boa_parser/src/parser/statement/declaration/mod.rs b/boa_parser/src/parser/statement/declaration/mod.rs index 9fd0a224b0..33d67c1a7d 100644 --- a/boa_parser/src/parser/statement/declaration/mod.rs +++ b/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 //! [spec]:https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement +mod export; mod hoistable; +mod import; mod lexical; #[cfg(test)] mod tests; -pub(in crate::parser) use hoistable::class_decl::ClassTail; -pub(in crate::parser) use hoistable::FunctionDeclaration; -use hoistable::HoistableDeclaration; -pub(in crate::parser) use lexical::LexicalDeclaration; - +pub(in crate::parser) use self::{ + export::ExportDeclaration, + hoistable::{ + class_decl::ClassTail, ClassDeclaration, FunctionDeclaration, HoistableDeclaration, + }, + import::ImportDeclaration, + lexical::LexicalDeclaration, +}; use crate::{ lexer::TokenKind, parser::{AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser}, + Error, }; use boa_ast::{self as ast, Keyword}; -use boa_interner::Interner; +use boa_interner::{Interner, Sym}; use boa_profiler::Profiler; use std::io::Read; @@ -39,6 +45,8 @@ pub(super) struct Declaration { } impl Declaration { + /// Creates a new declaration parser. + #[inline] pub(super) fn new(allow_yield: Y, allow_await: A) -> Self where Y: Into, @@ -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 TokenParser for FromClause +where + R: Read, +{ + type Output = ast::declaration::ModuleSpecifier; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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()) + } +} diff --git a/boa_parser/src/parser/statement/declaration/tests.rs b/boa_parser/src/parser/statement/declaration/tests.rs index 7034b6a330..b209c9ba57 100644 --- a/boa_parser/src/parser/statement/declaration/tests.rs +++ b/boa_parser/src/parser/statement/declaration/tests.rs @@ -1,12 +1,10 @@ -use std::convert::TryInto; - -use crate::parser::tests::{check_invalid, check_script_parser}; +use crate::parser::tests::{check_invalid_script, check_script_parser}; use boa_ast::{ declaration::{LexicalDeclaration, VarDeclaration, Variable}, expression::literal::Literal, Declaration, Statement, }; -use boa_interner::Interner; +use boa_interner::{Interner, Sym}; use boa_macros::utf16; /// Checks `var` declaration parsing. @@ -36,9 +34,7 @@ fn var_declaration_keywords() { "var yield = 5;", vec![Statement::Var(VarDeclaration( vec![Variable::from_identifier( - interner - .get_or_intern_static("yield", utf16!("yield")) - .into(), + Sym::YIELD.into(), Some(Literal::from(5).into()), )] .try_into() @@ -53,9 +49,7 @@ fn var_declaration_keywords() { "var await = 5;", vec![Statement::Var(VarDeclaration( vec![Variable::from_identifier( - interner - .get_or_intern_static("await", utf16!("await")) - .into(), + Sym::AWAIT.into(), Some(Literal::from(5).into()), )] .try_into() @@ -160,9 +154,7 @@ fn let_declaration_keywords() { "let yield = 5;", vec![Declaration::Lexical(LexicalDeclaration::Let( vec![Variable::from_identifier( - interner - .get_or_intern_static("yield", utf16!("yield")) - .into(), + Sym::YIELD.into(), Some(Literal::from(5).into()), )] .try_into() @@ -177,9 +169,7 @@ fn let_declaration_keywords() { "let await = 5;", vec![Declaration::Lexical(LexicalDeclaration::Let( vec![Variable::from_identifier( - interner - .get_or_intern_static("await", utf16!("await")) - .into(), + Sym::AWAIT.into(), Some(Literal::from(5).into()), )] .try_into() @@ -284,9 +274,7 @@ fn const_declaration_keywords() { "const yield = 5;", vec![Declaration::Lexical(LexicalDeclaration::Const( vec![Variable::from_identifier( - interner - .get_or_intern_static("yield", utf16!("yield")) - .into(), + Sym::YIELD.into(), Some(Literal::from(5).into()), )] .try_into() @@ -301,9 +289,7 @@ fn const_declaration_keywords() { "const await = 5;", vec![Declaration::Lexical(LexicalDeclaration::Const( vec![Variable::from_identifier( - interner - .get_or_intern_static("await", utf16!("await")) - .into(), + Sym::AWAIT.into(), Some(Literal::from(5).into()), )] .try_into() @@ -336,7 +322,7 @@ fn const_declaration_no_spaces() { /// Checks empty `const` declaration parsing. #[test] fn empty_const_declaration() { - check_invalid("const a;"); + check_invalid_script("const a;"); } /// Checks multiple `const` declarations. diff --git a/boa_parser/src/parser/statement/iteration/for_statement.rs b/boa_parser/src/parser/statement/iteration/for_statement.rs index e5adf74567..52bf715248 100644 --- a/boa_parser/src/parser/statement/iteration/for_statement.rs +++ b/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::ArrayLiteral(array) => array .to_pattern(strict) - .ok_or(Error::General { - message: "invalid array destructuring pattern in iterable loop initializer", - position, + .ok_or_else(|| { + Error::general( + "invalid array destructuring pattern in iterable loop initializer", + position, + ) }) .map(|arr| IterableLoopInitializer::Pattern(arr.into())), ast::Expression::ObjectLiteral(object) => object .to_pattern(strict) - .ok_or(Error::General { - message: "invalid object destructuring pattern in iterable loop initializer", - position, + .ok_or_else(|| { + Error::general( + "invalid object destructuring pattern in iterable loop initializer", + position, + ) }) .map(|obj| IterableLoopInitializer::Pattern(obj.into())), ast::Expression::PropertyAccess(access) => Ok(IterableLoopInitializer::Access(access)), diff --git a/boa_parser/src/parser/statement/iteration/tests.rs b/boa_parser/src/parser/statement/iteration/tests.rs index 6c3447af6f..314914a118 100644 --- a/boa_parser/src/parser/statement/iteration/tests.rs +++ b/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::{ declaration::{VarDeclaration, Variable}, expression::{ @@ -251,11 +251,11 @@ fn do_while_spaces() { /// Checks rejection of const bindings without init in for loops #[test] 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 #[test] 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]);"); } diff --git a/boa_parser/src/parser/statement/mod.rs b/boa_parser/src/parser/statement/mod.rs index 479e77c21e..3ed2b7ab19 100644 --- a/boa_parser/src/parser/statement/mod.rs +++ b/boa_parser/src/parser/statement/mod.rs @@ -25,7 +25,7 @@ use self::{ block::BlockStatement, break_stm::BreakStatement, continue_stm::ContinueStatement, - declaration::Declaration, + declaration::{Declaration, ExportDeclaration, ImportDeclaration}, expression::ExpressionStatement, if_stm::IfStatement, iteration::{DoWhileStatement, ForStatement, WhileStatement}, @@ -37,7 +37,10 @@ use self::{ variable::VariableStatement, }; use crate::{ - lexer::{token::EscapeSequence, Error as LexError, InputElement, Token, TokenKind}, + lexer::{ + token::{ContainsEscapeSequence, EscapeSequence}, + Error as LexError, InputElement, Token, TokenKind, + }, parser::{ expression::{BindingIdentifier, Initializer, PropertyName}, AllowAwait, AllowReturn, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser, @@ -49,7 +52,7 @@ use boa_ast::{ pattern::{ArrayPattern, ArrayPatternElement, ObjectPatternElement}, Keyword, Punctuator, }; -use boa_interner::Interner; +use boa_interner::{Interner, Sym}; use boa_macros::utf16; use boa_profiler::Profiler; 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 TokenParser for ModuleItemList +where + R: Read, +{ + type Output = boa_ast::ModuleItemList; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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 TokenParser for ModuleItem +where + R: Read, +{ + type Output = boa_ast::ModuleItem; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + 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), + } + } +} diff --git a/boa_parser/src/parser/statement/switch/tests.rs b/boa_parser/src/parser/statement/switch/tests.rs index d664756f32..c5c04ce6b6 100644 --- a/boa_parser/src/parser/statement/switch/tests.rs +++ b/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::{ declaration::{LexicalDeclaration, Variable}, expression::{access::SimplePropertyAccess, literal::Literal, Call, Identifier}, @@ -11,7 +11,7 @@ use boa_macros::utf16; /// Checks parsing malformed switch with no closeblock. #[test] fn check_switch_no_closeblock() { - check_invalid( + check_invalid_script( r#" let a = 10; switch (a) { @@ -26,7 +26,7 @@ fn check_switch_no_closeblock() { /// Checks parsing malformed switch in which a case is started but not finished. #[test] fn check_switch_case_unclosed() { - check_invalid( + check_invalid_script( r#" let a = 10; switch (a) { @@ -40,7 +40,7 @@ fn check_switch_case_unclosed() { /// Checks parsing malformed switch with 2 defaults. #[test] fn check_switch_two_default() { - check_invalid( + check_invalid_script( r#" let a = 10; switch (a) { @@ -58,7 +58,7 @@ fn check_switch_two_default() { /// Checks parsing malformed switch with no expression. #[test] fn check_switch_no_expr() { - check_invalid( + check_invalid_script( r#" let a = 10; switch { @@ -73,7 +73,7 @@ fn check_switch_no_expr() { /// Checks parsing malformed switch with an unknown label. #[test] fn check_switch_unknown_label() { - check_invalid( + check_invalid_script( r#" let a = 10; switch (a) { @@ -88,7 +88,7 @@ fn check_switch_unknown_label() { /// Checks parsing malformed switch with two defaults that are seperated by cases. #[test] fn check_switch_seperated_defaults() { - check_invalid( + check_invalid_script( r#" let a = 10; switch (a) { diff --git a/boa_parser/src/parser/statement/try_stm/tests.rs b/boa_parser/src/parser/statement/try_stm/tests.rs index 551e623df4..2320deff47 100644 --- a/boa_parser/src/parser/statement/try_stm/tests.rs +++ b/boa_parser/src/parser/statement/try_stm/tests.rs @@ -1,6 +1,4 @@ -use std::convert::TryInto; - -use crate::parser::tests::{check_invalid, check_script_parser}; +use crate::parser::tests::{check_invalid_script, check_script_parser}; use boa_ast::{ declaration::{VarDeclaration, Variable}, expression::{literal::Literal, Identifier}, @@ -265,35 +263,35 @@ fn check_catch_with_var_redeclaration() { #[test] fn check_inline_invalid_catch() { - check_invalid("try {} catch"); + check_invalid_script("try {} catch"); } #[test] fn check_inline_invalid_catch_without_closing_paren() { - check_invalid("try {} catch(e {}"); + check_invalid_script("try {} catch(e {}"); } #[test] fn check_inline_invalid_catch_parameter() { - check_invalid("try {} catch(1) {}"); + check_invalid_script("try {} catch(1) {}"); } #[test] fn check_invalid_try_no_catch_finally() { - check_invalid("try {} let a = 10;"); + check_invalid_script("try {} let a = 10;"); } #[test] fn check_invalid_catch_with_empty_paren() { - check_invalid("try {} catch() {}"); + check_invalid_script("try {} catch() {}"); } #[test] fn check_invalid_catch_with_duplicate_params() { - check_invalid("try {} catch({ a, b: a }) {}"); + check_invalid_script("try {} catch({ a, b: a }) {}"); } #[test] fn check_invalid_catch_with_lexical_redeclaration() { - check_invalid("try {} catch(e) { let e = 'oh' }"); + check_invalid_script("try {} catch(e) { let e = 'oh' }"); } diff --git a/boa_parser/src/parser/statement/variable/mod.rs b/boa_parser/src/parser/statement/variable/mod.rs index 1cce34859c..ce6a0f52f5 100644 --- a/boa_parser/src/parser/statement/variable/mod.rs +++ b/boa_parser/src/parser/statement/variable/mod.rs @@ -11,7 +11,7 @@ use crate::{ }; use boa_ast::{ declaration::{VarDeclaration, Variable}, - Keyword, Punctuator, Statement, + Keyword, Punctuator, }; use boa_interner::Interner; use boa_profiler::Profiler; @@ -51,7 +51,7 @@ impl TokenParser for VariableStatement where R: Read, { - type Output = Statement; + type Output = VarDeclaration; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("VariableStatement", "Parsing"); @@ -62,7 +62,7 @@ where cursor.expect_semicolon("variable statement", interner)?; - Ok(decl_list.into()) + Ok(decl_list) } } diff --git a/boa_parser/src/parser/tests/format/mod.rs b/boa_parser/src/parser/tests/format/mod.rs index 4bb87d5b2f..f55892f7b5 100644 --- a/boa_parser/src/parser/tests/format/mod.rs +++ b/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 interner = &mut Interner::default(); let result = Parser::new(source) - .parse_all(interner) + .parse_script(interner) .expect("parsing failed") .to_interned_string(interner); if scenario != result { diff --git a/boa_parser/src/parser/tests/mod.rs b/boa_parser/src/parser/tests/mod.rs index 18b6311632..77abbf90b9 100644 --- a/boa_parser/src/parser/tests/mod.rs +++ b/boa_parser/src/parser/tests/mod.rs @@ -37,7 +37,7 @@ where { assert_eq!( Parser::new(Source::from_bytes(js)) - .parse_all(interner) + .parse_script(interner) .expect("failed to parse"), StatementList::from(expr.into()) ); @@ -45,9 +45,9 @@ where /// Checks that the given javascript string creates a parse error. #[track_caller] -pub(super) fn check_invalid(js: &str) { +pub(super) fn check_invalid_script(js: &str) { assert!(Parser::new(Source::from_bytes(js)) - .parse_all(&mut Interner::default()) + .parse_script(&mut Interner::default()) .is_err()); } diff --git a/boa_tester/src/exec/js262.rs b/boa_tester/src/exec/js262.rs index 00659ab99b..781858008e 100644 --- a/boa_tester/src/exec/js262.rs +++ b/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 { args.get(0).and_then(JsValue::as_string).map_or_else( || 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 Err(e) => Err(JsNativeError::typ() @@ -98,7 +99,7 @@ fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> .into()), // Calling eval here parses the code a second time. // TODO: We can fix this after we have have defined the public api for the vm executer. - Ok(_) => context.eval(Source::from_bytes(&source_text.to_std_string_escaped())), + Ok(_) => context.eval_script(Source::from_bytes(&source_text.to_std_string_escaped())), }, ) } diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index bfe239a56d..69d8271529 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -2,10 +2,10 @@ mod js262; -use super::{ - Harness, Outcome, Phase, SuiteResult, Test, TestFlags, TestOutcomeResult, TestResult, TestSuite, +use crate::{ + read::ErrorType, Harness, Outcome, Phase, SuiteResult, Test, TestFlags, TestOutcomeResult, + TestResult, TestSuite, }; -use crate::read::ErrorType; use boa_engine::{ context::ContextBuilder, job::SimpleJobQueue, native_function::NativeFunction, object::FunctionObjectBuilder, property::Attribute, Context, JsArgs, JsNativeErrorKind, @@ -187,7 +187,11 @@ impl Test { context.strict(strict); // 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, Err(e) => return (false, format!("Uncaught {e}")), }; @@ -213,12 +217,22 @@ impl Test { let context = &mut Context::default(); context.strict(strict); - match context.parse(source) { - Ok(statement_list) => match context.compile(&statement_list) { - Ok(_) => (false, "StatementList compilation should fail".to_owned()), - Err(e) => (true, format!("Uncaught {e:?}")), - }, - Err(e) => (true, format!("Uncaught {e}")), + if self.is_module() { + 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()), + Err(e) => (true, format!("Uncaught {e:?}")), + }, + Err(e) => (true, format!("Uncaught {e}")), + } } } Outcome::Negative { @@ -230,17 +244,28 @@ impl Test { error_type, } => { let context = &mut Context::default(); + context.strict(strict); if let Err(e) = self.set_up_env(harness, context, AsyncResult::default()) { return (false, e); } - context.strict(strict); - let code = match context - .parse(source) - .map_err(Into::into) - .and_then(|stmts| context.compile(&stmts)) - { - Ok(code) => code, - Err(e) => return (false, format!("Uncaught {e}")), + let code = if self.is_module() { + match context + .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) + .and_then(|stmts| context.compile_script(&stmts)) + { + Ok(code) => code, + Err(e) => return (false, format!("Uncaught {e}")), + } }; 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)); context - .eval(assert) + .eval_script(assert) .map_err(|e| format!("could not run assert.js:\n{e}"))?; context - .eval(sta) + .eval_script(sta) .map_err(|e| format!("could not run sta.js:\n{e}"))?; if self.flags.contains(TestFlags::ASYNC) { @@ -367,7 +392,7 @@ impl Test { Some(&harness.doneprint_handle.path), ); context - .eval(dph) + .eval_script(dph) .map_err(|e| format!("could not run doneprintHandle.js:\n{e}"))?; } @@ -377,7 +402,7 @@ impl Test { .get(include_name) .ok_or_else(|| format!("could not find the {include_name} include file."))?; let source = Source::from_reader(include.content.as_bytes(), Some(&include.path)); - context.eval(source).map_err(|e| { + context.eval_script(source).map_err(|e| { format!("could not run the harness `{include_name}`:\nUncaught {e}",) })?; } @@ -422,6 +447,7 @@ struct AsyncResult { } impl Default for AsyncResult { + #[inline] fn default() -> Self { Self { inner: Rc::new(RefCell::new(Ok(()))), diff --git a/boa_tester/src/main.rs b/boa_tester/src/main.rs index e94c464080..a6ae47a540 100644 --- a/boa_tester/src/main.rs +++ b/boa_tester/src/main.rs @@ -407,9 +407,17 @@ impl Test { } } + /// Sets the test as ignored. + #[inline] fn set_ignored(&mut self) { 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. diff --git a/boa_wasm/src/lib.rs b/boa_wasm/src/lib.rs index 13853f5c26..777935f1a2 100644 --- a/boa_wasm/src/lib.rs +++ b/boa_wasm/src/lib.rs @@ -66,7 +66,7 @@ use wasm_bindgen::prelude::*; pub fn evaluate(src: &str) -> Result { // Setup executor Context::default() - .eval(Source::from_bytes(src)) + .eval_script(Source::from_bytes(src)) .map_err(|e| JsValue::from(format!("Uncaught {e}"))) .map(|v| v.display().to_string()) } diff --git a/fuzz/fuzz_targets/bytecompiler-implied.rs b/fuzz/fuzz_targets/bytecompiler-implied.rs index dd91bbc32a..5d4840600e 100644 --- a/fuzz/fuzz_targets/bytecompiler-implied.rs +++ b/fuzz/fuzz_targets/bytecompiler-implied.rs @@ -14,7 +14,7 @@ fn do_fuzz(original: FuzzSource) -> Corpus { .instructions_remaining(0) .build(); 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); Corpus::Keep } else { diff --git a/fuzz/fuzz_targets/parser-idempotency.rs b/fuzz/fuzz_targets/parser-idempotency.rs index cba2135b6a..ec3f49e725 100644 --- a/fuzz/fuzz_targets/parser-idempotency.rs +++ b/fuzz/fuzz_targets/parser-idempotency.rs @@ -21,7 +21,7 @@ fn do_fuzz(mut data: FuzzData) -> Result<(), Box> { let before = data.interner.len(); // 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. - 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 first_interned = first.to_interned_string(&data.interner); @@ -38,7 +38,7 @@ fn do_fuzz(mut data: FuzzData) -> Result<(), Box> { // Now, we most assuredly should produce valid code. It has already gone through a first pass. let second = parser - .parse_all(&mut data.interner) + .parse_script(&mut data.interner) .expect("Could not parse the first-pass interned copy."); let second_interned = second.to_interned_string(&data.interner); let after_second = data.interner.len(); diff --git a/test_ignore.toml b/test_ignore.toml index bf74802db7..724dcd2695 100644 --- a/test_ignore.toml +++ b/test_ignore.toml @@ -1,5 +1,5 @@ # Not implemented yet: -flags = ["module"] +flags = [] features = [ # Non-implemented features: