mirror of https://github.com/boa-dev/boa.git
Browse Source
I'm creating this draft PR, since I wanted to have some early feedback, and because I though I would have time to finish it last week, but I got caught up with other stuff. Feel free to contribute :) The main thing here is that I have divided `eval()`, `parse()` and similar functions so that they can decide if they are parsing scripts or modules. Let me know your thoughts. Then, I was checking the import & export parsing, and I noticed we are using `TokenKind::Identifier` for `IdentifierName`, so I changed that name. An `Identifier` is an `IdentifierName` that isn't a `ReservedWord`. This means we should probably also adapt all `IdentifierReference`, `BindingIdentifier` and so on parsing. I already created an `Identifier` parser. Something interesting there is that `await` is not a valid `Identifier` if the goal symbol is `Module`, as you can see in the [spec](https://tc39.es/ecma262/#prod-LabelIdentifier), but currently we don't have that information in the `InputElement` enumeration, we only have `Div`, `RegExp` and `TemplateTail`. How could we approach this? Co-authored-by: jedel1043 <jedel0124@gmail.com>pull/2588/head
Iban Eguia Moraza
2 years ago
84 changed files with 2350 additions and 340 deletions
@ -0,0 +1,215 @@ |
|||||||
|
//! Export declaration AST nodes.
|
||||||
|
//!
|
||||||
|
//! This module contains `export` declaration AST nodes.
|
||||||
|
//!
|
||||||
|
//! More information:
|
||||||
|
//! - [MDN documentation][mdn]
|
||||||
|
//! - [ECMAScript specification][spec]
|
||||||
|
//!
|
||||||
|
//! [spec]: https://tc39.es/ecma262/#sec-exports
|
||||||
|
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
|
||||||
|
|
||||||
|
use std::ops::ControlFlow; |
||||||
|
|
||||||
|
use super::{ModuleSpecifier, VarDeclaration}; |
||||||
|
use crate::{ |
||||||
|
function::{AsyncFunction, AsyncGenerator, Class, Function, Generator}, |
||||||
|
try_break, |
||||||
|
visitor::{VisitWith, Visitor, VisitorMut}, |
||||||
|
Declaration, Expression, |
||||||
|
}; |
||||||
|
use boa_interner::Sym; |
||||||
|
|
||||||
|
/// The kind of re-export in an [`ExportDeclaration`].
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub enum ReExportKind { |
||||||
|
/// Namespaced Re-export (`export * as name from "module-name"`).
|
||||||
|
Namespaced { |
||||||
|
/// Reexported name for the imported module.
|
||||||
|
name: Option<Sym>, |
||||||
|
}, |
||||||
|
/// Re-export list (`export { export1, export2 as alias2 } from "module-name"`).
|
||||||
|
Named { |
||||||
|
/// List of the required re-exports of the re-exported module.
|
||||||
|
names: Box<[ExportSpecifier]>, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitWith for ReExportKind { |
||||||
|
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: Visitor<'a>, |
||||||
|
{ |
||||||
|
match self { |
||||||
|
Self::Namespaced { name: Some(name) } => visitor.visit_sym(name), |
||||||
|
Self::Namespaced { name: None } => ControlFlow::Continue(()), |
||||||
|
Self::Named { names } => { |
||||||
|
for name in &**names { |
||||||
|
try_break!(visitor.visit_export_specifier(name)); |
||||||
|
} |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: VisitorMut<'a>, |
||||||
|
{ |
||||||
|
match self { |
||||||
|
Self::Namespaced { name: Some(name) } => visitor.visit_sym_mut(name), |
||||||
|
Self::Namespaced { name: None } => ControlFlow::Continue(()), |
||||||
|
Self::Named { names } => { |
||||||
|
for name in &mut **names { |
||||||
|
try_break!(visitor.visit_export_specifier_mut(name)); |
||||||
|
} |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// An export declaration AST node.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ExportDeclaration
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub enum ExportDeclaration { |
||||||
|
/// Re-export.
|
||||||
|
ReExport { |
||||||
|
/// The kind of reexport declared.
|
||||||
|
kind: ReExportKind, |
||||||
|
/// Reexported module specifier.
|
||||||
|
specifier: ModuleSpecifier, |
||||||
|
}, |
||||||
|
/// List of exports.
|
||||||
|
List(Box<[ExportSpecifier]>), |
||||||
|
/// Variable statement export.
|
||||||
|
VarStatement(VarDeclaration), |
||||||
|
/// Declaration export.
|
||||||
|
Declaration(Declaration), |
||||||
|
/// Default function export.
|
||||||
|
DefaultFunction(Function), |
||||||
|
/// Default generator export.
|
||||||
|
DefaultGenerator(Generator), |
||||||
|
/// Default async function export.
|
||||||
|
DefaultAsyncFunction(AsyncFunction), |
||||||
|
/// Default async generator export.
|
||||||
|
DefaultAsyncGenerator(AsyncGenerator), |
||||||
|
/// Default class declaration export.
|
||||||
|
DefaultClassDeclaration(Class), |
||||||
|
/// Default assignment expression export.
|
||||||
|
DefaultAssignmentExpression(Expression), |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitWith for ExportDeclaration { |
||||||
|
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: Visitor<'a>, |
||||||
|
{ |
||||||
|
match self { |
||||||
|
Self::ReExport { specifier, kind } => { |
||||||
|
try_break!(visitor.visit_module_specifier(specifier)); |
||||||
|
visitor.visit_re_export_kind(kind) |
||||||
|
} |
||||||
|
Self::List(list) => { |
||||||
|
for item in &**list { |
||||||
|
try_break!(visitor.visit_export_specifier(item)); |
||||||
|
} |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
Self::VarStatement(var) => visitor.visit_var_declaration(var), |
||||||
|
Self::Declaration(decl) => visitor.visit_declaration(decl), |
||||||
|
Self::DefaultFunction(f) => visitor.visit_function(f), |
||||||
|
Self::DefaultGenerator(g) => visitor.visit_generator(g), |
||||||
|
Self::DefaultAsyncFunction(af) => visitor.visit_async_function(af), |
||||||
|
Self::DefaultAsyncGenerator(ag) => visitor.visit_async_generator(ag), |
||||||
|
Self::DefaultClassDeclaration(c) => visitor.visit_class(c), |
||||||
|
Self::DefaultAssignmentExpression(expr) => visitor.visit_expression(expr), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: VisitorMut<'a>, |
||||||
|
{ |
||||||
|
match self { |
||||||
|
Self::ReExport { specifier, kind } => { |
||||||
|
try_break!(visitor.visit_module_specifier_mut(specifier)); |
||||||
|
visitor.visit_re_export_kind_mut(kind) |
||||||
|
} |
||||||
|
Self::List(list) => { |
||||||
|
for item in &mut **list { |
||||||
|
try_break!(visitor.visit_export_specifier_mut(item)); |
||||||
|
} |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
Self::VarStatement(var) => visitor.visit_var_declaration_mut(var), |
||||||
|
Self::Declaration(decl) => visitor.visit_declaration_mut(decl), |
||||||
|
Self::DefaultFunction(f) => visitor.visit_function_mut(f), |
||||||
|
Self::DefaultGenerator(g) => visitor.visit_generator_mut(g), |
||||||
|
Self::DefaultAsyncFunction(af) => visitor.visit_async_function_mut(af), |
||||||
|
Self::DefaultAsyncGenerator(ag) => visitor.visit_async_generator_mut(ag), |
||||||
|
Self::DefaultClassDeclaration(c) => visitor.visit_class_mut(c), |
||||||
|
Self::DefaultAssignmentExpression(expr) => visitor.visit_expression_mut(expr), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Export specifier
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ExportSpecifier
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
pub struct ExportSpecifier { |
||||||
|
alias: Sym, |
||||||
|
private_name: Sym, |
||||||
|
} |
||||||
|
|
||||||
|
impl ExportSpecifier { |
||||||
|
/// Creates a new [`ExportSpecifier`].
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn new(alias: Sym, private_name: Sym) -> Self { |
||||||
|
Self { |
||||||
|
alias, |
||||||
|
private_name, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the original alias.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn alias(self) -> Sym { |
||||||
|
self.alias |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the private name of the export inside the module.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn private_name(self) -> Sym { |
||||||
|
self.private_name |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitWith for ExportSpecifier { |
||||||
|
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: Visitor<'a>, |
||||||
|
{ |
||||||
|
try_break!(visitor.visit_sym(&self.alias)); |
||||||
|
visitor.visit_sym(&self.private_name) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: VisitorMut<'a>, |
||||||
|
{ |
||||||
|
try_break!(visitor.visit_sym_mut(&mut self.alias)); |
||||||
|
visitor.visit_sym_mut(&mut self.private_name) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,206 @@ |
|||||||
|
//! Import declaration AST nodes.
|
||||||
|
//!
|
||||||
|
//! This module contains `import` declaration AST nodes.
|
||||||
|
//!
|
||||||
|
//! More information:
|
||||||
|
//! - [MDN documentation][mdn]
|
||||||
|
//! - [ECMAScript specification][spec]
|
||||||
|
//!
|
||||||
|
//! [spec]: https://tc39.es/ecma262/#sec-imports
|
||||||
|
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
|
||||||
|
|
||||||
|
use std::ops::ControlFlow; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
expression::Identifier, |
||||||
|
try_break, |
||||||
|
visitor::{VisitWith, Visitor, VisitorMut}, |
||||||
|
}; |
||||||
|
use boa_interner::Sym; |
||||||
|
|
||||||
|
use super::ModuleSpecifier; |
||||||
|
|
||||||
|
/// The kind of import in an [`ImportDeclaration`].
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub enum ImportKind { |
||||||
|
/// Default (`import defaultName from "module-name"`) or unnamed (`import "module-name").
|
||||||
|
DefaultOrUnnamed, |
||||||
|
/// Namespaced import (`import * as name from "module-name"`).
|
||||||
|
Namespaced { |
||||||
|
/// Binding for the namespace created from the exports of the imported module.
|
||||||
|
binding: Identifier, |
||||||
|
}, |
||||||
|
/// Import list (`import { export1, export2 as alias2 } from "module-name"`).
|
||||||
|
Named { |
||||||
|
/// List of the required exports of the imported module.
|
||||||
|
names: Box<[ImportSpecifier]>, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitWith for ImportKind { |
||||||
|
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: Visitor<'a>, |
||||||
|
{ |
||||||
|
match self { |
||||||
|
Self::DefaultOrUnnamed => ControlFlow::Continue(()), |
||||||
|
Self::Namespaced { binding } => visitor.visit_identifier(binding), |
||||||
|
Self::Named { names } => { |
||||||
|
for name in &**names { |
||||||
|
try_break!(visitor.visit_import_specifier(name)); |
||||||
|
} |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: VisitorMut<'a>, |
||||||
|
{ |
||||||
|
match self { |
||||||
|
Self::DefaultOrUnnamed => ControlFlow::Continue(()), |
||||||
|
Self::Namespaced { binding } => visitor.visit_identifier_mut(binding), |
||||||
|
Self::Named { names } => { |
||||||
|
for name in &mut **names { |
||||||
|
try_break!(visitor.visit_import_specifier_mut(name)); |
||||||
|
} |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// An import declaration AST node.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ImportDeclaration
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub struct ImportDeclaration { |
||||||
|
/// Binding for the default export of `specifier`.
|
||||||
|
default: Option<Identifier>, |
||||||
|
/// See [`ImportKind`].
|
||||||
|
kind: ImportKind, |
||||||
|
/// Module specifier.
|
||||||
|
specifier: ModuleSpecifier, |
||||||
|
} |
||||||
|
|
||||||
|
impl ImportDeclaration { |
||||||
|
/// Creates a new import declaration.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn new( |
||||||
|
default: Option<Identifier>, |
||||||
|
kind: ImportKind, |
||||||
|
specifier: ModuleSpecifier, |
||||||
|
) -> Self { |
||||||
|
Self { |
||||||
|
default, |
||||||
|
kind, |
||||||
|
specifier, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the binding for the default export of the module.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn default(&self) -> Option<Identifier> { |
||||||
|
self.default |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the module specifier of the import declaration.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn specifier(&self) -> ModuleSpecifier { |
||||||
|
self.specifier |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the import kind of the import declaration
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn kind(&self) -> &ImportKind { |
||||||
|
&self.kind |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitWith for ImportDeclaration { |
||||||
|
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: Visitor<'a>, |
||||||
|
{ |
||||||
|
if let Some(default) = &self.default { |
||||||
|
try_break!(visitor.visit_identifier(default)); |
||||||
|
} |
||||||
|
try_break!(visitor.visit_import_kind(&self.kind)); |
||||||
|
visitor.visit_module_specifier(&self.specifier) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: VisitorMut<'a>, |
||||||
|
{ |
||||||
|
if let Some(default) = &mut self.default { |
||||||
|
try_break!(visitor.visit_identifier_mut(default)); |
||||||
|
} |
||||||
|
try_break!(visitor.visit_import_kind_mut(&mut self.kind)); |
||||||
|
visitor.visit_module_specifier_mut(&mut self.specifier) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Import specifier
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ImportSpecifier
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
pub struct ImportSpecifier { |
||||||
|
binding: Identifier, |
||||||
|
export_name: Sym, |
||||||
|
} |
||||||
|
|
||||||
|
impl ImportSpecifier { |
||||||
|
/// Creates a new [`ImportSpecifier`].
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn new(binding: Identifier, export_name: Sym) -> Self { |
||||||
|
Self { |
||||||
|
binding, |
||||||
|
export_name, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the binding of the import specifier.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn binding(self) -> Identifier { |
||||||
|
self.binding |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the optional export name of the import.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn export_name(self) -> Sym { |
||||||
|
self.export_name |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitWith for ImportSpecifier { |
||||||
|
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: Visitor<'a>, |
||||||
|
{ |
||||||
|
try_break!(visitor.visit_identifier(&self.binding)); |
||||||
|
visitor.visit_sym(&self.export_name) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: VisitorMut<'a>, |
||||||
|
{ |
||||||
|
try_break!(visitor.visit_identifier_mut(&mut self.binding)); |
||||||
|
visitor.visit_sym_mut(&mut self.export_name) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,275 @@ |
|||||||
|
//! Module item list AST nodes.
|
||||||
|
//!
|
||||||
|
//! More information:
|
||||||
|
//! - [ECMAScript specification][spec]
|
||||||
|
//!
|
||||||
|
//! [spec]: https://tc39.es/ecma262/#sec-modules
|
||||||
|
|
||||||
|
use std::{convert::Infallible, ops::ControlFlow}; |
||||||
|
|
||||||
|
use boa_interner::Sym; |
||||||
|
use rustc_hash::FxHashSet; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
declaration::{ExportDeclaration, ExportSpecifier, ImportDeclaration, ReExportKind}, |
||||||
|
expression::Identifier, |
||||||
|
operations::BoundNamesVisitor, |
||||||
|
try_break, |
||||||
|
visitor::{VisitWith, Visitor, VisitorMut}, |
||||||
|
StatementListItem, |
||||||
|
}; |
||||||
|
|
||||||
|
/// Module item list AST node.
|
||||||
|
///
|
||||||
|
/// It contains a list of
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ModuleItemList
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub struct ModuleItemList { |
||||||
|
items: Box<[ModuleItem]>, |
||||||
|
} |
||||||
|
|
||||||
|
impl ModuleItemList { |
||||||
|
/// Gets the list of module items.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn items(&self) -> &[ModuleItem] { |
||||||
|
&self.items |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation [`ExportedNames`][spec].
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-exportednames
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub fn exported_names(&self) -> Vec<Sym> { |
||||||
|
#[derive(Debug)] |
||||||
|
struct ExportedItemsVisitor<'vec>(&'vec mut Vec<Sym>); |
||||||
|
|
||||||
|
impl<'ast> Visitor<'ast> for ExportedItemsVisitor<'_> { |
||||||
|
type BreakTy = Infallible; |
||||||
|
|
||||||
|
fn visit_import_declaration( |
||||||
|
&mut self, |
||||||
|
_: &'ast ImportDeclaration, |
||||||
|
) -> ControlFlow<Self::BreakTy> { |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
fn visit_statement_list_item( |
||||||
|
&mut self, |
||||||
|
_: &'ast StatementListItem, |
||||||
|
) -> ControlFlow<Self::BreakTy> { |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
fn visit_export_specifier( |
||||||
|
&mut self, |
||||||
|
node: &'ast ExportSpecifier, |
||||||
|
) -> ControlFlow<Self::BreakTy> { |
||||||
|
self.0.push(node.alias()); |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
fn visit_export_declaration( |
||||||
|
&mut self, |
||||||
|
node: &'ast ExportDeclaration, |
||||||
|
) -> ControlFlow<Self::BreakTy> { |
||||||
|
match node { |
||||||
|
ExportDeclaration::ReExport { kind, .. } => { |
||||||
|
match kind { |
||||||
|
ReExportKind::Namespaced { name: Some(name) } => self.0.push(*name), |
||||||
|
ReExportKind::Namespaced { name: None } => {} |
||||||
|
ReExportKind::Named { names } => { |
||||||
|
for specifier in &**names { |
||||||
|
try_break!(self.visit_export_specifier(specifier)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
ExportDeclaration::List(list) => { |
||||||
|
for specifier in &**list { |
||||||
|
try_break!(self.visit_export_specifier(specifier)); |
||||||
|
} |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
ExportDeclaration::VarStatement(var) => { |
||||||
|
BoundNamesVisitor(self.0).visit_var_declaration(var) |
||||||
|
} |
||||||
|
ExportDeclaration::Declaration(decl) => { |
||||||
|
BoundNamesVisitor(self.0).visit_declaration(decl) |
||||||
|
} |
||||||
|
ExportDeclaration::DefaultFunction(_) |
||||||
|
| ExportDeclaration::DefaultGenerator(_) |
||||||
|
| ExportDeclaration::DefaultAsyncFunction(_) |
||||||
|
| ExportDeclaration::DefaultAsyncGenerator(_) |
||||||
|
| ExportDeclaration::DefaultClassDeclaration(_) |
||||||
|
| ExportDeclaration::DefaultAssignmentExpression(_) => { |
||||||
|
self.0.push(Sym::DEFAULT); |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let mut names = Vec::new(); |
||||||
|
|
||||||
|
ExportedItemsVisitor(&mut names).visit_module_item_list(self); |
||||||
|
|
||||||
|
names |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation [`ExportedBindings`][spec].
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-exportedbindings
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub fn exported_bindings(&self) -> FxHashSet<Identifier> { |
||||||
|
#[derive(Debug)] |
||||||
|
struct ExportedBindingsVisitor<'vec>(&'vec mut FxHashSet<Identifier>); |
||||||
|
|
||||||
|
impl<'ast> Visitor<'ast> for ExportedBindingsVisitor<'_> { |
||||||
|
type BreakTy = Infallible; |
||||||
|
|
||||||
|
fn visit_import_declaration( |
||||||
|
&mut self, |
||||||
|
_: &'ast ImportDeclaration, |
||||||
|
) -> ControlFlow<Self::BreakTy> { |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
fn visit_statement_list_item( |
||||||
|
&mut self, |
||||||
|
_: &'ast StatementListItem, |
||||||
|
) -> ControlFlow<Self::BreakTy> { |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
fn visit_export_specifier( |
||||||
|
&mut self, |
||||||
|
node: &'ast ExportSpecifier, |
||||||
|
) -> ControlFlow<Self::BreakTy> { |
||||||
|
self.0.insert(Identifier::new(node.private_name())); |
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
fn visit_export_declaration( |
||||||
|
&mut self, |
||||||
|
node: &'ast ExportDeclaration, |
||||||
|
) -> ControlFlow<Self::BreakTy> { |
||||||
|
let name = match node { |
||||||
|
ExportDeclaration::ReExport { .. } => return ControlFlow::Continue(()), |
||||||
|
ExportDeclaration::List(list) => { |
||||||
|
for specifier in &**list { |
||||||
|
try_break!(self.visit_export_specifier(specifier)); |
||||||
|
} |
||||||
|
return ControlFlow::Continue(()); |
||||||
|
} |
||||||
|
ExportDeclaration::DefaultAssignmentExpression(expr) => { |
||||||
|
return BoundNamesVisitor(self.0).visit_expression(expr); |
||||||
|
} |
||||||
|
ExportDeclaration::VarStatement(var) => { |
||||||
|
return BoundNamesVisitor(self.0).visit_var_declaration(var); |
||||||
|
} |
||||||
|
ExportDeclaration::Declaration(decl) => { |
||||||
|
return BoundNamesVisitor(self.0).visit_declaration(decl); |
||||||
|
} |
||||||
|
ExportDeclaration::DefaultFunction(f) => f.name(), |
||||||
|
ExportDeclaration::DefaultGenerator(g) => g.name(), |
||||||
|
ExportDeclaration::DefaultAsyncFunction(af) => af.name(), |
||||||
|
ExportDeclaration::DefaultAsyncGenerator(ag) => ag.name(), |
||||||
|
ExportDeclaration::DefaultClassDeclaration(cl) => cl.name(), |
||||||
|
}; |
||||||
|
|
||||||
|
self.0 |
||||||
|
.insert(name.unwrap_or_else(|| Identifier::new(Sym::DEFAULT_EXPORT))); |
||||||
|
|
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let mut names = FxHashSet::default(); |
||||||
|
|
||||||
|
ExportedBindingsVisitor(&mut names).visit_module_item_list(self); |
||||||
|
|
||||||
|
names |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<T> From<T> for ModuleItemList |
||||||
|
where |
||||||
|
T: Into<Box<[ModuleItem]>>, |
||||||
|
{ |
||||||
|
#[inline] |
||||||
|
fn from(items: T) -> Self { |
||||||
|
Self { |
||||||
|
items: items.into(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitWith for ModuleItemList { |
||||||
|
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: Visitor<'a>, |
||||||
|
{ |
||||||
|
for item in &*self.items { |
||||||
|
try_break!(visitor.visit_module_item(item)); |
||||||
|
} |
||||||
|
|
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: VisitorMut<'a>, |
||||||
|
{ |
||||||
|
for item in &mut *self.items { |
||||||
|
try_break!(visitor.visit_module_item_mut(item)); |
||||||
|
} |
||||||
|
|
||||||
|
ControlFlow::Continue(()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Module item AST node.
|
||||||
|
///
|
||||||
|
/// This is an extension over a [`StatementList`](crate::StatementList), which can also include
|
||||||
|
/// multiple [`ImportDeclaration`] and [`ExportDeclaration`] nodes, along with
|
||||||
|
/// [`StatementListItem`] nodes.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ModuleItem
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub enum ModuleItem { |
||||||
|
/// See [`ImportDeclaration`].
|
||||||
|
ImportDeclaration(ImportDeclaration), |
||||||
|
/// See [`ExportDeclaration`].
|
||||||
|
ExportDeclaration(ExportDeclaration), |
||||||
|
/// See [`StatementListItem`].
|
||||||
|
StatementListItem(StatementListItem), |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitWith for ModuleItem { |
||||||
|
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: Visitor<'a>, |
||||||
|
{ |
||||||
|
match self { |
||||||
|
Self::ImportDeclaration(i) => visitor.visit_import_declaration(i), |
||||||
|
Self::ExportDeclaration(e) => visitor.visit_export_declaration(e), |
||||||
|
Self::StatementListItem(s) => visitor.visit_statement_list_item(s), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: VisitorMut<'a>, |
||||||
|
{ |
||||||
|
match self { |
||||||
|
Self::ImportDeclaration(i) => visitor.visit_import_declaration_mut(i), |
||||||
|
Self::ExportDeclaration(e) => visitor.visit_export_declaration_mut(e), |
||||||
|
Self::StatementListItem(s) => visitor.visit_statement_list_item_mut(s), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,316 @@ |
|||||||
|
//! Export declaration parsing
|
||||||
|
//!
|
||||||
|
//! This parses `export` declarations.
|
||||||
|
//!
|
||||||
|
//! More information:
|
||||||
|
//! - [MDN documentation][mdn]
|
||||||
|
//! - [ECMAScript specification][spec]
|
||||||
|
//!
|
||||||
|
//! [spec]: https://tc39.es/ecma262/#sec-exports
|
||||||
|
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
|
||||||
|
|
||||||
|
use crate::{ |
||||||
|
lexer::{token::ContainsEscapeSequence, TokenKind}, |
||||||
|
parser::{ |
||||||
|
cursor::Cursor, |
||||||
|
expression::AssignmentExpression, |
||||||
|
statement::{declaration::ClassDeclaration, variable::VariableStatement}, |
||||||
|
Error, OrAbrupt, ParseResult, TokenParser, |
||||||
|
}, |
||||||
|
}; |
||||||
|
use boa_ast::{ |
||||||
|
declaration::{ExportDeclaration as AstExportDeclaration, ReExportKind}, |
||||||
|
Keyword, Punctuator, |
||||||
|
}; |
||||||
|
use boa_interner::{Interner, Sym}; |
||||||
|
use boa_profiler::Profiler; |
||||||
|
use std::io::Read; |
||||||
|
|
||||||
|
use super::{ |
||||||
|
hoistable::{AsyncFunctionDeclaration, AsyncGeneratorDeclaration, GeneratorDeclaration}, |
||||||
|
Declaration, FromClause, FunctionDeclaration, |
||||||
|
}; |
||||||
|
|
||||||
|
/// Parses an export declaration.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ExportDeclaration
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
pub(in crate::parser) struct ExportDeclaration; |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for ExportDeclaration |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = AstExportDeclaration; |
||||||
|
|
||||||
|
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { |
||||||
|
let _timer = Profiler::global().start_event("ExportDeclaration", "Parsing"); |
||||||
|
|
||||||
|
cursor.expect((Keyword::Export, false), "export declaration", interner)?; |
||||||
|
|
||||||
|
let tok = cursor.peek(0, interner).or_abrupt()?; |
||||||
|
|
||||||
|
let export_clause: Self::Output = match tok.kind() { |
||||||
|
TokenKind::Punctuator(Punctuator::Mul) => { |
||||||
|
cursor.advance(interner); |
||||||
|
|
||||||
|
let next = cursor.peek(0, interner).or_abrupt()?; |
||||||
|
|
||||||
|
match next.kind() { |
||||||
|
TokenKind::IdentifierName((Sym::AS, _)) => { |
||||||
|
cursor.advance(interner); |
||||||
|
let tok = cursor.next(interner).or_abrupt()?; |
||||||
|
|
||||||
|
let alias = match tok.kind() { |
||||||
|
TokenKind::StringLiteral((export_name, _)) |
||||||
|
| TokenKind::IdentifierName((export_name, _)) => *export_name, |
||||||
|
_ => { |
||||||
|
return Err(Error::expected( |
||||||
|
["identifier".to_owned(), "string literal".to_owned()], |
||||||
|
tok.to_string(interner), |
||||||
|
tok.span(), |
||||||
|
"export declaration", |
||||||
|
)) |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let specifier = |
||||||
|
FromClause::new("export declaration").parse(cursor, interner)?; |
||||||
|
|
||||||
|
AstExportDeclaration::ReExport { |
||||||
|
kind: ReExportKind::Namespaced { name: Some(alias) }, |
||||||
|
specifier, |
||||||
|
} |
||||||
|
} |
||||||
|
TokenKind::IdentifierName((Sym::FROM, _)) => { |
||||||
|
let specifier = |
||||||
|
FromClause::new("export declaration").parse(cursor, interner)?; |
||||||
|
|
||||||
|
AstExportDeclaration::ReExport { |
||||||
|
kind: ReExportKind::Namespaced { name: None }, |
||||||
|
specifier, |
||||||
|
} |
||||||
|
} |
||||||
|
_ => { |
||||||
|
return Err(Error::expected( |
||||||
|
["as".to_owned(), "from".to_owned()], |
||||||
|
next.to_string(interner), |
||||||
|
next.span(), |
||||||
|
"export declaration", |
||||||
|
)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
TokenKind::Punctuator(Punctuator::OpenBlock) => { |
||||||
|
let names = NamedExports.parse(cursor, interner)?; |
||||||
|
|
||||||
|
let next = cursor.peek(0, interner).or_abrupt()?; |
||||||
|
|
||||||
|
if matches!( |
||||||
|
next.kind(), |
||||||
|
TokenKind::IdentifierName((Sym::FROM, ContainsEscapeSequence(false))) |
||||||
|
) { |
||||||
|
let specifier = |
||||||
|
FromClause::new("export declaration").parse(cursor, interner)?; |
||||||
|
AstExportDeclaration::ReExport { |
||||||
|
kind: ReExportKind::Named { names }, |
||||||
|
specifier, |
||||||
|
} |
||||||
|
} else { |
||||||
|
AstExportDeclaration::List(names) |
||||||
|
} |
||||||
|
} |
||||||
|
TokenKind::Keyword((Keyword::Var, false)) => VariableStatement::new(false, true) |
||||||
|
.parse(cursor, interner) |
||||||
|
.map(AstExportDeclaration::VarStatement)?, |
||||||
|
TokenKind::Keyword((Keyword::Default, _)) => { |
||||||
|
cursor.advance(interner); |
||||||
|
|
||||||
|
let tok = cursor.peek(0, interner).or_abrupt()?; |
||||||
|
|
||||||
|
match tok.kind() { |
||||||
|
TokenKind::Keyword((Keyword::Function, false)) => { |
||||||
|
let next_token = cursor.peek(1, interner).or_abrupt()?; |
||||||
|
if next_token.kind() == &TokenKind::Punctuator(Punctuator::Mul) { |
||||||
|
AstExportDeclaration::DefaultGenerator( |
||||||
|
GeneratorDeclaration::new(false, true, true) |
||||||
|
.parse(cursor, interner)?, |
||||||
|
) |
||||||
|
} else { |
||||||
|
AstExportDeclaration::DefaultFunction( |
||||||
|
FunctionDeclaration::new(false, true, true) |
||||||
|
.parse(cursor, interner)?, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
TokenKind::Keyword((Keyword::Async, false)) => { |
||||||
|
let next_token = cursor.peek(2, interner).or_abrupt()?; |
||||||
|
if next_token.kind() == &TokenKind::Punctuator(Punctuator::Mul) { |
||||||
|
AstExportDeclaration::DefaultAsyncGenerator( |
||||||
|
AsyncGeneratorDeclaration::new(false, true, true) |
||||||
|
.parse(cursor, interner)?, |
||||||
|
) |
||||||
|
} else { |
||||||
|
AstExportDeclaration::DefaultAsyncFunction( |
||||||
|
AsyncFunctionDeclaration::new(false, true, true) |
||||||
|
.parse(cursor, interner)?, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
TokenKind::Keyword((Keyword::Class, false)) => { |
||||||
|
AstExportDeclaration::DefaultClassDeclaration( |
||||||
|
ClassDeclaration::new(false, true, true).parse(cursor, interner)?, |
||||||
|
) |
||||||
|
} |
||||||
|
_ => AstExportDeclaration::DefaultAssignmentExpression( |
||||||
|
AssignmentExpression::new(None, true, false, true) |
||||||
|
.parse(cursor, interner)?, |
||||||
|
), |
||||||
|
} |
||||||
|
} |
||||||
|
_ => AstExportDeclaration::Declaration( |
||||||
|
Declaration::new(false, true).parse(cursor, interner)?, |
||||||
|
), |
||||||
|
}; |
||||||
|
|
||||||
|
Ok(export_clause) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Parses a named export list.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-NamedExports
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
struct NamedExports; |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for NamedExports |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = Box<[boa_ast::declaration::ExportSpecifier]>; |
||||||
|
|
||||||
|
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { |
||||||
|
cursor.expect(Punctuator::OpenBlock, "export declaration", interner)?; |
||||||
|
|
||||||
|
let mut list = Vec::new(); |
||||||
|
|
||||||
|
loop { |
||||||
|
let tok = cursor.next(interner).or_abrupt()?; |
||||||
|
match tok.kind() { |
||||||
|
TokenKind::Punctuator(Punctuator::CloseBlock) => { |
||||||
|
break; |
||||||
|
} |
||||||
|
TokenKind::Punctuator(Punctuator::Comma) => { |
||||||
|
if list.is_empty() { |
||||||
|
return Err(Error::expected( |
||||||
|
[ |
||||||
|
Punctuator::CloseBlock.to_string(), |
||||||
|
"string literal".to_owned(), |
||||||
|
"identifier".to_owned(), |
||||||
|
], |
||||||
|
tok.to_string(interner), |
||||||
|
tok.span(), |
||||||
|
"export declaration", |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
TokenKind::StringLiteral(_) | TokenKind::IdentifierName(_) => { |
||||||
|
list.push(ExportSpecifier.parse(cursor, interner)?); |
||||||
|
} |
||||||
|
_ => { |
||||||
|
return Err(Error::expected( |
||||||
|
[ |
||||||
|
Punctuator::CloseBlock.to_string(), |
||||||
|
Punctuator::Comma.to_string(), |
||||||
|
], |
||||||
|
tok.to_string(interner), |
||||||
|
tok.span(), |
||||||
|
"export declaration", |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Ok(list.into_boxed_slice()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Parses a module export name.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ModuleExportName
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
pub(super) struct ModuleExportName; |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for ModuleExportName |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = Sym; |
||||||
|
|
||||||
|
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { |
||||||
|
let tok = cursor.next(interner).or_abrupt()?; |
||||||
|
|
||||||
|
match tok.kind() { |
||||||
|
TokenKind::StringLiteral((ident, _)) => { |
||||||
|
if interner.resolve_expect(*ident).utf8().is_none() { |
||||||
|
return Err(Error::general( |
||||||
|
"import specifiers don't allow unpaired surrogates", |
||||||
|
tok.span().end(), |
||||||
|
)); |
||||||
|
} |
||||||
|
Ok(*ident) |
||||||
|
} |
||||||
|
TokenKind::IdentifierName((ident, _)) => Ok(*ident), |
||||||
|
_ => Err(Error::expected( |
||||||
|
["identifier".to_owned(), "string literal".to_owned()], |
||||||
|
tok.to_string(interner), |
||||||
|
tok.span(), |
||||||
|
"export specifier parsing", |
||||||
|
)), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Parses an export specifier.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ExportSpecifier
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
struct ExportSpecifier; |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for ExportSpecifier |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = boa_ast::declaration::ExportSpecifier; |
||||||
|
|
||||||
|
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { |
||||||
|
let inner_name = ModuleExportName.parse(cursor, interner)?; |
||||||
|
|
||||||
|
if cursor |
||||||
|
.next_if(TokenKind::identifier(Sym::AS), interner)? |
||||||
|
.is_some() |
||||||
|
{ |
||||||
|
let export_name = ModuleExportName.parse(cursor, interner)?; |
||||||
|
Ok(boa_ast::declaration::ExportSpecifier::new( |
||||||
|
export_name, |
||||||
|
inner_name, |
||||||
|
)) |
||||||
|
} else { |
||||||
|
Ok(boa_ast::declaration::ExportSpecifier::new( |
||||||
|
inner_name, inner_name, |
||||||
|
)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,327 @@ |
|||||||
|
//! Import declaration parsing
|
||||||
|
//!
|
||||||
|
//! This parses `import` declarations.
|
||||||
|
//!
|
||||||
|
//! More information:
|
||||||
|
//! - [MDN documentation][mdn]
|
||||||
|
//! - [ECMAScript specification][spec]
|
||||||
|
//!
|
||||||
|
//! [spec]: https://tc39.es/ecma262/#sec-imports
|
||||||
|
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
|
||||||
|
|
||||||
|
use crate::{ |
||||||
|
lexer::TokenKind, |
||||||
|
parser::{ |
||||||
|
cursor::Cursor, |
||||||
|
statement::{declaration::FromClause, BindingIdentifier}, |
||||||
|
Error, OrAbrupt, ParseResult, TokenParser, |
||||||
|
}, |
||||||
|
}; |
||||||
|
use boa_ast::{ |
||||||
|
declaration::{ |
||||||
|
ImportDeclaration as AstImportDeclaration, ImportKind, |
||||||
|
ImportSpecifier as AstImportSpecifier, ModuleSpecifier, |
||||||
|
}, |
||||||
|
expression::Identifier, |
||||||
|
Keyword, Punctuator, |
||||||
|
}; |
||||||
|
use boa_interner::{Interner, Sym}; |
||||||
|
use boa_profiler::Profiler; |
||||||
|
use std::io::Read; |
||||||
|
|
||||||
|
/// Parses an import declaration.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ImportDeclaration
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
pub(in crate::parser) struct ImportDeclaration; |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for ImportDeclaration |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = AstImportDeclaration; |
||||||
|
|
||||||
|
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { |
||||||
|
let _timer = Profiler::global().start_event("ImportDeclaration", "Parsing"); |
||||||
|
|
||||||
|
cursor.expect((Keyword::Import, false), "import declaration", interner)?; |
||||||
|
|
||||||
|
let tok = cursor.peek(0, interner).or_abrupt()?; |
||||||
|
|
||||||
|
let import_clause = match tok.kind() { |
||||||
|
TokenKind::StringLiteral((module_identifier, _)) => { |
||||||
|
let module_identifier = *module_identifier; |
||||||
|
|
||||||
|
cursor.advance(interner); |
||||||
|
cursor.expect_semicolon("import declaration", interner)?; |
||||||
|
|
||||||
|
return Ok(AstImportDeclaration::new( |
||||||
|
None, |
||||||
|
ImportKind::DefaultOrUnnamed, |
||||||
|
ModuleSpecifier::new(module_identifier), |
||||||
|
)); |
||||||
|
} |
||||||
|
TokenKind::Punctuator(Punctuator::OpenBlock) => { |
||||||
|
let list = NamedImports.parse(cursor, interner)?; |
||||||
|
ImportClause::ImportList(None, list) |
||||||
|
} |
||||||
|
TokenKind::Punctuator(Punctuator::Mul) => { |
||||||
|
let alias = NameSpaceImport.parse(cursor, interner)?; |
||||||
|
ImportClause::Namespace(None, alias) |
||||||
|
} |
||||||
|
TokenKind::IdentifierName(_) |
||||||
|
| TokenKind::Keyword((Keyword::Await | Keyword::Yield, _)) => { |
||||||
|
let imported_binding = ImportedBinding.parse(cursor, interner)?; |
||||||
|
|
||||||
|
let tok = cursor.peek(0, interner).or_abrupt()?; |
||||||
|
|
||||||
|
match tok.kind() { |
||||||
|
TokenKind::Punctuator(Punctuator::Comma) => { |
||||||
|
cursor.advance(interner); |
||||||
|
let tok = cursor.peek(0, interner).or_abrupt()?; |
||||||
|
|
||||||
|
match tok.kind() { |
||||||
|
TokenKind::Punctuator(Punctuator::OpenBlock) => { |
||||||
|
let list = NamedImports.parse(cursor, interner)?; |
||||||
|
ImportClause::ImportList(Some(imported_binding), list) |
||||||
|
} |
||||||
|
TokenKind::Punctuator(Punctuator::Mul) => { |
||||||
|
let alias = NameSpaceImport.parse(cursor, interner)?; |
||||||
|
ImportClause::Namespace(Some(imported_binding), alias) |
||||||
|
} |
||||||
|
_ => { |
||||||
|
return Err(Error::expected( |
||||||
|
[ |
||||||
|
Punctuator::OpenBlock.to_string(), |
||||||
|
Punctuator::Mul.to_string(), |
||||||
|
], |
||||||
|
tok.to_string(interner), |
||||||
|
tok.span(), |
||||||
|
"import declaration", |
||||||
|
)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
_ => ImportClause::ImportList(Some(imported_binding), Box::default()), |
||||||
|
} |
||||||
|
} |
||||||
|
_ => { |
||||||
|
return Err(Error::expected( |
||||||
|
[ |
||||||
|
Punctuator::OpenBlock.to_string(), |
||||||
|
Punctuator::Mul.to_string(), |
||||||
|
"identifier".to_owned(), |
||||||
|
"string literal".to_owned(), |
||||||
|
], |
||||||
|
tok.to_string(interner), |
||||||
|
tok.span(), |
||||||
|
"import declaration", |
||||||
|
)) |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let module_identifier = FromClause::new("import declaration").parse(cursor, interner)?; |
||||||
|
|
||||||
|
Ok(import_clause.with_specifier(module_identifier)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Parses an imported binding
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ImportedBinding
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
struct ImportedBinding; |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for ImportedBinding |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = Identifier; |
||||||
|
|
||||||
|
#[inline] |
||||||
|
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { |
||||||
|
BindingIdentifier::new(false, true).parse(cursor, interner) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Parses a named import list.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-NamedImports
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
struct NamedImports; |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for NamedImports |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = Box<[AstImportSpecifier]>; |
||||||
|
|
||||||
|
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { |
||||||
|
cursor.expect(Punctuator::OpenBlock, "import declaration", interner)?; |
||||||
|
|
||||||
|
let mut list = Vec::new(); |
||||||
|
|
||||||
|
loop { |
||||||
|
let tok = cursor.next(interner).or_abrupt()?; |
||||||
|
match tok.kind() { |
||||||
|
TokenKind::Punctuator(Punctuator::CloseBlock) => { |
||||||
|
break; |
||||||
|
} |
||||||
|
TokenKind::Punctuator(Punctuator::Comma) => { |
||||||
|
if list.is_empty() { |
||||||
|
return Err(Error::expected( |
||||||
|
[ |
||||||
|
Punctuator::CloseBlock.to_string(), |
||||||
|
"string literal".to_owned(), |
||||||
|
"identifier".to_owned(), |
||||||
|
], |
||||||
|
tok.to_string(interner), |
||||||
|
tok.span(), |
||||||
|
"import declaration", |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
TokenKind::StringLiteral(_) | TokenKind::IdentifierName(_) => { |
||||||
|
list.push(ImportSpecifier.parse(cursor, interner)?); |
||||||
|
} |
||||||
|
_ => { |
||||||
|
return Err(Error::expected( |
||||||
|
[ |
||||||
|
Punctuator::CloseBlock.to_string(), |
||||||
|
Punctuator::Comma.to_string(), |
||||||
|
], |
||||||
|
tok.to_string(interner), |
||||||
|
tok.span(), |
||||||
|
"import declaration", |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Ok(list.into_boxed_slice()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Parses an import clause.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ImportClause
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
enum ImportClause { |
||||||
|
Namespace(Option<Identifier>, Identifier), |
||||||
|
ImportList(Option<Identifier>, Box<[AstImportSpecifier]>), |
||||||
|
} |
||||||
|
|
||||||
|
impl ImportClause { |
||||||
|
#[inline] |
||||||
|
#[allow(clippy::missing_const_for_fn)] |
||||||
|
fn with_specifier(self, specifier: ModuleSpecifier) -> AstImportDeclaration { |
||||||
|
match self { |
||||||
|
Self::Namespace(default, binding) => { |
||||||
|
AstImportDeclaration::new(default, ImportKind::Namespaced { binding }, specifier) |
||||||
|
} |
||||||
|
Self::ImportList(default, names) => { |
||||||
|
if names.is_empty() { |
||||||
|
AstImportDeclaration::new(default, ImportKind::DefaultOrUnnamed, specifier) |
||||||
|
} else { |
||||||
|
AstImportDeclaration::new(default, ImportKind::Named { names }, specifier) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Parses an import specifier.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-ImportSpecifier
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
struct ImportSpecifier; |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for ImportSpecifier |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = AstImportSpecifier; |
||||||
|
|
||||||
|
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { |
||||||
|
let tok = cursor.next(interner).or_abrupt()?; |
||||||
|
|
||||||
|
match tok.kind() { |
||||||
|
TokenKind::StringLiteral((name, _)) => { |
||||||
|
if interner.resolve_expect(*name).utf8().is_none() { |
||||||
|
return Err(Error::general( |
||||||
|
"import specifiers don't allow unpaired surrogates", |
||||||
|
tok.span().end(), |
||||||
|
)); |
||||||
|
} |
||||||
|
cursor.expect( |
||||||
|
TokenKind::identifier(Sym::AS), |
||||||
|
"import declaration", |
||||||
|
interner, |
||||||
|
)?; |
||||||
|
|
||||||
|
let binding = ImportedBinding.parse(cursor, interner)?; |
||||||
|
|
||||||
|
Ok(AstImportSpecifier::new(binding, *name)) |
||||||
|
} |
||||||
|
TokenKind::IdentifierName((name, _)) => { |
||||||
|
if cursor |
||||||
|
.next_if(TokenKind::identifier(Sym::AS), interner)? |
||||||
|
.is_some() |
||||||
|
{ |
||||||
|
let binding = ImportedBinding.parse(cursor, interner)?; |
||||||
|
Ok(AstImportSpecifier::new(binding, *name)) |
||||||
|
} else { |
||||||
|
Ok(AstImportSpecifier::new(Identifier::new(*name), *name)) |
||||||
|
} |
||||||
|
} |
||||||
|
_ => Err(Error::expected( |
||||||
|
["string literal".to_owned(), "identifier".to_owned()], |
||||||
|
tok.to_string(interner), |
||||||
|
tok.span(), |
||||||
|
"import declaration", |
||||||
|
)), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Parses a namespace import
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-NameSpaceImport
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
struct NameSpaceImport; |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for NameSpaceImport |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = Identifier; |
||||||
|
|
||||||
|
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { |
||||||
|
cursor.expect(Punctuator::Mul, "import declaration", interner)?; |
||||||
|
cursor.expect( |
||||||
|
TokenKind::identifier(Sym::AS), |
||||||
|
"import declaration", |
||||||
|
interner, |
||||||
|
)?; |
||||||
|
|
||||||
|
ImportedBinding.parse(cursor, interner) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue