///|
pub fn setup_object_builtins(env : Environment) -> Unit {
// Object.keys, Object.values, Object.entries, Object.create, Object.assign,
// Object.getPrototypeOf, Object.getOwnPropertyNames
let obj_proto : Value = Object({
properties: {
"toString": Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("toString", fn(this_val, _args) {
match this_val {
Undefined => String_("[object Undefined]")
Null => String_("[object Null]")
Bool(_) => String_("[object Boolean]")
Number(_) => String_("[object Number]")
String_(_) => String_("[object String]")
Array(_) => String_("[object Array]")
Symbol(_) => String_("[object Symbol]")
Map(_) => String_("[object Map]")
Set(_) => String_("[object Set]")
Promise(_) => String_("[object Promise]")
Object(data) =>
if data.class_name == "Function" {
String_("[object Function]")
} else if data.class_name == "RegExp" {
String_("[object RegExp]")
} else if data.class_name == "Error" ||
data.class_name == "TypeError" ||
data.class_name == "ReferenceError" ||
data.class_name == "SyntaxError" ||
data.class_name == "RangeError" ||
data.class_name == "URIError" ||
data.class_name == "EvalError" ||
data.class_name == "AggregateError" {
String_("[object Error]")
} else if data.class_name == "Date" {
String_("[object Date]")
} else if data.class_name == "Map" {
String_("[object Map]")
} else if data.class_name == "Set" {
String_("[object Set]")
} else if data.class_name == "Promise" {
String_("[object Promise]")
} else if data.class_name == "Arguments" {
String_("[object Arguments]")
} else if data.class_name == "Number" {
String_("[object Number]")
} else if data.class_name == "String" {
String_("[object String]")
} else if data.class_name == "Boolean" {
String_("[object Boolean]")
} else {
String_("[object Object]")
}
_ => String_("[object Object]")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
}),
"valueOf": Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("valueOf", fn(this_val, _args) { this_val }),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
}),
"hasOwnProperty": Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("hasOwnProperty", fn(this_val, args) {
let key_val = if args.length() > 0 { args[0] } else { Undefined }
match this_val {
Object(data) =>
match key_val {
Symbol(sym) => Bool(data.symbol_properties.contains(sym.id))
_ => Bool(data.properties.contains(key_val.to_string()))
}
Array(data) => {
let key = key_val.to_string()
if key == "length" {
Bool(true)
} else {
let idx = try { @strconv.parse_int(key) } catch { _ => -1 }
Bool(
idx >= 0 &&
idx.to_string() == key &&
idx < data.elements.length(),
)
}
}
String_(s) => {
let key = key_val.to_string()
if key == "length" {
Bool(true)
} else {
let idx = try { @strconv.parse_int(key) } catch { _ => -1 }
Bool(
idx >= 0 && idx.to_string() == key && idx < s.length(),
)
}
}
_ => Bool(false)
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
}),
"propertyIsEnumerable": Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("propertyIsEnumerable", fn(this_val, args) {
let key_val = if args.length() > 0 { args[0] } else { Undefined }
let key = key_val.to_string()
match this_val {
Object(data) =>
if data.properties.contains(key) {
match data.descriptors.get(key) {
Some(d) => Bool(d.enumerable)
None => Bool(true)
}
} else {
Bool(false)
}
Array(data) => {
if key == "length" {
Bool(false) // length is not enumerable
} else {
let idx = try { @strconv.parse_int(key) } catch { _ => -1 }
Bool(
idx >= 0 &&
idx.to_string() == key &&
idx < data.elements.length(),
)
}
}
String_(s) => {
if key == "length" {
Bool(false)
} else {
let idx = try { @strconv.parse_int(key) } catch { _ => -1 }
Bool(
idx >= 0 && idx.to_string() == key && idx < s.length(),
)
}
}
_ => Bool(false)
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
}),
"isPrototypeOf": Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("isPrototypeOf", fn(this_val, args) {
// Walk the argument's prototype chain looking for this_val
let arg = if args.length() > 0 { args[0] } else { Undefined }
let mut current : Value = match arg {
Object(data) => data.prototype
_ => return Bool(false)
}
while true {
match current {
Null | Undefined => return Bool(false)
Object(proto_data) => {
// Compare by reference identity - check if this_val is the same object
match this_val {
Object(this_data) =>
if physical_equal(this_data, proto_data) {
return Bool(true)
}
_ => ()
}
current = proto_data.prototype
}
_ => return Bool(false)
}
}
Bool(false)
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
}),
},
symbol_properties: {},
prototype: Null,
callable: None,
class_name: "Object",
descriptors: {
"toString": { writable: true, enumerable: false, configurable: true },
"valueOf": { writable: true, enumerable: false, configurable: true },
"hasOwnProperty": { writable: true, enumerable: false, configurable: true },
"propertyIsEnumerable": { writable: true, enumerable: false, configurable: true },
"isPrototypeOf": { writable: true, enumerable: false, configurable: true },
},
symbol_descriptors: {},
extensible: true,
})
// Store Object.prototype so plain objects {} can link to it
env.def_builtin("[[ObjectPrototype]]", obj_proto)
env.def_builtin(
"Object",
Object({
properties: {
"prototype": obj_proto,
"keys": make_native_func("keys", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
let keys : Array[Value] = []
data.properties.each(fn(k, _v) {
let enumerable = match data.descriptors.get(k) {
Some(d) => d.enumerable
None => true
}
if enumerable {
keys.push(String_(k))
}
})
Array({ elements: keys })
}
String_(s) => {
// String objects have enumerable indexed properties
let keys : Array[Value] = []
let len = s.length()
for i = 0; i < len; i = i + 1 {
keys.push(String_(i.to_string()))
}
Array({ elements: keys })
}
Array(data) => {
// Array objects have enumerable indexed properties
let keys : Array[Value] = []
for i = 0; i < data.elements.length(); i = i + 1 {
keys.push(String_(i.to_string()))
}
Array({ elements: keys })
}
_ => Array({ elements: [] })
}
}),
"values": make_native_func("values", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
let vals : Array[Value] = []
data.properties.each(fn(k, v) {
let enumerable = match data.descriptors.get(k) {
Some(d) => d.enumerable
None => true
}
if enumerable {
vals.push(v)
}
})
Array({ elements: vals })
}
String_(s) => {
// String objects have enumerable indexed properties
let vals : Array[Value] = []
let chars = s.to_array()
for ch in chars {
vals.push(String_(ch.to_string()))
}
Array({ elements: vals })
}
Array(data) => {
// Array objects - return array elements (defensive copy)
let vals : Array[Value] = []
for elem in data.elements {
vals.push(elem)
}
Array({ elements: vals })
}
_ => Array({ elements: [] })
}
}),
"entries": make_native_func("entries", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
let entries : Array[Value] = []
data.properties.each(fn(k, v) {
let enumerable = match data.descriptors.get(k) {
Some(d) => d.enumerable
None => true
}
if enumerable {
entries.push(Array({ elements: [String_(k), v] }))
}
})
Array({ elements: entries })
}
String_(s) => {
// String objects have enumerable indexed properties
let entries : Array[Value] = []
let chars = s.to_array()
for i = 0; i < chars.length(); i = i + 1 {
entries.push(
Array({
elements: [
String_(i.to_string()),
String_(chars[i].to_string()),
],
}),
)
}
Array({ elements: entries })
}
Array(data) => {
// Array objects - keys must be strings per spec
let entries : Array[Value] = []
for i = 0; i < data.elements.length(); i = i + 1 {
entries.push(
Array({
elements: [String_(i.to_string()), data.elements[i]],
}),
)
}
Array({ elements: entries })
}
_ => Array({ elements: [] })
}
}),
"create": make_native_func("create", fn(args) {
let proto = if args.length() > 0 { args[0] } else { Null }
let obj = Object({
properties: {},
symbol_properties: {},
prototype: proto,
callable: None,
class_name: "Object",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
// Handle optional second argument (property descriptors)
if args.length() > 1 {
match args[1] {
Undefined => ()
// Per ECMAScript spec, only Object with properties field accepted
// Arrays, Maps, Sets don't have properties field in our implementation
Null | String_(_) | Number(_) | Bool(_) | Symbol(_) | Array(_)
| Map(_) | Set(_) =>
raise @errors.TypeError(
message="Property descriptors must be an object",
)
Object(desc_obj) =>
// Objects have properties field
match obj {
Object(data) =>
desc_obj.properties.each(fn(prop_name, desc_val) {
match desc_val {
Object(desc) => {
match desc.properties.get("value") {
Some(v) => data.properties[prop_name] = v
None =>
if not(data.properties.contains(prop_name)) {
data.properties[prop_name] = Undefined
}
}
let writable = match desc.properties.get("writable") {
Some(v) => is_truthy(v)
_ => false
}
let enumerable = match desc.properties.get("enumerable") {
Some(v) => is_truthy(v)
_ => false
}
let configurable = match
desc.properties.get("configurable") {
Some(v) => is_truthy(v)
_ => false
}
data.descriptors[prop_name] = {
writable,
enumerable,
configurable,
}
}
_ => ()
}
})
_ => ()
}
Promise(desc_obj) =>
// Promises also have properties field
match obj {
Object(data) =>
desc_obj.properties.each(fn(prop_name, desc_val) {
match desc_val {
Object(desc) => {
match desc.properties.get("value") {
Some(v) => data.properties[prop_name] = v
None =>
if not(data.properties.contains(prop_name)) {
data.properties[prop_name] = Undefined
}
}
let writable = match desc.properties.get("writable") {
Some(v) => is_truthy(v)
_ => false
}
let enumerable = match desc.properties.get("enumerable") {
Some(v) => is_truthy(v)
_ => false
}
let configurable = match
desc.properties.get("configurable") {
Some(v) => is_truthy(v)
_ => false
}
data.descriptors[prop_name] = {
writable,
enumerable,
configurable,
}
}
_ => ()
}
})
_ => ()
}
}
}
obj
}),
"assign": make_native_func("assign", fn(args) {
let target = if args.length() > 0 { args[0] } else { Undefined }
match target {
Object(data) => {
for i = 1; i < args.length(); i = i + 1 {
match args[i] {
// ToObject conversion: skip null and undefined
Null | Undefined => ()
// ToObject conversion: String primitives enumerate characters
String_(s) => {
let chars = s.to_array()
for idx = 0; idx < chars.length(); idx = idx + 1 {
let k = idx.to_string()
let v = String_(chars[idx].to_string())
// Check writable on target property
let can_write = match data.descriptors.get(k) {
Some(d) => d.writable
None => true
}
let exists = data.properties.contains(k)
if not(can_write) {
raise @errors.TypeError(
message="Cannot assign to read-only property '\{k}'",
)
}
if not(exists) && not(data.extensible) {
raise @errors.TypeError(
message="Cannot add property '\{k}' to non-extensible object",
)
}
data.properties[k] = v
}
}
// ToObject conversion: Arrays enumerate elements as indexed properties
Array(src) => {
// Copy array elements as indexed properties
for idx = 0; idx < src.elements.length(); idx = idx + 1 {
let k = idx.to_string()
let v = src.elements[idx]
let can_write = match data.descriptors.get(k) {
Some(d) => d.writable
None => true
}
let exists = data.properties.contains(k)
if not(can_write) {
raise @errors.TypeError(
message="Cannot assign to read-only property '\{k}'",
)
}
if not(exists) && not(data.extensible) {
raise @errors.TypeError(
message="Cannot add property '\{k}' to non-extensible object",
)
}
data.properties[k] = v
}
}
// Regular objects have properties and descriptors
Object(src) => {
// Copy enumerable string properties
src.properties.each(fn(k, v) {
let enumerable = match src.descriptors.get(k) {
Some(d) => d.enumerable
None => true
}
if enumerable {
// Check writable on target property
let can_write = match data.descriptors.get(k) {
Some(d) => d.writable
None => true
}
// Check extensibility if property doesn't exist
let exists = data.properties.contains(k)
// Per ECMAScript spec, Object.assign throws TypeError on Set failure
if not(can_write) {
raise @errors.TypeError(
message="Cannot assign to read-only property '\{k}'",
)
}
if not(exists) && not(data.extensible) {
raise @errors.TypeError(
message="Cannot add property '\{k}' to non-extensible object",
)
}
data.properties[k] = v
}
})
// Copy enumerable symbol properties
src.symbol_properties.each(fn(sym_id, v) {
let enumerable = match src.symbol_descriptors.get(sym_id) {
Some(d) => d.enumerable
None => true
}
if enumerable {
// Check writable on target property
let can_write = match data.symbol_descriptors.get(sym_id) {
Some(d) => d.writable
None => true
}
// Check extensibility if property doesn't exist
let exists = data.symbol_properties.contains(sym_id)
// Per ECMAScript spec, Object.assign throws TypeError on Set failure
if not(can_write) {
raise @errors.TypeError(
message="Cannot assign to read-only symbol property",
)
}
if not(exists) && not(data.extensible) {
raise @errors.TypeError(
message="Cannot add symbol property to non-extensible object",
)
}
data.symbol_properties[sym_id] = v
}
})
}
// Promises have properties but no descriptors
Promise(src) => {
// Copy all string properties (no descriptors to check)
src.properties.each(fn(k, v) {
let can_write = match data.descriptors.get(k) {
Some(d) => d.writable
None => true
}
let exists = data.properties.contains(k)
if not(can_write) {
raise @errors.TypeError(
message="Cannot assign to read-only property '\{k}'",
)
}
if not(exists) && not(data.extensible) {
raise @errors.TypeError(
message="Cannot add property '\{k}' to non-extensible object",
)
}
data.properties[k] = v
})
// Copy symbol properties
src.symbol_properties.each(fn(sym_id, v) {
let can_write = match data.symbol_descriptors.get(sym_id) {
Some(d) => d.writable
None => true
}
let exists = data.symbol_properties.contains(sym_id)
if not(can_write) {
raise @errors.TypeError(
message="Cannot assign to read-only symbol property",
)
}
if not(exists) && not(data.extensible) {
raise @errors.TypeError(
message="Cannot add symbol property to non-extensible object",
)
}
data.symbol_properties[sym_id] = v
})
}
// ToObject conversion: Map, Set, Number, Bool, Symbol have no enumerable properties
Map(_) | Set(_) | Number(_) | Bool(_) | Symbol(_) => ()
}
}
target
}
_ => target
}
}),
"getPrototypeOf": make_native_func("getPrototypeOf", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => data.prototype
_ => Null
}
}),
"getOwnPropertyNames": make_native_func("getOwnPropertyNames", fn(
args,
) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
let keys : Array[Value] = []
data.properties.each(fn(k, _v) { keys.push(String_(k)) })
Array({ elements: keys })
}
Array(data) => {
let keys : Array[Value] = []
for i = 0; i < data.elements.length(); i = i + 1 {
keys.push(String_(i.to_string()))
}
keys.push(String_("length"))
Array({ elements: keys })
}
String_(s) => {
let keys : Array[Value] = []
let chars = s.to_array()
for i = 0; i < chars.length(); i = i + 1 {
keys.push(String_(i.to_string()))
}
keys.push(String_("length"))
Array({ elements: keys })
}
_ => Array({ elements: [] })
}
}),
"getOwnPropertySymbols": make_native_func("getOwnPropertySymbols", fn(
args,
) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
let symbols : Array[Value] = []
data.symbol_properties.each(fn(sym_id, _val) {
match get_symbol_by_id(sym_id) {
Some(sym_data) => symbols.push(Symbol(sym_data))
None => () // Symbol no longer exists (shouldn't happen)
}
})
Array({ elements: symbols })
}
_ => Array({ elements: [] })
}
}),
"getOwnPropertyDescriptors": make_native_func(
"getOwnPropertyDescriptors",
fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
let descriptors_props : Map[String, Value] = {}
data.properties.each(fn(key, val) {
let desc = data.descriptors.get(key)
let writable = match desc {
Some(d) => d.writable
None => true
}
let enumerable = match desc {
Some(d) => d.enumerable
None => true
}
let configurable = match desc {
Some(d) => d.configurable
None => true
}
descriptors_props[key] = Object({
properties: {
"value": val,
"writable": Bool(writable),
"enumerable": Bool(enumerable),
"configurable": Bool(configurable),
},
symbol_properties: {},
prototype: Null,
callable: None,
class_name: "Object",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
})
Object({
properties: descriptors_props,
symbol_properties: {},
prototype: Null,
callable: None,
class_name: "Object",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
}
_ =>
Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: None,
class_name: "Object",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
}
},
),
"defineProperty": make_native_func("defineProperty", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
let prop_name = if args.length() > 1 {
args[1].to_string()
} else {
""
}
let descriptor = if args.length() > 2 { args[2] } else { Undefined }
match obj {
Object(data) =>
match descriptor {
Object(desc) => {
// Check if property exists
let prop_exists = data.properties.contains(prop_name)
// Build new descriptor flags from input (using is_truthy for coercion)
let existing_desc = data.descriptors.get(prop_name)
let new_writable = match desc.properties.get("writable") {
Some(v) => Some(is_truthy(v))
None => None
}
let new_enumerable = match desc.properties.get("enumerable") {
Some(v) => Some(is_truthy(v))
None => None
}
let new_configurable = match desc.properties.get("configurable") {
Some(v) => Some(is_truthy(v))
None => None
}
// Check if configurable on existing descriptor
match existing_desc {
Some(existing) => {
if not(existing.configurable) {
// Non-configurable property: only allow if new descriptor matches existing
let writable_matches = match new_writable {
Some(w) => w == existing.writable
None => true // Not specified, no conflict
}
let enumerable_matches = match new_enumerable {
Some(e) => e == existing.enumerable
None => true
}
let configurable_matches = match new_configurable {
Some(c) => c == existing.configurable
None => true
}
if not(writable_matches && enumerable_matches && configurable_matches) {
raise @errors.TypeError(
message="Cannot redefine non-configurable property: \{prop_name}",
)
}
}
}
None => {
// Check extensibility when adding new property
if not(prop_exists) && not(data.extensible) {
raise @errors.TypeError(
message="Cannot add property \{prop_name}, object is not extensible",
)
}
}
}
// Set value
match desc.properties.get("value") {
Some(v) => data.properties[prop_name] = v
None =>
if not(data.properties.contains(prop_name)) {
data.properties[prop_name] = Undefined
}
}
// Build final descriptor using new values or existing defaults
let writable = match new_writable {
Some(w) => w
None =>
match existing_desc {
Some(d) => d.writable
None => false
}
}
let enumerable = match new_enumerable {
Some(e) => e
None =>
match existing_desc {
Some(d) => d.enumerable
None => false
}
}
let configurable = match new_configurable {
Some(c) => c
None =>
match existing_desc {
Some(d) => d.configurable
None => false
}
}
data.descriptors[prop_name] = {
writable,
enumerable,
configurable,
}
}
_ => ()
}
_ => ()
}
obj
}),
"getOwnPropertyDescriptor": make_native_func(
"getOwnPropertyDescriptor",
fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
let prop_name = if args.length() > 1 {
args[1].to_string()
} else {
""
}
fn make_desc(
val : Value,
writable : Bool,
enumerable : Bool,
configurable : Bool
) -> Value {
Object({
properties: {
"value": val,
"writable": Bool(writable),
"enumerable": Bool(enumerable),
"configurable": Bool(configurable),
},
symbol_properties: {},
prototype: Null,
callable: None,
class_name: "Object",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
}
match obj {
Object(data) =>
match data.properties.get(prop_name) {
Some(val) => {
let desc = data.descriptors.get(prop_name)
let writable = match desc {
Some(d) => d.writable
None => true
}
let enumerable = match desc {
Some(d) => d.enumerable
None => true
}
let configurable = match desc {
Some(d) => d.configurable
None => true
}
make_desc(val, writable, enumerable, configurable)
}
None => Undefined
}
Array(data) =>
if prop_name == "length" {
make_desc(
Number(data.elements.length().to_double()),
true,
false,
false,
)
} else {
// Check indexed properties
let idx = try {
@strconv.parse_int(prop_name)
} catch {
_ => -1
}
if idx >= 0 &&
idx.to_string() == prop_name &&
idx < data.elements.length() {
make_desc(data.elements[idx], true, true, true)
} else {
Undefined
}
}
String_(s) =>
if prop_name == "length" {
make_desc(
Number(s.length().to_double()),
false,
false,
false,
)
} else {
let idx = try {
@strconv.parse_int(prop_name)
} catch {
_ => -1
}
if idx >= 0 &&
idx.to_string() == prop_name &&
idx < s.length() {
let chars = s.to_array()
let buf = StringBuilder::new()
buf.write_char(chars[idx])
make_desc(String_(buf.to_string()), false, true, false)
} else {
Undefined
}
}
_ => Undefined
}
},
),
"freeze": make_native_func("freeze", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
data.extensible = false
// Freeze string properties
let keys : Array[String] = []
data.properties.each(fn(k, _v) { keys.push(k) })
for key in keys {
data.descriptors[key] = {
writable: false,
enumerable: match data.descriptors.get(key) {
Some(d) => d.enumerable
None => true
},
configurable: false,
}
}
// Freeze symbol properties
let sym_ids : Array[Int] = []
data.symbol_properties.each(fn(id, _v) { sym_ids.push(id) })
for id in sym_ids {
data.symbol_descriptors[id] = {
writable: false,
enumerable: match data.symbol_descriptors.get(id) {
Some(d) => d.enumerable
None => true
},
configurable: false,
}
}
obj
}
_ => obj
}
}),
"seal": make_native_func("seal", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
data.extensible = false
// Seal string properties
let keys : Array[String] = []
data.properties.each(fn(k, _v) { keys.push(k) })
for key in keys {
data.descriptors[key] = {
writable: match data.descriptors.get(key) {
Some(d) => d.writable
None => true
},
enumerable: match data.descriptors.get(key) {
Some(d) => d.enumerable
None => true
},
configurable: false,
}
}
// Seal symbol properties
let sym_ids : Array[Int] = []
data.symbol_properties.each(fn(id, _v) { sym_ids.push(id) })
for id in sym_ids {
data.symbol_descriptors[id] = {
writable: match data.symbol_descriptors.get(id) {
Some(d) => d.writable
None => true
},
enumerable: match data.symbol_descriptors.get(id) {
Some(d) => d.enumerable
None => true
},
configurable: false,
}
}
obj
}
_ => obj
}
}),
"preventExtensions": make_native_func("preventExtensions", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
data.extensible = false
obj
}
_ => obj
}
}),
"isFrozen": make_native_func("isFrozen", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
if data.extensible {
return Bool(false)
}
let mut frozen = true
data.properties.each(fn(k, _v) {
match data.descriptors.get(k) {
Some(d) => if d.writable || d.configurable { frozen = false }
None => frozen = false // No descriptor = writable+configurable
}
})
Bool(frozen)
}
_ => Bool(true) // Non-objects are vacuously frozen
}
}),
"isSealed": make_native_func("isSealed", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => {
if data.extensible {
return Bool(false)
}
let mut is_sealed = true
data.properties.each(fn(k, _v) {
match data.descriptors.get(k) {
Some(d) => if d.configurable { is_sealed = false }
None => is_sealed = false
}
})
Bool(is_sealed)
}
_ => Bool(true)
}
}),
"isExtensible": make_native_func("isExtensible", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
match obj {
Object(data) => Bool(data.extensible)
_ => Bool(false)
}
}),
"is": make_native_func("is", fn(args) {
// Object.is() - SameValue algorithm
let x = if args.length() > 0 { args[0] } else { Undefined }
let y = if args.length() > 1 { args[1] } else { Undefined }
Bool(same_value(x, y))
}),
"fromEntries": make_native_func("fromEntries", fn(args) {
// Object.fromEntries() - create object from iterable of key-value pairs
let iterable = if args.length() > 0 { args[0] } else { Undefined }
let properties : Map[String, Value] = {}
match iterable {
Array(data) =>
for entry in data.elements {
match entry {
Array(pair) =>
if pair.elements.length() >= 2 {
let key = pair.elements[0].to_string()
let value = pair.elements[1]
properties[key] = value
} else {
raise @errors.TypeError(
message="Iterator value is not an entry object",
)
}
Object(obj) => {
// Object with numeric indices 0 and 1
let key = match obj.properties.get("0") {
Some(v) => v.to_string()
None =>
raise @errors.TypeError(
message="Iterator value is not an entry object",
)
}
let value = match obj.properties.get("1") {
Some(v) => v
None =>
raise @errors.TypeError(
message="Iterator value is not an entry object",
)
}
properties[key] = value
}
_ =>
raise @errors.TypeError(
message="Iterator value is not an entry object",
)
}
}
_ =>
raise @errors.TypeError(
message="Object.fromEntries requires an iterable argument",
)
}
Object({
properties,
symbol_properties: {},
prototype: Null,
callable: None,
class_name: "Object",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
}),
"hasOwn": make_native_func("hasOwn", fn(args) {
// Object.hasOwn() - modern hasOwnProperty replacement
let obj = if args.length() > 0 { args[0] } else { Undefined }
let key_val = if args.length() > 1 { args[1] } else { Undefined }
match obj {
Object(data) =>
// Handle Symbol keys
match key_val {
Symbol(sym) => Bool(data.symbol_properties.contains(sym.id))
_ => Bool(data.properties.contains(key_val.to_string()))
}
Array(data) => {
let key = key_val.to_string()
// Arrays support numeric index check
if key == "length" {
Bool(true)
} else {
let idx = @strconv.parse_int(key) catch { _ => -1 }
Bool(
idx >= 0 &&
idx.to_string() == key &&
idx < data.elements.length(),
)
}
}
_ => Bool(false)
}
}),
"setPrototypeOf": make_native_func("setPrototypeOf", fn(args) {
// Object.setPrototypeOf(obj, prototype)
// Sets the prototype of the specified object
let obj = if args.length() > 0 { args[0] } else { Undefined }
let proto = if args.length() > 1 { args[1] } else { Null }
// Validate proto is Object or null
match proto {
Object(_) | Null => ()
_ =>
raise @errors.TypeError(
message="Object prototype may only be an Object or null",
)
}
// Set the prototype if obj is an Object (return primitives unchanged)
match obj {
Object(data) => {
// Check if object is extensible (frozen/sealed objects can't change prototype)
if not(data.extensible) {
raise @errors.TypeError(
message="Cannot set prototype of non-extensible object",
)
}
data.prototype = proto
}
// Per ECMAScript spec, primitives are returned unchanged
_ => ()
}
obj
}),
"defineProperties": make_native_func("defineProperties", fn(args) {
let obj = if args.length() > 0 { args[0] } else { Undefined }
let descs = if args.length() > 1 { args[1] } else { Undefined }
match (obj, descs) {
(Object(data), Object(desc_obj)) =>
desc_obj.properties.each(fn(prop_name, desc_val) {
match desc_val {
Object(desc) => {
match desc.properties.get("value") {
Some(v) => data.properties[prop_name] = v
None =>
if not(data.properties.contains(prop_name)) {
data.properties[prop_name] = Undefined
}
}
let writable = match desc.properties.get("writable") {
Some(Bool(b)) => b
_ => false
}
let enumerable = match desc.properties.get("enumerable") {
Some(Bool(b)) => b
_ => false
}
let configurable = match
desc.properties.get("configurable") {
Some(Bool(b)) => b
_ => false
}
data.descriptors[prop_name] = {
writable,
enumerable,
configurable,
}
}
_ => ()
}
})
_ => ()
}
obj
}),
},
symbol_properties: {},
prototype: Null,
callable: Some(
NativeCallable("Object", fn(args) {
let val = if args.length() > 0 { args[0] } else { Undefined }
match val {
Object(_) => val
Undefined | Null =>
Object({
properties: {},
symbol_properties: {},
prototype: obj_proto,
callable: None,
class_name: "Object",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
_ => val
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
}),
)
// Set Object.prototype.constructor = Object
match obj_proto {
Object(data) => {
let obj_ctor = try { env.get("Object") } catch { _ => Undefined }
data.properties["constructor"] = obj_ctor
data.descriptors["constructor"] = { writable: true, enumerable: false, configurable: true }
}
_ => ()
}
// Create Array.prototype with all array methods
let array_proto_props : Map[String, Value] = {}
// Non-callback methods (using MethodCallable for this-aware dispatch)
array_proto_props["push"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("push", fn(this_val, args) {
match this_val {
Array(arr) => {
for arg in args {
arr.elements.push(arg)
}
Number(arr.elements.length().to_double())
}
_ => raise @errors.TypeError(message="push called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["pop"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("pop", fn(this_val, _args) {
match this_val {
Array(arr) =>
if arr.elements.length() == 0 {
Undefined
} else {
arr.elements.pop() |> Option::unwrap
}
_ => raise @errors.TypeError(message="pop called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["shift"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("shift", fn(this_val, _args) {
match this_val {
Array(arr) => {
if arr.elements.length() == 0 {
return Undefined
}
let first = arr.elements[0]
let new_elems : Array[Value] = []
for i = 1; i < arr.elements.length(); i = i + 1 {
new_elems.push(arr.elements[i])
}
while arr.elements.length() > 0 {
let _ = arr.elements.pop()
}
for el in new_elems {
arr.elements.push(el)
}
first
}
_ => raise @errors.TypeError(message="shift called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["unshift"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("unshift", fn(this_val, args) {
match this_val {
Array(arr) => {
let old : Array[Value] = []
for el in arr.elements {
old.push(el)
}
while arr.elements.length() > 0 {
let _ = arr.elements.pop()
}
for arg in args {
arr.elements.push(arg)
}
for el in old {
arr.elements.push(el)
}
Number(arr.elements.length().to_double())
}
_ => raise @errors.TypeError(message="unshift called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["join"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("join", fn(this_val, args) {
let len = to_array_like_length(this_val)
let sep = if args.length() > 0 {
match args[0] {
Undefined => ","
v => v.to_string()
}
} else {
","
}
let parts : Array[String] = []
for i = 0; i < len; i = i + 1 {
let v = get_array_like_element(this_val, i)
match v {
Null | Undefined => parts.push("")
_ => parts.push(v.to_string())
}
}
String_(parts.join(sep))
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["indexOf"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("indexOf", fn(this_val, args) {
let len = to_array_like_length(this_val)
let search = if args.length() > 0 { args[0] } else { Undefined }
let from_idx = if args.length() > 1 {
to_number(args[1]).to_int()
} else {
0
}
let start = if from_idx < 0 {
let s = len + from_idx
if s < 0 { 0 } else { s }
} else {
from_idx
}
for i = start; i < len; i = i + 1 {
if has_array_like_element(this_val, i) {
if strict_equal_val(
get_array_like_element(this_val, i),
search,
) {
return Number(i.to_double())
}
}
}
Number(-1.0)
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["lastIndexOf"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("lastIndexOf", fn(this_val, args) {
let len = to_array_like_length(this_val)
let search = if args.length() > 0 { args[0] } else { Undefined }
let start = if args.length() > 1 {
to_number(args[1]).to_int()
} else {
len - 1
}
let from = if start < 0 {
len + start
} else if start >= len {
len - 1
} else {
start
}
for i = from; i >= 0; i = i - 1 {
if has_array_like_element(this_val, i) {
if strict_equal_val(
get_array_like_element(this_val, i),
search,
) {
return Number(i.to_double())
}
}
}
Number(-1.0)
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["includes"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("includes", fn(this_val, args) {
let len = to_array_like_length(this_val)
let search = if args.length() > 0 { args[0] } else { Undefined }
let from_idx = if args.length() > 1 {
to_number(args[1]).to_int()
} else {
0
}
let start = if from_idx < 0 {
let s = len + from_idx
if s < 0 { 0 } else { s }
} else {
from_idx
}
for i = start; i < len; i = i + 1 {
if same_value_zero(get_array_like_element(this_val, i), search) {
return Bool(true)
}
}
Bool(false)
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["slice"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("slice", fn(this_val, args) {
let alen = to_array_like_length(this_val)
let mut start = if args.length() > 0 {
to_number(args[0]).to_int()
} else {
0
}
let mut end = if args.length() > 1 {
match args[1] {
Undefined => alen
_ => to_number(args[1]).to_int()
}
} else {
alen
}
if start < 0 {
start = alen + start
if start < 0 { start = 0 }
}
if end < 0 {
end = alen + end
if end < 0 { end = 0 }
}
if end > alen { end = alen }
let result : Array[Value] = []
for i = start; i < end; i = i + 1 {
result.push(get_array_like_element(this_val, i))
}
Array({ elements: result })
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["concat"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("concat", fn(this_val, args) {
let result : Array[Value] = []
// Add elements from this
let len = to_array_like_length(this_val)
match this_val {
Array(arr) =>
for el in arr.elements {
result.push(el)
}
_ =>
for i = 0; i < len; i = i + 1 {
result.push(get_array_like_element(this_val, i))
}
}
for arg in args {
match arg {
Array(other) =>
for el in other.elements {
result.push(el)
}
_ => result.push(arg)
}
}
Array({ elements: result })
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["reverse"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("reverse", fn(this_val, _args) {
match this_val {
Array(arr) => {
arr.elements.rev_in_place()
Array(arr)
}
_ => raise @errors.TypeError(message="reverse called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["fill"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("fill", fn(this_val, args) {
match this_val {
Array(arr) => {
let fill_val = if args.length() > 0 { args[0] } else { Undefined }
let alen = arr.elements.length()
let mut start = if args.length() > 1 {
to_number(args[1]).to_int()
} else {
0
}
let mut end = if args.length() > 2 {
to_number(args[2]).to_int()
} else {
alen
}
if start < 0 {
start = alen + start
}
if end < 0 {
end = alen + end
}
for i = start; i < end; i = i + 1 {
if i >= 0 && i < alen {
arr.elements[i] = fill_val
}
}
Array(arr)
}
_ => raise @errors.TypeError(message="fill called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["toString"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("toString", fn(this_val, _args) {
let len = to_array_like_length(this_val)
let parts : Array[String] = []
for i = 0; i < len; i = i + 1 {
let v = get_array_like_element(this_val, i)
match v {
Null | Undefined => parts.push("")
_ => parts.push(v.to_string())
}
}
String_(parts.join(","))
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["at"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("at", fn(this_val, args) {
let len = to_array_like_length(this_val)
let idx = if args.length() > 0 {
to_number(args[0]).to_int()
} else {
0
}
let actual_idx = if idx < 0 { len + idx } else { idx }
if actual_idx >= 0 && actual_idx < len {
get_array_like_element(this_val, actual_idx)
} else {
Undefined
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["toLocaleString"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("toLocaleString", fn(this_val, _args) {
let len = to_array_like_length(this_val)
let parts : Array[String] = []
for i = 0; i < len; i = i + 1 {
let v = get_array_like_element(this_val, i)
match v {
Null | Undefined => parts.push("")
_ => parts.push(v.to_string())
}
}
String_(parts.join(","))
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
// Callback-requiring methods (using InterpreterCallable)
array_proto_props["forEach"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("forEach", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let this_arg = if args.length() > 1 { args[1] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
for i = 0; i < len; i = i + 1 {
if has_array_like_element(this_val, i) {
let _ = interp.call_value(
callback,
this_arg,
[get_array_like_element(this_val, i), Number(i.to_double()), this_val],
loc,
)
}
}
Undefined
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["map"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("map", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let this_arg = if args.length() > 1 { args[1] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
let result : Array[Value] = []
for i = 0; i < len; i = i + 1 {
if has_array_like_element(this_val, i) {
let val = interp.call_value(
callback,
this_arg,
[get_array_like_element(this_val, i), Number(i.to_double()), this_val],
loc,
)
result.push(val)
} else {
result.push(Undefined)
}
}
Array({ elements: result })
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["filter"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("filter", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let this_arg = if args.length() > 1 { args[1] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
let result : Array[Value] = []
for i = 0; i < len; i = i + 1 {
if has_array_like_element(this_val, i) {
let el = get_array_like_element(this_val, i)
let val = interp.call_value(
callback,
this_arg,
[el, Number(i.to_double()), this_val],
loc,
)
if is_truthy(val) {
result.push(el)
}
}
}
Array({ elements: result })
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["reduce"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("reduce", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
let mut acc : Value = Undefined
let mut start = 0
if args.length() > 1 {
acc = args[1]
} else {
if len == 0 {
raise @errors.TypeError(
message="Reduce of empty array with no initial value",
)
}
acc = get_array_like_element(this_val, 0)
start = 1
}
for i = start; i < len; i = i + 1 {
if has_array_like_element(this_val, i) {
acc = interp.call_value(
callback,
Undefined,
[acc, get_array_like_element(this_val, i), Number(i.to_double()), this_val],
loc,
)
}
}
acc
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["reduceRight"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("reduceRight", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
let mut acc : Value = Undefined
let mut start = len - 1
if args.length() > 1 {
acc = args[1]
} else {
if len == 0 {
raise @errors.TypeError(
message="Reduce of empty array with no initial value",
)
}
acc = get_array_like_element(this_val, start)
start = start - 1
}
for i = start; i >= 0; i = i - 1 {
if has_array_like_element(this_val, i) {
acc = interp.call_value(
callback,
Undefined,
[acc, get_array_like_element(this_val, i), Number(i.to_double()), this_val],
loc,
)
}
}
acc
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["find"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("find", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let this_arg = if args.length() > 1 { args[1] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
for i = 0; i < len; i = i + 1 {
let el = get_array_like_element(this_val, i)
let val = interp.call_value(
callback, this_arg, [el, Number(i.to_double()), this_val], loc,
)
if is_truthy(val) { return el }
}
Undefined
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["findIndex"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("findIndex", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let this_arg = if args.length() > 1 { args[1] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
for i = 0; i < len; i = i + 1 {
let el = get_array_like_element(this_val, i)
let val = interp.call_value(
callback, this_arg, [el, Number(i.to_double()), this_val], loc,
)
if is_truthy(val) { return Number(i.to_double()) }
}
Number(-1.0)
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["findLast"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("findLast", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let this_arg = if args.length() > 1 { args[1] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
for i = len - 1; i >= 0; i = i - 1 {
let el = get_array_like_element(this_val, i)
let val = interp.call_value(
callback, this_arg, [el, Number(i.to_double()), this_val], loc,
)
if is_truthy(val) { return el }
}
Undefined
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["findLastIndex"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("findLastIndex", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let this_arg = if args.length() > 1 { args[1] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
for i = len - 1; i >= 0; i = i - 1 {
let el = get_array_like_element(this_val, i)
let val = interp.call_value(
callback, this_arg, [el, Number(i.to_double()), this_val], loc,
)
if is_truthy(val) { return Number(i.to_double()) }
}
Number(-1.0)
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["every"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("every", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let this_arg = if args.length() > 1 { args[1] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
for i = 0; i < len; i = i + 1 {
if has_array_like_element(this_val, i) {
let val = interp.call_value(
callback, this_arg,
[get_array_like_element(this_val, i), Number(i.to_double()), this_val],
loc,
)
if not(is_truthy(val)) { return Bool(false) }
}
}
Bool(true)
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["some"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("some", fn(interp, this_val, args) {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let this_arg = if args.length() > 1 { args[1] } else { Undefined }
let loc = @token.Loc::default()
let len = to_array_like_length(this_val)
for i = 0; i < len; i = i + 1 {
if has_array_like_element(this_val, i) {
let val = interp.call_value(
callback, this_arg,
[get_array_like_element(this_val, i), Number(i.to_double()), this_val],
loc,
)
if is_truthy(val) { return Bool(true) }
}
}
Bool(false)
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["sort"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("sort", fn(interp, this_val, args) {
match this_val {
Array(arr) => {
if args.length() > 0 {
match args[0] {
Undefined =>
arr.elements.sort_by(fn(a, b) {
let sa = a.to_string()
let sb = b.to_string()
if sa < sb {
-1
} else if sa > sb {
1
} else {
0
}
})
_ => {
let comparefn = args[0]
let loc = @token.Loc::default()
// Store any exception to re-throw after sort (spec requires propagation)
let mut caught_error : Error? = None
arr.elements.sort_by(fn(a, b) {
if caught_error is Some(_) {
return 0
}
let result = interp.call_value(
comparefn,
Undefined,
[a, b],
loc,
) catch {
e => {
caught_error = Some(e)
return 0
}
}
// Use sign-based comparison to avoid truncating fractional values
let cmp = to_number(result) catch {
e => {
caught_error = Some(e)
return 0
}
}
if cmp.is_nan() {
0
} else if cmp < 0.0 {
-1
} else if cmp > 0.0 {
1
} else {
0
}
})
match caught_error {
Some(e) => raise e
None => ()
}
}
}
} else {
arr.elements.sort_by(fn(a, b) {
let sa = a.to_string()
let sb = b.to_string()
if sa < sb {
-1
} else if sa > sb {
1
} else {
0
}
})
}
Array(arr)
}
_ => raise @errors.TypeError(message="sort called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["flat"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("flat", fn(this_val, args) {
match this_val {
Array(arr) => {
let depth = if args.length() > 0 {
to_number(args[0]).to_int()
} else {
1
}
let result : Array[Value] = []
flatten_array_val(arr.elements, depth, result)
Array({ elements: result })
}
_ => raise @errors.TypeError(message="flat called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["flatMap"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("flatMap", fn(interp, this_val, args) {
match this_val {
Array(arr) => {
let callback = if args.length() > 0 { args[0] } else { Undefined }
let this_arg = if args.length() > 1 { args[1] } else { Undefined }
let loc = @token.Loc::default()
let result : Array[Value] = []
for i = 0; i < arr.elements.length(); i = i + 1 {
let val = interp.call_value(
callback,
this_arg,
[arr.elements[i], Number(i.to_double()), Array(arr)],
loc,
)
match val {
Array(inner) =>
for el in inner.elements {
result.push(el)
}
_ => result.push(val)
}
}
Array({ elements: result })
}
_ => raise @errors.TypeError(message="flatMap called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["splice"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("splice", fn(this_val, args) {
match this_val {
Array(arr) => {
let alen = arr.elements.length()
let mut start = if args.length() > 0 {
to_number(args[0]).to_int()
} else {
0
}
if start < 0 {
start = alen + start
if start < 0 {
start = 0
}
}
if start > alen {
start = alen
}
let del_count = if args.length() > 1 {
let d = to_number(args[1]).to_int()
if d < 0 {
0
} else if d > alen - start {
alen - start
} else {
d
}
} else {
alen - start
}
let removed : Array[Value] = []
for i = start; i < start + del_count; i = i + 1 {
removed.push(arr.elements[i])
}
let new_items : Array[Value] = []
for i = 2; i < args.length(); i = i + 1 {
new_items.push(args[i])
}
let tail : Array[Value] = []
for i = start + del_count; i < alen; i = i + 1 {
tail.push(arr.elements[i])
}
while arr.elements.length() > start {
let _ = arr.elements.pop()
}
for item in new_items {
arr.elements.push(item)
}
for item in tail {
arr.elements.push(item)
}
Array({ elements: removed })
}
_ => raise @errors.TypeError(message="splice called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["copyWithin"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("copyWithin", fn(this_val, args) {
match this_val {
Array(arr) => {
let len = arr.elements.length()
let mut target = if args.length() > 0 {
to_number(args[0]).to_int()
} else {
0
}
let mut start = if args.length() > 1 {
to_number(args[1]).to_int()
} else {
0
}
let mut end = if args.length() > 2 {
match args[2] {
Undefined => len
_ => to_number(args[2]).to_int()
}
} else {
len
}
if target < 0 {
target = len + target
if target < 0 {
target = 0
}
}
if start < 0 {
start = len + start
if start < 0 {
start = 0
}
}
if end < 0 {
end = len + end
if end < 0 {
end = 0
}
}
if end > len {
end = len
}
let count = if end - start < len - target {
end - start
} else {
len - target
}
if count > 0 {
let temp : Array[Value] = []
for i = start; i < start + count; i = i + 1 {
temp.push(arr.elements[i])
}
for i = 0; i < count; i = i + 1 {
if target + i < len {
arr.elements[target + i] = temp[i]
}
}
}
Array(arr)
}
_ => raise @errors.TypeError(message="copyWithin called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["entries"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("entries", fn(this_val, _args) {
match this_val {
Array(arr) => {
let result : Array[Value] = []
for i = 0; i < arr.elements.length(); i = i + 1 {
result.push(
Array({ elements: [Number(i.to_double()), arr.elements[i]] }),
)
}
Array({ elements: result })
}
_ => raise @errors.TypeError(message="entries called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["keys"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("keys", fn(this_val, _args) {
match this_val {
Array(arr) => {
let result : Array[Value] = []
for i = 0; i < arr.elements.length(); i = i + 1 {
result.push(Number(i.to_double()))
}
Array({ elements: result })
}
_ => raise @errors.TypeError(message="keys called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["values"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("values", fn(this_val, _args) {
match this_val {
Array(arr) => {
// Return a copy to prevent mutation of the original
let result : Array[Value] = []
for el in arr.elements {
result.push(el)
}
Array({ elements: result })
}
_ => raise @errors.TypeError(message="values called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["toReversed"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("toReversed", fn(this_val, _args) {
match this_val {
Array(arr) => {
let result : Array[Value] = []
for i = arr.elements.length() - 1; i >= 0; i = i - 1 {
result.push(arr.elements[i])
}
Array({ elements: result })
}
_ => raise @errors.TypeError(message="toReversed called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["toSorted"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("toSorted", fn(interp, this_val, args) {
match this_val {
Array(arr) => {
let result : Array[Value] = []
for el in arr.elements {
result.push(el)
}
if args.length() > 0 {
match args[0] {
Undefined =>
result.sort_by(fn(a, b) {
let sa = a.to_string()
let sb = b.to_string()
if sa < sb {
-1
} else if sa > sb {
1
} else {
0
}
})
_ => {
let comparefn = args[0]
let loc = @token.Loc::default()
// Store any exception to re-throw after sort (spec requires propagation)
let mut caught_error : Error? = None
result.sort_by(fn(a, b) {
if caught_error is Some(_) {
return 0
}
let cmp_result = interp.call_value(
comparefn,
Undefined,
[a, b],
loc,
) catch {
e => {
caught_error = Some(e)
return 0
}
}
// Use sign-based comparison to avoid truncating fractional values
let cmp = to_number(cmp_result) catch {
e => {
caught_error = Some(e)
return 0
}
}
if cmp.is_nan() {
0
} else if cmp < 0.0 {
-1
} else if cmp > 0.0 {
1
} else {
0
}
})
match caught_error {
Some(e) => raise e
None => ()
}
}
}
} else {
result.sort_by(fn(a, b) {
let sa = a.to_string()
let sb = b.to_string()
if sa < sb {
-1
} else if sa > sb {
1
} else {
0
}
})
}
Array({ elements: result })
}
_ => raise @errors.TypeError(message="toSorted called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["toSpliced"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("toSpliced", fn(this_val, args) {
match this_val {
Array(arr) => {
let len = arr.elements.length()
let mut start = if args.length() > 0 {
to_number(args[0]).to_int()
} else {
0
}
if start < 0 {
start = len + start
if start < 0 {
start = 0
}
}
if start > len {
start = len
}
let del_count = if args.length() > 1 {
let d = to_number(args[1]).to_int()
if d < 0 {
0
} else if d > len - start {
len - start
} else {
d
}
} else {
len - start
}
let result : Array[Value] = []
for i = 0; i < start; i = i + 1 {
result.push(arr.elements[i])
}
for i = 2; i < args.length(); i = i + 1 {
result.push(args[i])
}
for i = start + del_count; i < len; i = i + 1 {
result.push(arr.elements[i])
}
Array({ elements: result })
}
_ => raise @errors.TypeError(message="toSpliced called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
array_proto_props["with"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
MethodCallable("with", fn(this_val, args) {
match this_val {
Array(arr) => {
let idx = if args.length() > 0 {
to_number(args[0]).to_int()
} else {
0
}
let value = if args.length() > 1 { args[1] } else { Undefined }
let len = arr.elements.length()
let actual_idx = if idx < 0 { len + idx } else { idx }
if actual_idx < 0 || actual_idx >= len {
raise @errors.RangeError(message="Invalid index")
}
let result : Array[Value] = []
for i = 0; i < len; i = i + 1 {
if i == actual_idx {
result.push(value)
} else {
result.push(arr.elements[i])
}
}
Array({ elements: result })
}
_ => raise @errors.TypeError(message="with called on non-array")
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
let array_proto : Value = Object({
properties: array_proto_props,
symbol_properties: {},
prototype: obj_proto,
callable: None,
class_name: "Array",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
// Array constructor with Array.isArray, Array.from, Array.of
let array_props : Map[String, Value] = {}
array_props["prototype"] = array_proto
array_props["isArray"] = make_native_func("isArray", fn(args) {
let val = if args.length() > 0 { args[0] } else { Undefined }
match val {
Array(_) => Bool(true)
_ => Bool(false)
}
})
array_props["of"] = make_native_func("of", fn(args) {
// Array.of() - creates array from arguments
Array({ elements: args })
})
// Array.from with mapFn support using InterpreterCallable
array_props["from"] = Object({
properties: {},
symbol_properties: {},
prototype: Null,
callable: Some(
InterpreterCallable("from", fn(interp, _this_val, args) {
let source = if args.length() > 0 { args[0] } else { Undefined }
let map_fn = if args.length() > 1 {
match args[1] {
Undefined => None
fn_val => Some(fn_val)
}
} else {
None
}
let this_arg = if args.length() > 2 { args[2] } else { Undefined }
let loc = @token.Loc::default()
let elements : Array[Value] = []
match source {
Array(data) =>
for i = 0; i < data.elements.length(); i = i + 1 {
let el = data.elements[i]
let mapped = match map_fn {
Some(fn_val) =>
interp.call_value(
fn_val,
this_arg,
[el, Number(i.to_double())],
loc,
)
None => el
}
elements.push(mapped)
}
String_(s) => {
let chars = s.to_array()
for i = 0; i < chars.length(); i = i + 1 {
let el = String_(chars[i].to_string())
let mapped = match map_fn {
Some(fn_val) =>
interp.call_value(
fn_val,
this_arg,
[el, Number(i.to_double())],
loc,
)
None => el
}
elements.push(mapped)
}
}
Object(data) => {
let len = match data.properties.get("length") {
Some(Number(n)) => n.to_int()
Some(v) => to_number(v).to_int() catch { _ => 0 }
None => 0
}
for i = 0; i < len; i = i + 1 {
let key = i.to_string()
let el = match data.properties.get(key) {
Some(v) => v
None => Undefined
}
let mapped = match map_fn {
Some(fn_val) =>
interp.call_value(
fn_val,
this_arg,
[el, Number(i.to_double())],
loc,
)
None => el
}
elements.push(mapped)
}
}
_ => ()
}
Array({ elements, })
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
})
env.def_builtin(
"Array",
Object({
properties: array_props,
symbol_properties: {},
prototype: Null,
callable: Some(
NativeCallable("Array", fn(args) {
// Array constructor behavior
if args.length() == 1 {
match args[0] {
Number(n) => {
// Array(n) produces a dense array populated with undefined values.
// LIMITATION: This interpreter does not support sparse arrays.
// In standard JS, Array(n) creates sparse holes (empty slots), which
// behave differently: hasOwnProperty returns false for holes,
// 'in' operator returns false, and forEach/map/filter skip holes.
// Our implementation fills with undefined instead, making all indices
// present. This is an intentional simplification.
let len = n.to_int()
if len < 0 || n != len.to_double() || len > 0xFFFFFFFF {
raise @errors.RangeError(message="Invalid array length")
}
let elements : Array[Value] = []
for _i = 0; _i < len; _i = _i + 1 {
elements.push(Undefined)
}
Array({ elements, })
}
_ => Array({ elements: args })
}
} else {
Array({ elements: args })
}
}),
),
class_name: "Function",
descriptors: {},
symbol_descriptors: {},
extensible: true,
}),
)
// Set Array.prototype.constructor = Array
match array_proto {
Object(data) => {
let arr_ctor = try { env.get("Array") } catch { _ => Undefined }
data.properties["constructor"] = arr_ctor
data.descriptors["constructor"] = { writable: true, enumerable: false, configurable: true }
}
_ => ()
}
}
///|
fn flatten_array_val(
elements : Array[Value],
depth : Int,
result : Array[Value],
) -> Unit {
for el in elements {
match el {
Array(inner) =>
if depth > 0 {
flatten_array_val(inner.elements, depth - 1, result)
} else {
result.push(el)
}
_ => result.push(el)
}
}
}
///|
/// ToLength: Get length from any value as per ECMAScript spec (array-like objects)
pub fn to_array_like_length(val : Value) -> Int {
match val {
Array(data) => data.elements.length()
Object(data) =>
match data.properties.get("length") {
Some(Number(n)) => {
let len = n.to_int()
if len < 0 { 0 } else { len }
}
Some(v) => {
let n = try { to_number(v) } catch { _ => 0.0 }
if n.is_nan() {
0
} else if n.is_inf() {
if n > 0.0 { 0x7FFFFFFF } else { 0 }
} else {
let i = n.to_int()
if i < 0 { 0 } else { i }
}
}
None => 0
}
String_(s) => s.length()
_ => 0
}
}
///|
/// Get indexed element from array-like value
pub fn get_array_like_element(val : Value, index : Int) -> Value {
let key = index.to_string()
match val {
Array(data) =>
if index >= 0 && index < data.elements.length() {
data.elements[index]
} else {
Undefined
}
Object(data) => {
// Walk prototype chain for property lookup
match data.properties.get(key) {
Some(v) => v
None => {
let mut current = data.prototype
let mut result : Value = Undefined
while true {
match current {
Object(proto_data) =>
match proto_data.properties.get(key) {
Some(v) => {
result = v
break
}
None => current = proto_data.prototype
}
_ => break
}
}
result
}
}
}
String_(s) => {
let chars = s.to_array()
if index >= 0 && index < chars.length() {
let buf = StringBuilder::new()
buf.write_char(chars[index])
String_(buf.to_string())
} else {
Undefined
}
}
_ => Undefined
}
}
///|
/// Check if array-like value has indexed property
pub fn has_array_like_element(val : Value, index : Int) -> Bool {
let key = index.to_string()
match val {
Array(data) => index >= 0 && index < data.elements.length()
Object(data) => {
if data.properties.contains(key) {
return true
}
// Walk prototype chain
let mut current = data.prototype
while true {
match current {
Object(proto_data) =>
if proto_data.properties.contains(key) {
return true
} else {
current = proto_data.prototype
}
_ => break
}
}
false
}
String_(s) => index >= 0 && index < s.length()
_ => false
}
}