///|
pub(all) struct FuncData {
name : String?
params : Array[String]
body : Array[@ast.Stmt]
closure : Environment
strict : Bool
// True only for named FunctionExpression per §15.2.5: such functions
// get a dedicated funcEnv (between outer env and call-time param env)
// holding an immutable self-name binding. Methods, class methods,
// function declarations, and anonymous functions do NOT get this
// binding — `name` is used only for `.name` display in those cases.
has_name_binding : Bool
// True for method-shorthand definitions in object literals
// (e.g. `{ m() {} }`). Per ES §15.4.5 MethodDefinitionEvaluation,
// such functions have no [[Construct]] internal method and must throw
// TypeError when called via `new`. Class methods use a separate path.
is_method : Bool
source_text : String?
}
///|
pub(all) struct FuncDataExt {
name : String?
params : Array[@ast.Param]
rest_param : String?
body : Array[@ast.Stmt]
closure : Environment
strict : Bool
has_name_binding : Bool
// True for method-shorthand definitions in object literals
// (e.g. `{ m() {} }`). Per ES §15.4.5 MethodDefinitionEvaluation,
// such functions have no [[Construct]] internal method and must throw
// TypeError when called via `new`. Class methods use a separate path.
is_method : Bool
source_text : String?
}
///|
pub(all) enum CallContext {
Call
Construct
ConstructWithTarget(Value)
}
///|
pub fn CallContext::is_constructing(self : CallContext) -> Bool {
match self {
Call => false
Construct | ConstructWithTarget(_) => true
}
}
///|
pub fn CallContext::new_target(self : CallContext) -> Value? {
match self {
ConstructWithTarget(value) => Some(value)
_ => None
}
}
///|
pub(all) enum Callable {
UserFunc(FuncData)
ArrowFunc(FuncData)
UserFuncExt(FuncDataExt)
ArrowFuncExt(FuncDataExt)
NativeCallable(String, (Array[Value]) -> Value raise Error)
NativeCallableWithContext(
String,
(CallContext, Array[Value]) -> Value raise Error
)
NonConstructableCallable(String, (Array[Value]) -> Value raise Error) // like NativeCallable but throws on new
BoundFunc(Value, Value, Array[Value]) // (target, this_val, bound_args)
// .call proxy captures a target function. `Undefined` is reserved for the
// exact Function.prototype.call intrinsic; fallback proxies always capture
// a callable object.
FuncCallMethod(Value)
FuncApplyMethod(Value) // .apply proxy: captures target function
MethodCallable(String, (Value, Array[Value]) -> Value raise Error) // this-aware native method
InterpreterCallable(
String,
(Interpreter, Value, Array[Value]) -> Value raise Error
) // needs interpreter for callback invocation
InterpreterCallableWithContext(
String,
(Interpreter, CallContext, Value, Array[Value]) -> Value raise Error
)
ExecutorCallable(ExecutorCallableData)
NonConstructableInterpreterCallable(
String,
(Interpreter, Array[Value]) -> Value raise Error
) // like InterpreterCallable but throws TypeError on new
ConstructorOnlyCallable(
String,
(Interpreter, Array[Value]) -> Value raise Error
) // like InterpreterCallable but throws on call without new
ClassConstructor(ClassConstructorData)
}
///|
/// Named fields for a class constructor callable.
/// Replaces the former anonymous 6-tuple so call sites are self-documenting
/// and future fields (e.g. instance fields, private names) can be added
/// without positional reasoning.
///|
/// A single instance field initializer, captured at class definition time.
pub(all) struct ClassFieldInit {
key : Value // already-evaluated key (String_ for string keys, Symbol for symbol keys)
initializer : @ast.Expr? // None if no `= expr`; evaluated with `this` = new instance
closure : Environment // lexical closure for evaluating the initializer
}
///|
pub(all) struct ClassConstructorData {
name : String // class name
proto : Value // prototype object given to new instances
super_ctor : Value? // super constructor when class uses `extends`
ctor_fn : (Array[@ast.Param], String?, Array[@ast.Stmt])? // (params, rest_name, body); None = no explicit ctor
closure : Environment // lexical environment where the class was defined
super_proto : Value // super class's prototype (bound as [[SuperPrototype]])
instance_fields : Array[ClassFieldInit] // instance field initializers, run before ctor body
private_instance_fields : Array[ClassFieldInit] // private instance field initializers, run before ctor body
source_text : String?
private_brand : Value // Brand symbol for private field/method access checks
private_methods : Map[String, Value] // Private method values keyed by name string
}
///|
pub(all) struct PropDescriptor {
mut writable : Bool
mut enumerable : Bool
mut configurable : Bool
mut getter : Value? // Accessor descriptor: get function (None if absent or undefined)
mut setter : Value? // Accessor descriptor: set function (None if absent or undefined)
// True iff this was defined as an accessor (via get/set) even when both
// getter and setter are None (i.e., `{ get: undefined }`).
mut is_accessor : Bool
}
///|
pub(all) enum InternalSlotKey {
StringData
NumberData
BooleanData
SymbolData
PrimitiveValue
ArrayLength
TypedArrayName
ViewedArrayBuffer
ArrayBufferID
ByteOffset
ByteLength
ArrayBufferByteLength
DateValue
ExportName
NamespaceObject
ExportValue
SyncIterator
SyncNextMethod
SourceText
PrivateBrandStore
} derive(Eq, Hash, Debug)
///|
/// Unified named/symbol property + descriptor storage embedded in every
/// exotic Value variant. Consolidates what used to be four parallel fields
/// so descriptor invariants are enforced in one place.
pub(all) struct PropertyBag {
properties : Map[String, Value] // User-visible string-keyed properties only
symbol_properties : Map[Int, Value] // Symbol-keyed properties (key is symbol ID)
descriptors : Map[String, PropDescriptor]
symbol_descriptors : Map[Int, PropDescriptor] // Descriptors for symbol properties
internal_slots : Map[InternalSlotKey, Value] // Engine-internal slots, invisible to enumeration
host_slots : Map[Int, Value] // Embedder slots; invisible to JS enumeration
}
///|
/// Construct an empty PropertyBag.
pub fn PropertyBag::PropertyBag() -> PropertyBag {
{
properties: Map([]),
symbol_properties: Map([]),
descriptors: Map([]),
symbol_descriptors: Map([]),
internal_slots: Map([]),
host_slots: Map([]),
}
}
///|
pub(all) struct ArrayBufferState {
id_counter : Ref[Int]
store : Map[Int, Array[Int]]
detached : Map[Int, Bool]
}
///|
pub fn ArrayBufferState::ArrayBufferState() -> ArrayBufferState {
{ id_counter: { val: 0 }, store: Map([]), detached: Map([]) }
}
///|
pub(all) struct ObjectData {
bag : PropertyBag
mut prototype : Value // Mutable to support Object.setPrototypeOf()
callable : Callable?
class_name : String
mut extensible : Bool
arraybuffer_state : ArrayBufferState?
}
///|
pub(all) struct ArrayData {
elements : Array[Value]
bag : PropertyBag
// §10.4.2.4: tracks whether Array `length` is writable. Starts `true`;
// set to `false` by Object.defineProperty(arr, "length", {writable: false}).
mut length_writable : Bool
// Tracks deleted (hole) indices so ordinary_get_own_property and
// has_array_property correctly report them absent after `delete arr[i]`.
holes : Map[Int, Unit]
// §10.4.2 [[Extensible]]: false after Object.preventExtensions/seal/freeze.
mut extensible : Bool
}
///|
/// Partial property descriptor for VAP (§10.1.6.3 `ValidateAndApplyPropertyDescriptor`)
/// inputs. Each field is independently absent (`None`) or present (`Some(_)`),
/// distinct from the stored `PropDescriptor` where every attribute has a
/// concrete value. `has_getter` / `has_setter` disambiguate "field absent"
/// from "getter: undefined" since a user can write `{ get: undefined }`.
pub(all) struct PartialDescriptor {
value : Value? // None = absent; Some(v) = present (v may be Undefined)
writable : Bool?
enumerable : Bool?
configurable : Bool?
getter : Value? // valid only when has_getter == true
setter : Value?
has_getter : Bool
has_setter : Bool
}
///|
/// Build a PartialDescriptor representing the "no attributes specified" case.
pub fn PartialDescriptor::empty() -> PartialDescriptor {
{
value: None,
writable: None,
enumerable: None,
configurable: None,
getter: None,
setter: None,
has_getter: false,
has_setter: false,
}
}
///|
/// Build a PartialDescriptor representing ES §7.3.5 `CreateDataPropertyOrThrow`
/// — every data attribute explicit with defaults `writable/enumerable/configurable = true`.
/// Used by the `[[Set]]` landing rule §10.1.9.2 step 3.f.
pub fn PartialDescriptor::data_default(v : Value) -> PartialDescriptor {
{
value: Some(v),
writable: Some(true),
enumerable: Some(true),
configurable: Some(true),
getter: None,
setter: None,
has_getter: false,
has_setter: false,
}
}
///|
/// Build a PartialDescriptor with only `value` set. Used by the `[[Set]]`
/// landing rule §10.1.9.2 step 3.e (existing writable-data descriptor: call
/// `[[DefineOwnProperty]]` with just { [[Value]]: V }).
pub fn PartialDescriptor::value_only(v : Value) -> PartialDescriptor {
{
value: Some(v),
writable: None,
enumerable: None,
configurable: None,
getter: None,
setter: None,
has_getter: false,
has_setter: false,
}
}
///|
/// ES §6.2.5.4 `IsAccessorDescriptor`: true iff either getter or setter is
/// explicitly present on the partial.
pub fn PartialDescriptor::is_accessor(self : PartialDescriptor) -> Bool {
self.has_getter || self.has_setter
}
///|
/// ES §6.2.5.4 `IsDataDescriptor`: true iff either value or writable is
/// explicitly present on the partial.
pub fn PartialDescriptor::is_data(self : PartialDescriptor) -> Bool {
self.value is Some(_) || self.writable is Some(_)
}
///|
/// ES §6.2.5.4 `IsGenericDescriptor`: neither data nor accessor — only
/// enumerable/configurable populated, or nothing at all.
pub fn PartialDescriptor::is_generic(self : PartialDescriptor) -> Bool {
!self.is_data() && !self.is_accessor()
}
///|
/// Symbol data structure - each symbol has a unique ID and optional description
pub(all) struct SymbolData {
id : Int // Unique identifier for this symbol
description : String? // Optional description (the argument to Symbol())
}
///|
/// Map data structure - stores key-value pairs with insertion order preservation
/// Uses SameValueZero for key comparison (NaN === NaN, +0 === -0)
pub(all) struct MapData {
entries : Array[(Value, Value)] // Array of (key, value) pairs
entry_ids : Array[Int] // Stable record identities for live-iteration semantics
mut next_entry_id : Int
// None = use realm's Map.prototype; Some(Null) = explicit null; Some(v) = override
mut prototype : Value?
// Expando properties: stores instance fields from subclasses (class D extends Map)
bag : PropertyBag
mut extensible : Bool
}
///|
/// Construct MapData with entries and an empty property bag (no expando properties).
pub fn MapData::MapData(
entries : Array[(Value, Value)],
prototype? : Value? = None,
) -> MapData {
let entry_ids : Array[Int] = []
for i in 0..0
// to avoid compacting while the forEach cursor is still walking.
mut iteration_depth : Int
// None = use realm's Set.prototype; Some(Null) = explicit null; Some(v) = override
mut prototype : Value?
// Expando properties: stores instance fields from subclasses (class D extends Set)
bag : PropertyBag
mut extensible : Bool
}
///|
/// Construct SetData with values and an empty property bag (no expando properties).
pub fn SetData::SetData(
values : Array[Value],
prototype? : Value? = None,
) -> SetData {
{
values,
tombstones: None,
iteration_depth: 0,
prototype,
bag: PropertyBag(),
extensible: true,
}
}
///|
/// Live element count — values.length() minus tombstoned slots.
pub fn SetData::effective_size(self : SetData) -> Int {
let ts_count = match self.tombstones {
None => 0
Some(ts) => ts.length()
}
self.values.length() - ts_count
}
///|
/// Promise state per ECMAScript spec
pub(all) enum PromiseState {
Pending
Fulfilled
Rejected
}
///|
/// Promise reaction record - stores callbacks for promise resolution
/// Each reaction contains the handler (onFulfilled or onRejected) and the
/// dependent promise's resolve/reject capabilities
pub(all) struct PromiseReaction {
handler : Value? // The callback function (None means identity/thrower)
resolve : Value // Resolve function for the dependent promise
reject : Value // Reject function for the dependent promise
reaction_type : PromiseReactionType // Fulfill or Reject
}
///|
pub(all) enum PromiseReactionType {
Fulfill
Reject
}
///|
/// Promise data structure per ECMAScript spec
/// Promises have a state, result value, and queues of pending reactions
pub(all) struct PromiseData {
mut state : PromiseState
mut result : Value // undefined when pending, result when settled
fulfill_reactions : Array[PromiseReaction] // Called when fulfilled
reject_reactions : Array[PromiseReaction] // Called when rejected
mut is_handled : Bool // Whether .catch or second arg to .then was provided
bag : PropertyBag
mut extensible : Bool
// None = use realm's Promise.prototype; Some(Null) = explicit null; Some(v) = override
mut prototype : Value?
}
///|
/// Create a new pending promise data structure
pub fn new_promise_data() -> PromiseData {
{
state: Pending,
result: Undefined,
fulfill_reactions: [],
reject_reactions: [],
is_handled: false,
bag: PropertyBag(),
extensible: true,
prototype: None,
}
}
///|
/// Proxy data structure - wraps a target and handler for meta-programming
pub(all) struct ProxyData {
mut target : Value? // None when revoked
mut handler : Value? // None when revoked
is_callable : Bool // Set at creation time, persists after revocation
is_constructor : Bool // Set at creation time, persists after revocation
}
///|
pub(all) enum Value {
Number(Double)
String_(String)
Bool(Bool)
Null
Undefined
Object(ObjectData)
Array(ArrayData)
Symbol(SymbolData)
Map(MapData)
Set(SetData)
Promise(PromiseData)
Proxy(ProxyData)
}
///|
// Engine-private negative symbol IDs are reserved in docs/development.md.
// -1 and -2 are Array exotic override slots stored in PropertyBag symbol maps;
// they must not collide with function realm metadata or traversal markers.
const ARRAY_LENGTH_OVERRIDE_SYMBOL_ID = -1
///|
const ARRAY_PROTOTYPE_OVERRIDE_SYMBOL_ID = -2
///|
fn default_data_descriptor() -> PropDescriptor {
{
writable: true,
enumerable: true,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
}
///|
/// Store sparse Array length state in the ArrayData PropertyBag. This keeps
/// Array exotic state attached to the array object instead of a module-level
/// identity side table, using a non-forgeable internal symbol id so ordinary
/// string-keyed property lookup cannot observe it.
pub fn set_array_length_override(arr : ArrayData, len : Int64) -> Unit {
arr.bag.symbol_properties[ARRAY_LENGTH_OVERRIDE_SYMBOL_ID] = Number(
len.to_double(),
)
}
///|
pub fn get_array_length_override(arr : ArrayData) -> Int64? {
match arr.bag.symbol_properties.get(ARRAY_LENGTH_OVERRIDE_SYMBOL_ID) {
Some(Number(n)) => Some(n.to_int64())
_ => None
}
}
///|
pub fn clear_array_length_override(arr : ArrayData) -> Unit {
let _ = arr.bag.symbol_properties.remove(ARRAY_LENGTH_OVERRIDE_SYMBOL_ID)
let _ = arr.bag.symbol_descriptors.remove(ARRAY_LENGTH_OVERRIDE_SYMBOL_ID)
}
///|
pub fn set_array_prototype_override(arr : ArrayData, proto : Value) -> Unit {
arr.bag.symbol_properties[ARRAY_PROTOTYPE_OVERRIDE_SYMBOL_ID] = proto
}
///|
pub fn get_array_prototype_override(arr : ArrayData) -> Value? {
arr.bag.symbol_properties.get(ARRAY_PROTOTYPE_OVERRIDE_SYMBOL_ID)
}
///|
pub fn set_array_named_prop(
arr : ArrayData,
key : String,
value : Value,
) -> Unit {
arr.bag.properties[key] = value
if !arr.bag.descriptors.contains(key) {
arr.bag.descriptors[key] = default_data_descriptor()
}
}
///|
pub fn get_array_named_prop(arr : ArrayData, key : String) -> Value? {
arr.bag.properties.get(key)
}
///|
pub fn set_array_symbol_prop(
arr : ArrayData,
sym_id : Int,
value : Value,
) -> Unit {
arr.bag.symbol_properties[sym_id] = value
if !arr.bag.symbol_descriptors.contains(sym_id) {
arr.bag.symbol_descriptors[sym_id] = default_data_descriptor()
}
}
///|
pub fn get_array_symbol_prop(arr : ArrayData, sym_id : Int) -> Value? {
arr.bag.symbol_properties.get(sym_id)
}
///|
pub fn set_array_iterator_override(
arr : ArrayData,
well_known_symbols~ : WellKnownSymbols,
getter : Value?,
value : Value?,
) -> Unit {
let iterator_sym = well_known_symbols.iterator
match value {
Some(v) => arr.bag.symbol_properties[iterator_sym.id] = v
None => {
let _ = arr.bag.symbol_properties.remove(iterator_sym.id)
}
}
arr.bag.symbol_descriptors[iterator_sym.id] = {
writable: true,
enumerable: false,
configurable: true,
getter,
setter: None,
is_accessor: getter is Some(_),
}
}
///|
pub fn get_array_iterator_override(
arr : ArrayData,
well_known_symbols~ : WellKnownSymbols,
) -> (Value?, Value?) {
let iterator_sym = well_known_symbols.iterator
let getter = match arr.bag.symbol_descriptors.get(iterator_sym.id) {
Some(desc) => desc.getter
None => None
}
let value = arr.bag.symbol_properties.get(iterator_sym.id)
(getter, value)
}
///|
pub suberror JsException {
JsException(Value)
}
///|
pub impl Show for Value with fn output(self, logger) {
match self {
Number(n) => {
// Format integers without decimal point
let i = n.to_int()
if i.to_double() == n && !n.is_inf() && !n.is_nan() {
logger.write_string(i.to_string())
} else {
logger.write_string(n.to_string())
}
}
String_(s) => logger.write_string(s)
Bool(b) => logger.write_string(b.to_string())
Null => logger.write_string("null")
Undefined => logger.write_string("undefined")
Object(data) =>
match data.callable {
Some(UserFunc(func)) =>
match func.name {
Some(n) => logger.write_string("function \{n}() { [code] }")
None => logger.write_string("function() { [code] }")
}
Some(ArrowFunc(_)) | Some(ArrowFuncExt(_)) =>
logger.write_string("() => { [code] }")
Some(UserFuncExt(func)) =>
match func.name {
Some(n) => logger.write_string("function \{n}() { [code] }")
None => logger.write_string("function() { [code] }")
}
Some(BoundFunc(_, _, _)) =>
logger.write_string("function bound() { [native code] }")
Some(NativeCallable(name, _))
| Some(NativeCallableWithContext(name, _)) =>
logger.write_string("function \{name}() { [native code] }")
Some(NonConstructableCallable(name, _)) =>
logger.write_string("function \{name}() { [native code] }")
Some(FuncCallMethod(_)) =>
logger.write_string("function call() { [native code] }")
Some(FuncApplyMethod(_)) =>
logger.write_string("function apply() { [native code] }")
Some(MethodCallable(name, _)) =>
logger.write_string("function \{name}() { [native code] }")
Some(InterpreterCallable(name, _))
| Some(InterpreterCallableWithContext(name, _)) =>
logger.write_string("function \{name}() { [native code] }")
Some(ExecutorCallable(executable)) =>
logger.write_string(
"function \{executable.name()}() { [native code] }",
)
Some(NonConstructableInterpreterCallable(name, _)) =>
logger.write_string("function \{name}() { [native code] }")
Some(ConstructorOnlyCallable(name, _)) =>
logger.write_string("function \{name}() { [native code] }")
Some(ClassConstructor({ name, .. })) =>
logger.write_string("class \{name} { [code] }")
None =>
// Boxed primitive objects: unwrap to primitive string representation
if data.class_name == "String" {
match data.bag.internal_slots.get(StringData) {
Some(String_(s)) => logger.write_string(s)
_ => logger.write_string("[object String]")
}
} else if data.class_name == "Number" {
match data.bag.internal_slots.get(NumberData) {
Some(n) => n.output(logger)
_ => logger.write_string("[object Number]")
}
} else if data.class_name == "Boolean" {
match data.bag.internal_slots.get(BooleanData) {
Some(Bool(b)) => logger.write_string(b.to_string())
_ => logger.write_string("[object Boolean]")
}
} else if data.class_name.has_suffix("Error") {
let name = match data.bag.properties.get("name") {
Some(String_(s)) => s
_ => data.class_name
}
let msg = match data.bag.properties.get("message") {
Some(String_(s)) => s
_ => ""
}
if msg == "" {
logger.write_string(name)
} else {
logger.write_string(name + ": " + msg)
}
} else {
logger.write_string("[object \{data.class_name}]")
}
}
Array(data) =>
logger.write_string(
data.elements
.map(fn(v) {
match v {
Undefined | Null => ""
_ => v.to_string()
}
})
.join(","),
)
Symbol(sym) =>
match sym.description {
Some(desc) => logger.write_string("Symbol(\{desc})")
None => logger.write_string("Symbol()")
}
Map(_) => logger.write_string("[object Map]")
Set(_) => logger.write_string("[object Set]")
Promise(_) => logger.write_string("[object Promise]")
Proxy(proxy_data) =>
if proxy_data.is_callable {
logger.write_string("function proxy() { [native code] }")
} else {
logger.write_string("[object Object]")
}
}
}