///|
const MODULE_NAMESPACE_OBJECT_KEY = "[[NamespaceObject]]"
///|
const MODULE_NAMESPACE_CACHE_CLASS = "ModuleNamespaceCache"
///|
fn make_module_namespace_cache(ns_obj : Value) -> Value {
Object({
bag: {
properties: Map([]),
symbol_properties: Map([]),
descriptors: Map([]),
symbol_descriptors: Map([]),
internal_slots: Map::from_array([(NamespaceObject, ns_obj)]),
host_slots: Map([]),
},
prototype: Null,
callable: None,
class_name: MODULE_NAMESPACE_CACHE_CLASS,
extensible: false,
arraybuffer_state: None,
})
}
///|
fn make_module_namespace_cache_with_export(
ns_obj : Value,
export_value : Value,
) -> Value {
Object({
bag: {
properties: Map([]),
symbol_properties: Map([]),
descriptors: Map([]),
symbol_descriptors: Map([]),
internal_slots: Map::from_array([
(NamespaceObject, ns_obj),
(ExportValue, export_value),
]),
host_slots: Map([]),
},
prototype: Null,
callable: None,
class_name: MODULE_NAMESPACE_CACHE_CLASS,
extensible: false,
arraybuffer_state: None,
})
}
///|
fn module_namespace_cached_value(value : Value) -> Value? {
match value {
Object(data) if data.class_name == MODULE_NAMESPACE_CACHE_CLASS =>
data.bag.internal_slots.get(NamespaceObject)
_ => None
}
}
///|
fn module_namespace_public_export_value(value : Value) -> Value? {
match value {
Object(data) if data.class_name == MODULE_NAMESPACE_CACHE_CLASS =>
data.bag.internal_slots.get(ExportValue)
_ => None
}
}
///|
fn is_private_module_export_entry(name : String, value : Value) -> Bool {
name == MODULE_NAMESPACE_OBJECT_KEY &&
module_namespace_cached_value(value) is Some(_) &&
module_namespace_public_export_value(value) is None
}
///|
fn module_export_map_get_public(
exports_map : Map[String, Value],
export_name : String,
) -> Value? {
match exports_map.get(export_name) {
Some(value) =>
match module_namespace_public_export_value(value) {
Some(public_value) => Some(public_value)
None =>
if is_private_module_export_entry(export_name, value) {
None
} else {
Some(value)
}
}
_ => None
}
}
///|
fn module_export_entry_is_live_cell(value : Value) -> Bool {
let public_value = match module_namespace_public_export_value(value) {
Some(v) => v
None => value
}
match public_value {
Object(data) => data.class_name == "ModuleExportCell"
_ => false
}
}
///|
/// A private marker for exported bindings that exist during module
/// instantiation but have not been initialized yet. Namespace access resolves
/// this marker to the ReferenceError required for TDZ exports.
fn make_module_tdz_marker(export_name : String) -> Value {
Object({
bag: {
properties: Map([]),
symbol_properties: Map([]),
descriptors: Map([]),
symbol_descriptors: Map([]),
internal_slots: Map::from_array([(ExportName, String_(export_name))]),
host_slots: Map([]),
},
prototype: Null,
callable: None,
class_name: "ModuleTDZ",
extensible: false,
arraybuffer_state: None,
})
}
///|
/// A private cell used while a module is evaluating. It lets module namespace
/// getters observe the current value of a local export before final exports are
/// materialized into plain values.
fn make_module_export_cell(
export_name : String,
local_name : String,
env : Environment,
) -> Value {
Object({
bag: PropertyBag(),
prototype: Null,
callable: Some(
NonConstructableCallable("get export \{export_name}", fn(_args) raise {
env.get(local_name)
}),
),
class_name: "ModuleExportCell",
extensible: false,
arraybuffer_state: None,
})
}
///|
fn make_module_import_cell(
export_name : String,
module_exports : Map[String, Value],
) -> Value {
Object({
bag: PropertyBag(),
prototype: Null,
callable: Some(
NonConstructableCallable("get import \{export_name}", fn(_args) raise {
match module_export_map_get_public(module_exports, export_name) {
Some(value) => resolve_module_export_value(value)
None =>
raise @errors.SyntaxError(
message="Module does not export '\{export_name}'",
)
}
}),
),
class_name: "ModuleExportCell",
extensible: false,
arraybuffer_state: None,
})
}
///|
fn resolve_module_export_value(value : Value) -> Value raise Error {
match module_namespace_public_export_value(value) {
Some(public_value) => return resolve_module_export_value(public_value)
None => ()
}
match value {
Object(data) if data.class_name == "ModuleTDZ" => {
let export_name = match data.bag.internal_slots.get(ExportName) {
Some(String_(name)) => name
_ => "export"
}
raise @errors.ReferenceError(
message="Cannot access '\{export_name}' before initialization",
)
}
Object(data) if data.class_name == "ModuleExportCell" =>
match data.callable {
Some(NonConstructableCallable(_, get_value)) => get_value([])
_ => Undefined
}
_ => value
}
}
///|
fn make_module_namespace_getter(
module_exports : Map[String, Value],
export_name : String,
) -> Value {
make_native_func(name="get \{export_name}", length=0, fn(_args) raise {
match module_exports.get(export_name) {
Some(value) => resolve_module_export_value(value)
None => Undefined
}
})
}
///|
fn Interpreter::create_module_namespace_object(
self : Interpreter,
module_exports : Map[String, Value],
) -> Value {
match module_exports.get(MODULE_NAMESPACE_OBJECT_KEY) {
Some(cached) =>
match module_namespace_cached_value(cached) {
Some(ns_obj) => return ns_obj
None => ()
}
None => ()
}
let ns_props : Map[String, Value] = Map([])
let ns_descs : Map[String, PropDescriptor] = Map([])
module_exports.each(fn(k, v) {
if is_private_module_export_entry(k, v) {
return
}
ns_props[k] = Undefined
ns_descs[k] = {
writable: false,
enumerable: true,
configurable: false,
getter: Some(make_module_namespace_getter(module_exports, k)),
setter: None,
is_accessor: true,
}
})
let symbol_props : Map[Int, Value] = Map([])
let symbol_descs : Map[Int, PropDescriptor] = Map([])
let tag_sym = self.symbols.well_known_symbols().to_string_tag
symbol_props[tag_sym.id] = String_("Module")
symbol_descs[tag_sym.id] = {
writable: false,
enumerable: false,
configurable: false,
getter: None,
setter: None,
is_accessor: false,
}
let ns_obj : Value = Object({
bag: {
properties: ns_props,
symbol_properties: symbol_props,
descriptors: ns_descs,
symbol_descriptors: symbol_descs,
internal_slots: Map([]),
host_slots: Map([]),
},
prototype: Null,
callable: None,
class_name: "Module",
extensible: false,
arraybuffer_state: None,
})
match module_exports.get(MODULE_NAMESPACE_OBJECT_KEY) {
Some(public_value) =>
module_exports[MODULE_NAMESPACE_OBJECT_KEY] = make_module_namespace_cache_with_export(
ns_obj, public_value,
)
None =>
module_exports[MODULE_NAMESPACE_OBJECT_KEY] = make_module_namespace_cache(
ns_obj,
)
}
ns_obj
}
///|
fn module_namespace_export_descriptor() -> PropDescriptor {
{
writable: true,
enumerable: true,
configurable: false,
getter: None,
setter: None,
is_accessor: false,
}
}
///|
fn module_namespace_own_string_keys(data : ObjectData) -> Array[String] {
let keys : Array[String] = []
data.bag.properties.each(fn(k, _) { keys.push(k) })
keys.sort_by(fn(a, b) { a.lexical_compare(b) })
keys
}
///|
fn Interpreter::module_namespace_get_own_property_pair(
self : Interpreter,
data : ObjectData,
prop_key : Value,
) -> (PropDescriptor, Value)? raise Error {
match prop_key {
String_(key) =>
if data.bag.properties.contains(key) {
Some(
(
module_namespace_export_descriptor(),
self.get_property(Object(data), key, @token.Loc::default()),
),
)
} else {
None
}
_ => ordinary_get_own_property_pair(Object(data), prop_key)
}
}
///|
fn Interpreter::module_namespace_define_own_property(
self : Interpreter,
data : ObjectData,
prop_key : Value,
partial : PartialDescriptor,
) -> Bool? raise Error {
match prop_key {
String_(key) => {
if !data.bag.properties.contains(key) {
return Some(false)
}
match partial.configurable {
Some(true) => return Some(false)
_ => ()
}
match partial.enumerable {
Some(false) => return Some(false)
_ => ()
}
match partial.writable {
Some(false) => return Some(false)
_ => ()
}
if partial.is_accessor() {
return Some(false)
}
match partial.value {
Some(value) => {
let current = self.get_property(
Object(data),
key,
@token.Loc::default(),
)
Some(same_value(current, value))
}
None => Some(true)
}
}
_ => None
}
}
///|
priv struct ModuleRunRecord {
specifier : String
stmts : Array[@ast.Stmt]
exports : Map[String, Value]
env : Environment
}