use super::{ExecError, Interp, N_SYNTAX_ERROR, NanBox, Thrown};
use crate::ast::{ExportDecl, ImportSpecifier, ModuleExportName, Program, Stmt};
use crate::env::Scope;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::rc::Rc;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
pub fn eval_module_typed(
entry_key: &str,
host: &dyn ModuleHost,
limits: crate::limits::Limits,
) -> Result<(String, String), Thrown> {
use super::ErrorPhase;
let mut interp = Interp::new_with_limits(limits);
let linked = interp
.load_module_pub(entry_key, host)
.and_then(|()| interp.link_module_pub(entry_key));
if let Err(e) = linked {
return Err(interp.exec_error_to_thrown(e, ErrorPhase::Parse));
}
match interp.evaluate_entry(entry_key) {
Ok(ns) => {
let completion = interp.display(ns);
Ok((String::from(interp.output()), completion))
}
Err(e) => Err(interp.exec_error_to_thrown(e, ErrorPhase::Runtime)),
}
}
pub fn eval_module_typed_with_prelude(
entry_key: &str,
host: &dyn ModuleHost,
prelude: &str,
limits: crate::limits::Limits,
) -> Result<(String, String), Thrown> {
use super::ErrorPhase;
let mut interp = Interp::new_with_limits(limits);
if !prelude.is_empty() {
let program = match crate::parser::Parser::parse_program(prelude) {
Ok(p) => alloc::boxed::Box::leak(alloc::boxed::Box::new(p)),
Err(e) => {
return Err(Thrown {
phase: ErrorPhase::Parse,
name: String::from("SyntaxError"),
message: alloc::format!("{e}"),
});
}
};
if let Err(e) = interp.run(program) {
return Err(interp.exec_error_to_thrown(e, ErrorPhase::Runtime));
}
}
let linked = interp
.load_module_pub(entry_key, host)
.and_then(|()| interp.link_module_pub(entry_key));
if let Err(e) = linked {
return Err(interp.exec_error_to_thrown(e, ErrorPhase::Parse));
}
match interp.evaluate_entry(entry_key) {
Ok(ns) => {
let completion = interp.display(ns);
Ok((String::from(interp.output()), completion))
}
Err(e) => Err(interp.exec_error_to_thrown(e, ErrorPhase::Runtime)),
}
}
pub fn eval_script_typed_with_import_base(
source: &str,
base_path: &str,
limits: crate::limits::Limits,
) -> Result<(String, String), Thrown> {
use super::ErrorPhase;
let program = match crate::parser::Parser::parse_program(source) {
Ok(p) => alloc::boxed::Box::leak(alloc::boxed::Box::new(p)),
Err(e) => {
return Err(Thrown {
phase: ErrorPhase::Parse,
name: String::from("SyntaxError"),
message: alloc::format!("{e}"),
});
}
};
let mut interp = Interp::new_with_limits(limits);
interp.set_script_import_base(Some(base_path.to_string()));
match interp.run(program) {
Ok(value) => {
let completion = interp.display(value);
Ok((String::from(interp.output()), completion))
}
Err(e) => Err(interp.exec_error_to_thrown(e, ErrorPhase::Runtime)),
}
}
pub fn eval_module(
entry_key: &str,
host: &dyn ModuleHost,
limits: crate::limits::Limits,
) -> Result<(String, String), String> {
match eval_module_typed(entry_key, host, limits) {
Ok(ok) => Ok(ok),
Err(t) => Err(if t.message.is_empty() {
t.name
} else {
alloc::format!("{}: {}", t.name, t.message)
}),
}
}
pub trait ModuleHost {
fn resolve(&self, specifier: &str, referrer: Option<&str>) -> Result<String, String>;
fn load(&self, key: &str) -> Result<String, String>;
}
pub struct FileModuleHost;
impl ModuleHost for FileModuleHost {
fn resolve(&self, specifier: &str, referrer: Option<&str>) -> Result<String, String> {
use std::path::{Path, PathBuf};
let base: PathBuf = match referrer {
Some(r) => Path::new(r)
.parent()
.map_or_else(|| PathBuf::from("."), Path::to_path_buf),
None => PathBuf::from("."),
};
let joined = base.join(specifier);
match std::fs::canonicalize(&joined) {
Ok(p) => Ok(p.to_string_lossy().into_owned()),
Err(_) => Ok(joined.to_string_lossy().into_owned()),
}
}
fn load(&self, key: &str) -> Result<String, String> {
std::fs::read_to_string(key).map_err(|e| alloc::format!("cannot load module {key}: {e}"))
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Status {
New,
Loaded,
Linked,
Evaluating,
Evaluated,
}
struct ImportEntry {
key: String,
specifiers: Vec<ImportBind>,
deferred: bool,
}
enum ImportBind {
Default(String),
Namespace(String),
Named { imported: String, local: String },
}
enum ReExport {
Named {
key: String,
local: String,
exported: String,
},
Star { key: String },
StarAs { key: String, exported: String },
}
struct ModuleRecord {
key: String,
program: &'static Program,
imports: Vec<ImportEntry>,
reexports: Vec<ReExport>,
local_exports: BTreeMap<String, String>,
scope: Scope,
import_aliases: Rc<BTreeMap<String, (Scope, String)>>,
namespace: Option<NanBox>,
deferred_namespace: Option<NanBox>,
meta: Option<NanBox>,
status: Status,
eval_error: Option<NanBox>,
}
pub struct ModuleRegistry {
records: BTreeMap<String, ModuleRecord>,
}
impl ModuleRegistry {
pub(crate) fn new() -> Self {
Self {
records: BTreeMap::new(),
}
}
}
const DEFAULT_LOCAL: &str = "*default*";
impl<'a> Interp<'a> {
pub fn run_module(
&mut self,
entry_key: &str,
host: &dyn ModuleHost,
) -> Result<NanBox, ExecError> {
self.load_module(entry_key, host)?;
self.link_module(entry_key)?;
self.evaluate_entry(entry_key)
}
pub fn load_module_pub(
&mut self,
entry_key: &str,
host: &dyn ModuleHost,
) -> Result<(), ExecError> {
self.load_module(entry_key, host)
}
pub fn link_module_pub(&mut self, entry_key: &str) -> Result<(), ExecError> {
self.link_module(entry_key)
}
pub fn evaluate_entry(&mut self, entry_key: &str) -> Result<NanBox, ExecError> {
if self.script_import_base.is_none() {
self.script_import_base = Some(entry_key.to_string());
}
let r = self.evaluate_module(entry_key);
let _ = self.run_event_loop();
r?;
self.run_event_loop()?;
self.namespace_object(entry_key)
}
pub fn exec_error_to_thrown(&self, e: ExecError, phase: super::ErrorPhase) -> Thrown {
match e {
ExecError::Throw(thrown) => {
let (name, message) = super::error_name_message(self, thrown)
.unwrap_or_else(|| (self.display(thrown), String::new()));
Thrown {
phase,
name,
message,
}
}
other => Thrown {
phase,
name: String::from("Error"),
message: alloc::format!("{other:?}"),
},
}
}
fn load_module(&mut self, key: &str, host: &dyn ModuleHost) -> Result<(), ExecError> {
if self.modules.records.contains_key(key) {
return Ok(());
}
let source = host.load(key).map_err(|e| self.syntax_error(&e))?;
let record = self.parse_module(key, &source, host)?;
let deps: Vec<String> = record
.imports
.iter()
.map(|i| i.key.clone())
.chain(record.reexports.iter().map(reexport_key))
.collect();
self.modules.records.insert(key.to_string(), record);
for dep in deps {
self.load_module(&dep, host)?;
}
if let Some(r) = self.modules.records.get_mut(key) {
r.status = Status::Loaded;
}
Ok(())
}
fn parse_module(
&mut self,
key: &str,
source: &str,
host: &dyn ModuleHost,
) -> Result<ModuleRecord, ExecError> {
let program = crate::parser::Parser::parse_module(source)
.map_err(|e| self.syntax_error(&alloc::format!("{e}")))?;
let program: &'static Program = alloc::boxed::Box::leak(alloc::boxed::Box::new(program));
let mut imports: Vec<ImportEntry> = Vec::new();
let mut reexports: Vec<ReExport> = Vec::new();
let mut local_exports: BTreeMap<String, String> = BTreeMap::new();
let resolve = |spec: &str, this: &mut Self| -> Result<String, ExecError> {
host.resolve(spec, Some(key))
.map_err(|e| this.syntax_error(&e))
};
for stmt in &program.body {
match stmt {
Stmt::Import(decl) => {
let dep = resolve(&decl.source, self)?;
let mut binds = Vec::new();
for s in &decl.specifiers {
match s {
ImportSpecifier::Default(id) => {
binds.push(ImportBind::Default(id.name.to_string()));
}
ImportSpecifier::Namespace(id) => {
binds.push(ImportBind::Namespace(id.name.to_string()));
}
ImportSpecifier::Named { imported, local } => {
binds.push(ImportBind::Named {
imported: export_name(imported),
local: local.name.to_string(),
});
}
}
}
imports.push(ImportEntry {
key: dep,
specifiers: binds,
deferred: decl.deferred,
});
}
Stmt::Export(ExportDecl::Named {
specifiers,
source: Some(src),
..
}) => {
let dep = resolve(src, self)?;
for sp in specifiers {
reexports.push(ReExport::Named {
key: dep.clone(),
local: export_name(&sp.local),
exported: export_name(&sp.exported),
});
}
}
Stmt::Export(ExportDecl::Named {
specifiers,
source: None,
..
}) => {
for sp in specifiers {
local_exports.insert(export_name(&sp.exported), export_name(&sp.local));
}
}
Stmt::Export(ExportDecl::All {
exported,
source: src,
..
}) => {
let dep = resolve(src, self)?;
match exported {
Some(name) => reexports.push(ReExport::StarAs {
key: dep,
exported: export_name(name),
}),
None => reexports.push(ReExport::Star { key: dep }),
}
}
Stmt::Export(ExportDecl::Default { declaration, .. }) => {
let local = decl_name(declaration).map_or_else(
|| DEFAULT_LOCAL.to_string(),
alloc::string::ToString::to_string,
);
local_exports.insert("default".to_string(), local);
}
Stmt::Export(ExportDecl::Decl { declaration, .. }) => {
for name in declared_names(declaration) {
local_exports.insert(name.clone(), name);
}
}
_ => {}
}
}
Ok(ModuleRecord {
key: key.to_string(),
program,
imports,
reexports,
local_exports,
scope: Scope::root(),
import_aliases: Rc::new(BTreeMap::new()),
namespace: None,
deferred_namespace: None,
meta: None,
status: Status::New,
eval_error: None,
})
}
fn link_module(&mut self, key: &str) -> Result<(), ExecError> {
match self.modules.records.get(key).map(|r| r.status) {
Some(Status::New | Status::Loaded) => {}
_ => return Ok(()),
}
let scope = self.global_scope.child();
if let Some(r) = self.modules.records.get_mut(key) {
r.scope = scope;
r.status = Status::Linked;
}
let dep_keys: Vec<String> = {
let r = &self.modules.records[key];
r.imports
.iter()
.map(|i| i.key.clone())
.chain(r.reexports.iter().map(reexport_key))
.collect()
};
for dep in &dep_keys {
self.link_module(dep)?;
}
let imports: Vec<DepBinds> = {
let r = &self.modules.records[key];
r.imports
.iter()
.map(|i| {
let binds = i
.specifiers
.iter()
.map(|b| match b {
ImportBind::Default(local) => (local.clone(), ImportKind::Default),
ImportBind::Namespace(local) => (local.clone(), ImportKind::Namespace),
ImportBind::Named { imported, local } => {
(local.clone(), ImportKind::Named(imported.clone()))
}
})
.collect();
DepBinds {
dep: i.key.clone(),
binds,
deferred: i.deferred,
}
})
.collect()
};
let mut aliases: BTreeMap<String, (Scope, String)> = BTreeMap::new();
for DepBinds {
dep: dep_key,
binds,
deferred,
} in &imports
{
for (local, kind) in binds {
match kind {
ImportKind::Default => {
let (src_scope, src_name) =
self.resolve_export(dep_key, "default", &mut BTreeSet::new())?;
aliases.insert(local.clone(), (src_scope, src_name));
}
ImportKind::Named(imported) => {
let (src_scope, src_name) =
self.resolve_export(dep_key, imported, &mut BTreeSet::new())?;
aliases.insert(local.clone(), (src_scope, src_name));
}
ImportKind::Namespace => {
let ns = if *deferred {
self.deferred_namespace_object(dep_key)?
} else {
self.namespace_object(dep_key)?
};
let r = &self.modules.records[key];
r.scope.declare_const(local, ns);
}
}
}
}
let reexports: Vec<ReExport> = {
let r = &self.modules.records[key];
r.reexports
.iter()
.map(|re| match re {
ReExport::Named {
key,
local,
exported,
} => ReExport::Named {
key: key.clone(),
local: local.clone(),
exported: exported.clone(),
},
ReExport::Star { key } => ReExport::Star { key: key.clone() },
ReExport::StarAs { key, exported } => ReExport::StarAs {
key: key.clone(),
exported: exported.clone(),
},
})
.collect()
};
for re in &reexports {
if let ReExport::Named { key, local, .. } = re {
self.resolve_export(key, local, &mut BTreeSet::new())?;
}
}
let aliases = Rc::new(aliases);
if let Some(r) = self.modules.records.get_mut(key) {
r.import_aliases = aliases;
}
self.instantiate_module_functions(key)?;
Ok(())
}
fn instantiate_module_functions(&mut self, key: &str) -> Result<(), ExecError> {
let (scope, program) = {
let r = &self.modules.records[key];
(r.scope.clone(), r.program)
};
let saved = core::mem::replace(&mut self.current, scope.clone());
let saved_strict = core::mem::replace(&mut self.strict, true);
for stmt in &program.body {
let inner = match stmt {
Stmt::Function(_) => stmt,
Stmt::Export(ExportDecl::Decl { declaration, .. })
| Stmt::Export(ExportDecl::Default { declaration, .. }) => declaration,
_ => continue,
};
if let Stmt::Function(func) = inner {
let is_default = matches!(stmt, Stmt::Export(ExportDecl::Default { .. }));
match &func.id {
Some(id) => {
let value = self.make_function(
&func.params,
super::Body::Block(&func.body),
func.is_async,
func.is_generator,
);
self.set_fn_name(value, &id.name);
scope.declare(&id.name, value);
if is_default {
scope.declare_const(DEFAULT_LOCAL, value);
}
}
None if is_default => {
let value = self.make_function(
&func.params,
super::Body::Block(&func.body),
func.is_async,
func.is_generator,
);
self.set_fn_name(value, "default");
scope.declare_const(DEFAULT_LOCAL, value);
}
None => {}
}
}
}
self.current = saved;
self.strict = saved_strict;
Ok(())
}
fn resolve_export(
&mut self,
key: &str,
name: &str,
seen: &mut BTreeSet<(String, String)>,
) -> Result<(Scope, String), ExecError> {
if !seen.insert((key.to_string(), name.to_string())) {
return Err(self.syntax_error(&alloc::format!(
"circular re-export resolving '{name}' from {key}"
)));
}
let Some(record) = self.modules.records.get(key) else {
return Err(self.syntax_error(&alloc::format!("module not loaded: {key}")));
};
if let Some(local) = record.local_exports.get(name) {
return Ok((record.scope.clone(), local.clone()));
}
let named: Vec<(String, String)> = record
.reexports
.iter()
.filter_map(|re| match re {
ReExport::Named {
key,
local,
exported,
} if exported == name => Some((key.clone(), local.clone())),
ReExport::StarAs { key, exported } if exported == name => {
Some((key.clone(), String::new()))
}
_ => None,
})
.collect();
if let Some((dep, local)) = named.first() {
if local.is_empty() {
let ns = self.namespace_object(dep)?;
let scope = self.modules.records[key].scope.clone();
let synth = alloc::format!("*ns:{dep}*");
scope.declare_const(&synth, ns);
return Ok((scope, synth));
}
return self.resolve_export(dep, local, seen);
}
let stars: Vec<String> = record
.reexports
.iter()
.filter_map(|re| match re {
ReExport::Star { key } => Some(key.clone()),
_ => None,
})
.collect();
let mut found: Option<(Scope, String)> = None;
for dep in &stars {
if name == "default" {
continue;
}
if let Ok(slot) = self.resolve_export(dep, name, &mut seen.clone()) {
if let Some(prev) = &found {
let same_slot = prev.0.ptr_eq(&slot.0) && prev.1 == slot.1;
let same_value = {
let a = prev.0.get(&prev.1);
let b = slot.0.get(&slot.1);
matches!((a, b), (Some(x), Some(y)) if x.as_handle() == y.as_handle() && x.as_handle().is_some())
};
if !same_slot && !same_value {
return Err(self.syntax_error(&alloc::format!(
"ambiguous export '{name}' (multiple `export *`)"
)));
}
} else {
found = Some(slot);
}
}
}
if let Some(slot) = found {
return Ok(slot);
}
Err(self.syntax_error(&alloc::format!("module {key} has no export named '{name}'")))
}
fn evaluate_module(&mut self, key: &str) -> Result<(), ExecError> {
match self.modules.records.get(key).map(|r| r.status) {
Some(Status::Evaluating | Status::Evaluated) => {
if let Some(err) = self.modules.records.get(key).and_then(|r| r.eval_error) {
return Err(ExecError::Throw(err));
}
return Ok(());
}
Some(Status::Linked) => {}
_ => return Err(self.syntax_error(&alloc::format!("module {key} not linked"))),
}
let deps: Vec<String> = {
let r = &self.modules.records[key];
r.imports
.iter()
.filter(|i| !i.deferred)
.map(|i| i.key.clone())
.chain(r.reexports.iter().map(reexport_key))
.collect()
};
if let Some(r) = self.modules.records.get_mut(key) {
r.status = Status::Evaluating;
}
for dep in &deps {
self.evaluate_module(dep)?;
}
let result = self.run_module_body(key);
if let Some(r) = self.modules.records.get_mut(key) {
r.status = Status::Evaluated;
if let Err(ExecError::Throw(v)) = &result {
r.eval_error = Some(*v);
}
}
result
}
fn run_module_body(&mut self, key: &str) -> Result<(), ExecError> {
let (scope, program, aliases) = {
let r = &self.modules.records[key];
(r.scope.clone(), r.program, r.import_aliases.clone())
};
let meta = self.module_meta(key);
let saved_scope = core::mem::replace(&mut self.current, scope.clone());
let saved_var = core::mem::replace(&mut self.var_scope, scope.clone());
let saved_strict = core::mem::replace(&mut self.strict, true);
let saved_imports = core::mem::replace(&mut self.module_imports, aliases);
let saved_meta = self.import_meta.replace(meta);
let saved_this = core::mem::replace(&mut self.this_val, NanBox::undefined());
let saved_annexb = core::mem::take(&mut self.annexb_block_fns);
let saved_active = self.active_module_key.replace(key.to_string());
let result = self.exec_module_stmts(&program.body);
self.current = saved_scope;
self.var_scope = saved_var;
self.strict = saved_strict;
self.module_imports = saved_imports;
self.import_meta = saved_meta;
self.this_val = saved_this;
self.annexb_block_fns = saved_annexb;
self.active_module_key = saved_active;
result
}
fn exec_module_stmts(&mut self, stmts: &'a [Stmt]) -> Result<(), ExecError> {
self.hoist_with(stmts, true)?;
for stmt in stmts {
match stmt {
Stmt::Import(_) => {}
Stmt::Export(decl) => self.exec_export(decl)?,
other => {
self.exec(other)?;
}
}
}
Ok(())
}
fn exec_export(&mut self, decl: &'a ExportDecl) -> Result<(), ExecError> {
match decl {
ExportDecl::All { .. } => Ok(()),
ExportDecl::Named {
source: Some(_), ..
} => Ok(()),
ExportDecl::Named { source: None, .. } => Ok(()),
ExportDecl::Decl { declaration, .. } => {
self.exec(declaration)?;
Ok(())
}
ExportDecl::Default { declaration, .. } => {
match &**declaration {
Stmt::Function(crate::ast::Function { id: Some(_), .. })
| Stmt::Class(crate::ast::Class { id: Some(_), .. }) => {
self.exec(declaration)?;
let value = decl_name(declaration)
.and_then(|n| self.current.get(n))
.unwrap_or_else(NanBox::undefined);
self.current.declare_const(DEFAULT_LOCAL, value);
Ok(())
}
Stmt::Function(func) => {
let value = self.make_function(
&func.params,
super::Body::Block(&func.body),
func.is_async,
func.is_generator,
);
self.set_fn_name(value, "default");
self.current.declare_const(DEFAULT_LOCAL, value);
Ok(())
}
Stmt::Class(class) => {
let value = self.make_class(class)?;
self.set_fn_name(value, "default");
self.current.declare_const(DEFAULT_LOCAL, value);
Ok(())
}
Stmt::Expr { expression, .. } => {
let value = self.eval(expression)?;
if matches!(
&**expression,
crate::ast::Expr::Function(crate::ast::Function { id: None, .. })
| crate::ast::Expr::Class(crate::ast::Class { id: None, .. })
| crate::ast::Expr::Arrow(_)
) {
self.set_fn_name(value, "default");
}
self.current.declare_const(DEFAULT_LOCAL, value);
Ok(())
}
other => {
self.exec(other)?;
Ok(())
}
}
}
}
}
fn namespace_object(&mut self, key: &str) -> Result<NanBox, ExecError> {
if let Some(ns) = self.modules.records.get(key).and_then(|r| r.namespace) {
return Ok(ns);
}
let obj = self.realm.new_object_with_proto(None);
let ns = NanBox::handle(obj.to_raw());
if let Some(r) = self.modules.records.get_mut(key) {
r.namespace = Some(ns);
}
self.populate_namespace(obj, key, false)?;
Ok(ns)
}
fn deferred_namespace_object(&mut self, key: &str) -> Result<NanBox, ExecError> {
if let Some(ns) = self
.modules
.records
.get(key)
.and_then(|r| r.deferred_namespace)
{
return Ok(ns);
}
let obj = self.realm.new_object_with_proto(None);
let ns = NanBox::handle(obj.to_raw());
if let Some(r) = self.modules.records.get_mut(key) {
r.deferred_namespace = Some(ns);
}
let already = matches!(
self.modules.records.get(key).map(|r| r.status),
Some(Status::Evaluated)
);
#[cfg(all(feature = "module", feature = "std"))]
if !already {
self.deferred_namespaces
.insert(obj.to_raw(), key.to_string());
}
self.populate_namespace(obj, key, true)?;
Ok(ns)
}
fn populate_namespace(
&mut self,
obj: crate::heap::Handle,
key: &str,
deferred: bool,
) -> Result<(), ExecError> {
let names = self.export_names(key, &mut BTreeSet::new())?;
let mut slots: Vec<(String, Scope, String)> = Vec::new();
for n in &names {
if let Ok((s, l)) = self.resolve_export(key, n, &mut BTreeSet::new()) {
slots.push((n.clone(), s, l));
}
}
for (name, scope, local) in &slots {
let value = scope.get(local).unwrap_or_else(NanBox::undefined);
self.realm.set_property(obj, name, value);
}
let binding_map: BTreeMap<String, (Scope, String)> = slots
.iter()
.map(|(n, s, l)| (n.clone(), (s.clone(), l.clone())))
.collect();
self.module_namespaces.insert(obj.to_raw(), binding_map);
let tag_sym = self.well_known_symbol("toStringTag");
let tag_key = self.member_key(tag_sym);
let module_str = self.new_str(if deferred {
"Deferred Module"
} else {
"Module"
});
self.realm.set_property(obj, &tag_key, module_str);
self.realm.mark_hidden(obj, &tag_key);
self.realm.set_readonly_property(obj, &tag_key);
self.realm.set_non_configurable_property(obj, &tag_key);
for (name, _, _) in &slots {
self.realm.set_non_configurable_property(obj, name);
}
self.realm.prevent_extensions(obj);
Ok(())
}
pub(crate) fn trigger_deferred_namespace(
&mut self,
handle: crate::heap::Handle,
name: &str,
) -> Result<(), ExecError> {
if name == "then" || name.starts_with("\u{0}sym:") {
return Ok(());
}
self.force_deferred_namespace(handle)
}
pub(crate) fn trigger_deferred_in_chain(
&mut self,
handle: crate::heap::Handle,
name: &str,
) -> Result<(), ExecError> {
if self.deferred_namespaces.is_empty() || name == "then" || name.starts_with("\u{0}sym:") {
return Ok(());
}
let mut cur = Some(handle);
let mut guard = 0usize;
while let Some(h) = cur {
if self.deferred_namespaces.contains_key(&h.to_raw()) {
return self.force_deferred_namespace(h);
}
if self.realm.has_own(h, name) {
return Ok(());
}
guard += 1;
if guard > 100_000 {
break;
}
cur = self.realm.object_proto(h);
}
Ok(())
}
pub(crate) fn force_deferred_namespace(
&mut self,
handle: crate::heap::Handle,
) -> Result<(), ExecError> {
let Some(dep) = self.deferred_namespaces.get(&handle.to_raw()).cloned() else {
return Ok(());
};
if self.deferred_closure_has_evaluating(&dep) {
return Err(self.type_error(
"Cannot access a deferred module namespace while the module is being evaluated",
));
}
self.evaluate_module(&dep)?;
self.deferred_namespaces.remove(&handle.to_raw());
if let Some(map) = self.module_namespaces.get(&handle.to_raw()) {
let refreshed: Vec<(String, NanBox)> = map
.iter()
.map(|(name, (scope, local))| {
(
name.clone(),
scope.get(local).unwrap_or_else(NanBox::undefined),
)
})
.collect();
for (name, value) in refreshed {
self.realm.set_property(handle, &name, value);
}
}
Ok(())
}
fn deferred_closure_has_evaluating(&self, key: &str) -> bool {
let mut stack = alloc::vec![key.to_string()];
let mut seen = BTreeSet::new();
while let Some(k) = stack.pop() {
if !seen.insert(k.clone()) {
continue;
}
let Some(r) = self.modules.records.get(&k) else {
continue;
};
if r.status == Status::Evaluating {
return true;
}
if r.status == Status::Evaluated {
continue;
}
for dep in r
.imports
.iter()
.filter(|i| !i.deferred)
.map(|i| i.key.clone())
.chain(r.reexports.iter().map(reexport_key))
{
stack.push(dep);
}
}
false
}
fn export_names(
&mut self,
key: &str,
seen: &mut BTreeSet<String>,
) -> Result<Vec<String>, ExecError> {
if !seen.insert(key.to_string()) {
return Ok(Vec::new());
}
let Some(record) = self.modules.records.get(key) else {
return Err(self.syntax_error(&alloc::format!("module not loaded: {key}")));
};
let mut names: BTreeSet<String> = record.local_exports.keys().cloned().collect();
let mut star_deps: Vec<String> = Vec::new();
for re in &record.reexports {
match re {
ReExport::Named { exported, .. } | ReExport::StarAs { exported, .. } => {
names.insert(exported.clone());
}
ReExport::Star { key } => star_deps.push(key.clone()),
}
}
for dep in star_deps {
for n in self.export_names(&dep, seen)? {
if n != "default" {
names.insert(n);
}
}
}
Ok(names.into_iter().collect())
}
fn module_meta(&mut self, key: &str) -> NanBox {
if let Some(m) = self.modules.records.get(key).and_then(|r| r.meta) {
return m;
}
let obj = self.realm.new_object_with_proto(None);
let url = if key.starts_with("file://") || key.contains("://") {
key.to_string()
} else {
alloc::format!("file://{key}")
};
let url_val = self.new_str(&url);
self.realm.set_property(obj, "url", url_val);
let meta = NanBox::handle(obj.to_raw());
if let Some(r) = self.modules.records.get_mut(key) {
r.meta = Some(meta);
}
meta
}
pub(crate) fn dynamic_import(
&mut self,
arguments: &'a [crate::ast::Argument],
) -> Result<NanBox, ExecError> {
let promise = self.fresh_promise();
let spec = match arguments.first() {
Some(crate::ast::Argument::Item(e)) => self.eval(e)?,
_ => NanBox::undefined(),
};
let referrer = self.current_module_key();
let host = FileModuleHost;
let outcome: Result<NanBox, ExecError> = (|this: &mut Self| {
let spec_str = this.coerce_to_string(spec)?;
let dep = host
.resolve(&spec_str, referrer.as_deref())
.map_err(|e| this.type_error(&e))?;
this.load_module(&dep, &host)?;
this.link_module(&dep)?;
this.evaluate_module(&dep)?;
this.namespace_object(&dep)
})(self);
match outcome {
Ok(ns) => self.settle(promise, ns, true),
Err(ExecError::Throw(v)) => self.settle(promise, v, false),
Err(other) => {
let m = self.new_str(&alloc::format!("dynamic import failed: {other:?}"));
let err = self.make_error(N_SYNTAX_ERROR, Some(m));
self.settle(promise, err, false);
}
}
Ok(NanBox::handle(promise.to_raw()))
}
pub(crate) fn dynamic_import_deferred(
&mut self,
arguments: &'a [crate::ast::Argument],
) -> Result<NanBox, ExecError> {
let promise = self.fresh_promise();
let spec = match arguments.first() {
Some(crate::ast::Argument::Item(e)) => self.eval(e)?,
_ => NanBox::undefined(),
};
let referrer = self.current_module_key();
let host = FileModuleHost;
let outcome: Result<NanBox, ExecError> = (|this: &mut Self| {
let spec_str = this.coerce_to_string(spec)?;
let dep = host
.resolve(&spec_str, referrer.as_deref())
.map_err(|e| this.type_error(&e))?;
this.load_module(&dep, &host)?;
this.link_module(&dep)?;
this.deferred_namespace_object(&dep)
})(self);
match outcome {
Ok(ns) => self.settle(promise, ns, true),
Err(ExecError::Throw(v)) => self.settle(promise, v, false),
Err(other) => {
let m = self.new_str(&alloc::format!("dynamic import failed: {other:?}"));
let err = self.make_error(N_SYNTAX_ERROR, Some(m));
self.settle(promise, err, false);
}
}
Ok(NanBox::handle(promise.to_raw()))
}
fn current_module_key(&self) -> Option<String> {
self.modules
.records
.values()
.find(|r| r.scope.ptr_eq(&self.current))
.map(|r| r.key.clone())
.or_else(|| self.active_module_key.clone())
.or_else(|| self.script_import_base.clone())
}
pub fn set_script_import_base(&mut self, base: Option<String>) {
self.script_import_base = base;
}
}
enum ImportKind {
Default,
Namespace,
Named(String),
}
struct DepBinds {
dep: String,
binds: Vec<(String, ImportKind)>,
deferred: bool,
}
fn reexport_key(re: &ReExport) -> String {
match re {
ReExport::Named { key, .. } | ReExport::Star { key } | ReExport::StarAs { key, .. } => {
key.clone()
}
}
}
fn export_name(n: &ModuleExportName) -> String {
match n {
ModuleExportName::Ident(s) | ModuleExportName::Str(s) => s.to_string(),
}
}
fn decl_name(stmt: &Stmt) -> Option<&str> {
match stmt {
Stmt::Function(f) => f.id.as_ref().map(|i| i.name.as_ref()),
Stmt::Class(c) => c.id.as_ref().map(|i| i.name.as_ref()),
_ => None,
}
}
fn declared_names(stmt: &Stmt) -> Vec<String> {
let mut out = Vec::new();
match stmt {
Stmt::Function(f) => {
if let Some(id) = &f.id {
out.push(id.name.to_string());
}
}
Stmt::Class(c) => {
if let Some(id) = &c.id {
out.push(id.name.to_string());
}
}
Stmt::Var(decl) => {
for d in &decl.declarations {
collect_pattern_names(&d.target, &mut out);
}
}
_ => {}
}
out
}
fn collect_pattern_names(target: &crate::ast::BindingTarget, out: &mut Vec<String>) {
use crate::ast::{ArrayPatternElement, BindingTarget};
match target {
BindingTarget::Ident(id) => out.push(id.name.to_string()),
BindingTarget::Array(pat) => {
for el in &pat.elements {
match el {
ArrayPatternElement::Hole => {}
ArrayPatternElement::Item { target, .. }
| ArrayPatternElement::Rest { target, .. } => {
collect_pattern_names(target, out);
}
}
}
}
BindingTarget::Object(pat) => {
for p in &pat.properties {
collect_pattern_names(&p.value, out);
}
if let Some(r) = &pat.rest {
collect_pattern_names(r, out);
}
}
}
}