we (web engine): Experimental web browser project to understand the limits of Claude
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement JS built-in Object, Array, Function, and Error

Add comprehensive built-in object support for the JavaScript engine:

## Infrastructure
- NativeContext passed to native callbacks (GC access + this binding)
- Object.prototype and Array.prototype stored in VM
- CreateObject/CreateArray set prototype chains
- Array literals now set length property

## Object built-ins
- Constructor, Object.keys/values/entries/assign/create/is
- Object.getPrototypeOf/getOwnPropertyNames/getOwnPropertyDescriptor
- Object.defineProperty/freeze/seal/isFrozen/isSealed
- Object.fromEntries/hasOwn
- Object.prototype: hasOwnProperty/toString/valueOf

## Array built-ins
- Native: push/pop/shift/unshift, indexOf/lastIndexOf/includes
- Native: join/slice/concat/reverse/splice/fill/at/toString
- Static: Array.isArray/from/of
- JS preamble: map/filter/reduce/reduceRight/forEach
- JS preamble: find/findIndex/some/every/sort/flat/flatMap

## Error built-ins
- Error/TypeError/ReferenceError/SyntaxError/RangeError/URIError/EvalError
- Prototype chain with name/message, Error.prototype.toString

## Global functions
- parseInt/parseFloat/isNaN/isFinite

37 new tests covering all built-in categories.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+2207 -17
+1669
crates/js/src/builtins.rs
··· 1 + //! Built-in JavaScript objects and functions: Object, Array, Function, Error. 2 + //! 3 + //! Registers constructors, static methods, and prototype methods as globals 4 + //! in the VM. Callback-based array methods (map, filter, etc.) are defined 5 + //! via a JS preamble executed at init time. 6 + 7 + use crate::gc::{Gc, GcRef}; 8 + use crate::vm::*; 9 + use std::collections::HashMap; 10 + 11 + // ── Helpers ────────────────────────────────────────────────── 12 + 13 + /// Create a native function GcRef. 14 + fn make_native( 15 + gc: &mut Gc<HeapObject>, 16 + name: &str, 17 + callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>, 18 + ) -> GcRef { 19 + gc.alloc(HeapObject::Function(Box::new(FunctionData { 20 + name: name.to_string(), 21 + kind: FunctionKind::Native(NativeFunc { callback }), 22 + prototype_obj: None, 23 + properties: HashMap::new(), 24 + upvalues: Vec::new(), 25 + }))) 26 + } 27 + 28 + /// Set a non-enumerable property on an object. 29 + fn set_builtin_prop(gc: &mut Gc<HeapObject>, obj: GcRef, key: &str, val: Value) { 30 + if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { 31 + data.properties 32 + .insert(key.to_string(), Property::builtin(val)); 33 + } 34 + } 35 + 36 + /// Set a non-enumerable property on a function. 37 + fn set_func_prop(gc: &mut Gc<HeapObject>, func: GcRef, key: &str, val: Value) { 38 + if let Some(HeapObject::Function(fdata)) = gc.get_mut(func) { 39 + fdata 40 + .properties 41 + .insert(key.to_string(), Property::builtin(val)); 42 + } 43 + } 44 + 45 + /// Get own enumerable string keys of an object (for Object.keys, etc.). 46 + fn own_enumerable_keys(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> { 47 + match gc.get(obj_ref) { 48 + Some(HeapObject::Object(data)) => { 49 + let mut int_keys: Vec<(u32, String)> = Vec::new(); 50 + let mut str_keys: Vec<String> = Vec::new(); 51 + for (k, prop) in &data.properties { 52 + if prop.enumerable { 53 + if let Ok(idx) = k.parse::<u32>() { 54 + int_keys.push((idx, k.clone())); 55 + } else { 56 + str_keys.push(k.clone()); 57 + } 58 + } 59 + } 60 + int_keys.sort_by_key(|(idx, _)| *idx); 61 + let mut result: Vec<String> = int_keys.into_iter().map(|(_, k)| k).collect(); 62 + result.extend(str_keys); 63 + result 64 + } 65 + _ => Vec::new(), 66 + } 67 + } 68 + 69 + /// Get all own property names (enumerable or not) of an object. 70 + fn own_property_names(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> { 71 + match gc.get(obj_ref) { 72 + Some(HeapObject::Object(data)) => { 73 + let mut int_keys: Vec<(u32, String)> = Vec::new(); 74 + let mut str_keys: Vec<String> = Vec::new(); 75 + for k in data.properties.keys() { 76 + if let Ok(idx) = k.parse::<u32>() { 77 + int_keys.push((idx, k.clone())); 78 + } else { 79 + str_keys.push(k.clone()); 80 + } 81 + } 82 + int_keys.sort_by_key(|(idx, _)| *idx); 83 + let mut result: Vec<String> = int_keys.into_iter().map(|(_, k)| k).collect(); 84 + result.extend(str_keys); 85 + result 86 + } 87 + _ => Vec::new(), 88 + } 89 + } 90 + 91 + /// Read the "length" property of an array-like object as usize. 92 + fn array_length(gc: &Gc<HeapObject>, obj: GcRef) -> usize { 93 + match gc.get(obj) { 94 + Some(HeapObject::Object(data)) => match data.properties.get("length") { 95 + Some(prop) => prop.value.to_number() as usize, 96 + None => 0, 97 + }, 98 + _ => 0, 99 + } 100 + } 101 + 102 + /// Set the "length" property on an array-like object. 103 + fn set_array_length(gc: &mut Gc<HeapObject>, obj: GcRef, len: usize) { 104 + if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { 105 + if let Some(prop) = data.properties.get_mut("length") { 106 + prop.value = Value::Number(len as f64); 107 + } else { 108 + data.properties.insert( 109 + "length".to_string(), 110 + Property { 111 + value: Value::Number(len as f64), 112 + writable: true, 113 + enumerable: false, 114 + configurable: false, 115 + }, 116 + ); 117 + } 118 + } 119 + } 120 + 121 + /// Get an element by index from an array-like object. 122 + fn array_get(gc: &Gc<HeapObject>, obj: GcRef, idx: usize) -> Value { 123 + match gc.get(obj) { 124 + Some(HeapObject::Object(data)) => { 125 + let key = idx.to_string(); 126 + data.properties 127 + .get(&key) 128 + .map(|p| p.value.clone()) 129 + .unwrap_or(Value::Undefined) 130 + } 131 + _ => Value::Undefined, 132 + } 133 + } 134 + 135 + /// Set an element by index on an array-like object. 136 + fn array_set(gc: &mut Gc<HeapObject>, obj: GcRef, idx: usize, val: Value) { 137 + if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { 138 + data.properties.insert(idx.to_string(), Property::data(val)); 139 + } 140 + } 141 + 142 + // ── Initialization ─────────────────────────────────────────── 143 + 144 + /// Initialize all built-in objects and register them in the VM. 145 + pub fn init_builtins(vm: &mut Vm) { 146 + // Create Object.prototype first (root of the prototype chain). 147 + let obj_proto = vm.gc.alloc(HeapObject::Object(ObjectData::new())); 148 + init_object_prototype(&mut vm.gc, obj_proto); 149 + 150 + // Create Array.prototype (inherits from Object.prototype). 151 + let mut arr_proto_data = ObjectData::new(); 152 + arr_proto_data.prototype = Some(obj_proto); 153 + let arr_proto = vm.gc.alloc(HeapObject::Object(arr_proto_data)); 154 + init_array_prototype(&mut vm.gc, arr_proto); 155 + 156 + // Create Error.prototype (inherits from Object.prototype). 157 + let mut err_proto_data = ObjectData::new(); 158 + err_proto_data.prototype = Some(obj_proto); 159 + err_proto_data.properties.insert( 160 + "name".to_string(), 161 + Property::builtin(Value::String("Error".to_string())), 162 + ); 163 + err_proto_data.properties.insert( 164 + "message".to_string(), 165 + Property::builtin(Value::String(String::new())), 166 + ); 167 + let err_proto = vm.gc.alloc(HeapObject::Object(err_proto_data)); 168 + init_error_prototype(&mut vm.gc, err_proto); 169 + 170 + // Store prototypes in VM for use by CreateArray/CreateObject. 171 + vm.object_prototype = Some(obj_proto); 172 + vm.array_prototype = Some(arr_proto); 173 + 174 + // Create and register Object constructor. 175 + let obj_ctor = init_object_constructor(&mut vm.gc, obj_proto); 176 + vm.set_global("Object", Value::Function(obj_ctor)); 177 + 178 + // Create and register Array constructor. 179 + let arr_ctor = init_array_constructor(&mut vm.gc, arr_proto); 180 + vm.set_global("Array", Value::Function(arr_ctor)); 181 + 182 + // Create and register Error constructors. 183 + init_error_constructors(vm, obj_proto, err_proto); 184 + 185 + // Register global utility functions. 186 + init_global_functions(vm); 187 + 188 + // Execute JS preamble for callback-based methods. 189 + init_js_preamble(vm); 190 + } 191 + 192 + // ── Object.prototype ───────────────────────────────────────── 193 + 194 + fn init_object_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 195 + let has_own = make_native(gc, "hasOwnProperty", object_proto_has_own_property); 196 + set_builtin_prop(gc, proto, "hasOwnProperty", Value::Function(has_own)); 197 + 198 + let to_string = make_native(gc, "toString", object_proto_to_string); 199 + set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 200 + 201 + let value_of = make_native(gc, "valueOf", object_proto_value_of); 202 + set_builtin_prop(gc, proto, "valueOf", Value::Function(value_of)); 203 + } 204 + 205 + fn object_proto_has_own_property( 206 + args: &[Value], 207 + ctx: &mut NativeContext, 208 + ) -> Result<Value, RuntimeError> { 209 + let key = args 210 + .first() 211 + .map(|v| v.to_js_string(ctx.gc)) 212 + .unwrap_or_default(); 213 + match ctx.this.gc_ref() { 214 + Some(obj_ref) => match ctx.gc.get(obj_ref) { 215 + Some(HeapObject::Object(data)) => { 216 + Ok(Value::Boolean(data.properties.contains_key(&key))) 217 + } 218 + Some(HeapObject::Function(fdata)) => { 219 + Ok(Value::Boolean(fdata.properties.contains_key(&key))) 220 + } 221 + _ => Ok(Value::Boolean(false)), 222 + }, 223 + None => Ok(Value::Boolean(false)), 224 + } 225 + } 226 + 227 + fn object_proto_to_string( 228 + _args: &[Value], 229 + _ctx: &mut NativeContext, 230 + ) -> Result<Value, RuntimeError> { 231 + Ok(Value::String("[object Object]".to_string())) 232 + } 233 + 234 + fn object_proto_value_of(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 235 + Ok(ctx.this.clone()) 236 + } 237 + 238 + // ── Object constructor + static methods ────────────────────── 239 + 240 + fn init_object_constructor(gc: &mut Gc<HeapObject>, obj_proto: GcRef) -> GcRef { 241 + let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { 242 + name: "Object".to_string(), 243 + kind: FunctionKind::Native(NativeFunc { 244 + callback: object_constructor, 245 + }), 246 + prototype_obj: Some(obj_proto), 247 + properties: HashMap::new(), 248 + upvalues: Vec::new(), 249 + }))); 250 + 251 + // Static methods. 252 + let keys = make_native(gc, "keys", object_keys); 253 + set_func_prop(gc, ctor, "keys", Value::Function(keys)); 254 + 255 + let values = make_native(gc, "values", object_values); 256 + set_func_prop(gc, ctor, "values", Value::Function(values)); 257 + 258 + let entries = make_native(gc, "entries", object_entries); 259 + set_func_prop(gc, ctor, "entries", Value::Function(entries)); 260 + 261 + let assign = make_native(gc, "assign", object_assign); 262 + set_func_prop(gc, ctor, "assign", Value::Function(assign)); 263 + 264 + let create = make_native(gc, "create", object_create); 265 + set_func_prop(gc, ctor, "create", Value::Function(create)); 266 + 267 + let is = make_native(gc, "is", object_is); 268 + set_func_prop(gc, ctor, "is", Value::Function(is)); 269 + 270 + let get_proto = make_native(gc, "getPrototypeOf", object_get_prototype_of); 271 + set_func_prop(gc, ctor, "getPrototypeOf", Value::Function(get_proto)); 272 + 273 + let get_own_names = make_native(gc, "getOwnPropertyNames", object_get_own_property_names); 274 + set_func_prop( 275 + gc, 276 + ctor, 277 + "getOwnPropertyNames", 278 + Value::Function(get_own_names), 279 + ); 280 + 281 + let get_own_desc = make_native(gc, "getOwnPropertyDescriptor", object_get_own_prop_desc); 282 + set_func_prop( 283 + gc, 284 + ctor, 285 + "getOwnPropertyDescriptor", 286 + Value::Function(get_own_desc), 287 + ); 288 + 289 + let define_prop = make_native(gc, "defineProperty", object_define_property); 290 + set_func_prop(gc, ctor, "defineProperty", Value::Function(define_prop)); 291 + 292 + let freeze = make_native(gc, "freeze", object_freeze); 293 + set_func_prop(gc, ctor, "freeze", Value::Function(freeze)); 294 + 295 + let seal = make_native(gc, "seal", object_seal); 296 + set_func_prop(gc, ctor, "seal", Value::Function(seal)); 297 + 298 + let is_frozen = make_native(gc, "isFrozen", object_is_frozen); 299 + set_func_prop(gc, ctor, "isFrozen", Value::Function(is_frozen)); 300 + 301 + let is_sealed = make_native(gc, "isSealed", object_is_sealed); 302 + set_func_prop(gc, ctor, "isSealed", Value::Function(is_sealed)); 303 + 304 + let from_entries = make_native(gc, "fromEntries", object_from_entries); 305 + set_func_prop(gc, ctor, "fromEntries", Value::Function(from_entries)); 306 + 307 + let has_own = make_native(gc, "hasOwn", object_has_own); 308 + set_func_prop(gc, ctor, "hasOwn", Value::Function(has_own)); 309 + 310 + ctor 311 + } 312 + 313 + fn object_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 314 + match args.first() { 315 + Some(Value::Object(_)) | Some(Value::Function(_)) => Ok(args[0].clone()), 316 + Some(Value::Null) | Some(Value::Undefined) | None => { 317 + let obj = ctx.gc.alloc(HeapObject::Object(ObjectData::new())); 318 + Ok(Value::Object(obj)) 319 + } 320 + _ => { 321 + // Primitive wrapping (simplified): return a new object. 322 + let obj = ctx.gc.alloc(HeapObject::Object(ObjectData::new())); 323 + Ok(Value::Object(obj)) 324 + } 325 + } 326 + } 327 + 328 + fn object_keys(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 329 + let obj_ref = match args.first() { 330 + Some(Value::Object(r)) | Some(Value::Function(r)) => *r, 331 + _ => return Err(RuntimeError::type_error("Object.keys requires an object")), 332 + }; 333 + let keys = own_enumerable_keys(ctx.gc, obj_ref); 334 + Ok(make_string_array(ctx.gc, &keys)) 335 + } 336 + 337 + fn object_values(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 338 + let obj_ref = match args.first() { 339 + Some(Value::Object(r)) => *r, 340 + _ => return Err(RuntimeError::type_error("Object.values requires an object")), 341 + }; 342 + let keys = own_enumerable_keys(ctx.gc, obj_ref); 343 + let values: Vec<Value> = keys 344 + .iter() 345 + .map(|k| { 346 + ctx.gc 347 + .get(obj_ref) 348 + .and_then(|ho| match ho { 349 + HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), 350 + _ => None, 351 + }) 352 + .unwrap_or(Value::Undefined) 353 + }) 354 + .collect(); 355 + Ok(make_value_array(ctx.gc, &values)) 356 + } 357 + 358 + fn object_entries(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 359 + let obj_ref = match args.first() { 360 + Some(Value::Object(r)) => *r, 361 + _ => { 362 + return Err(RuntimeError::type_error( 363 + "Object.entries requires an object", 364 + )) 365 + } 366 + }; 367 + let keys = own_enumerable_keys(ctx.gc, obj_ref); 368 + let mut entries = Vec::new(); 369 + for k in &keys { 370 + let val = ctx 371 + .gc 372 + .get(obj_ref) 373 + .and_then(|ho| match ho { 374 + HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), 375 + _ => None, 376 + }) 377 + .unwrap_or(Value::Undefined); 378 + // Create a [key, value] pair array. 379 + let pair = make_value_array(ctx.gc, &[Value::String(k.clone()), val]); 380 + entries.push(pair); 381 + } 382 + Ok(make_value_array(ctx.gc, &entries)) 383 + } 384 + 385 + fn object_assign(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 386 + let target_ref = match args.first() { 387 + Some(Value::Object(r)) => *r, 388 + _ => { 389 + return Err(RuntimeError::type_error( 390 + "Object.assign requires an object target", 391 + )) 392 + } 393 + }; 394 + for source in args.iter().skip(1) { 395 + let src_ref = match source { 396 + Value::Object(r) => *r, 397 + Value::Null | Value::Undefined => continue, 398 + _ => continue, 399 + }; 400 + let keys = own_enumerable_keys(ctx.gc, src_ref); 401 + for k in &keys { 402 + let val = ctx 403 + .gc 404 + .get(src_ref) 405 + .and_then(|ho| match ho { 406 + HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), 407 + _ => None, 408 + }) 409 + .unwrap_or(Value::Undefined); 410 + if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(target_ref) { 411 + data.properties.insert(k.clone(), Property::data(val)); 412 + } 413 + } 414 + } 415 + Ok(Value::Object(target_ref)) 416 + } 417 + 418 + fn object_create(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 419 + let proto = match args.first() { 420 + Some(Value::Object(r)) => Some(*r), 421 + Some(Value::Null) => None, 422 + _ => { 423 + return Err(RuntimeError::type_error( 424 + "Object.create prototype must be an object or null", 425 + )) 426 + } 427 + }; 428 + let mut obj = ObjectData::new(); 429 + obj.prototype = proto; 430 + let gc_ref = ctx.gc.alloc(HeapObject::Object(obj)); 431 + Ok(Value::Object(gc_ref)) 432 + } 433 + 434 + fn object_is(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 435 + let x = args.first().cloned().unwrap_or(Value::Undefined); 436 + let y = args.get(1).cloned().unwrap_or(Value::Undefined); 437 + Ok(Value::Boolean(same_value(&x, &y))) 438 + } 439 + 440 + /// SameValue algorithm (ECMA-262 §7.2.10). 441 + fn same_value(x: &Value, y: &Value) -> bool { 442 + match (x, y) { 443 + (Value::Number(a), Value::Number(b)) => { 444 + if a.is_nan() && b.is_nan() { 445 + return true; 446 + } 447 + if *a == 0.0 && *b == 0.0 { 448 + return a.is_sign_positive() == b.is_sign_positive(); 449 + } 450 + a == b 451 + } 452 + (Value::Undefined, Value::Undefined) => true, 453 + (Value::Null, Value::Null) => true, 454 + (Value::Boolean(a), Value::Boolean(b)) => a == b, 455 + (Value::String(a), Value::String(b)) => a == b, 456 + (Value::Object(a), Value::Object(b)) => a == b, 457 + (Value::Function(a), Value::Function(b)) => a == b, 458 + _ => false, 459 + } 460 + } 461 + 462 + fn object_get_prototype_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 463 + let obj_ref = match args.first() { 464 + Some(Value::Object(r)) => *r, 465 + _ => { 466 + return Err(RuntimeError::type_error( 467 + "Object.getPrototypeOf requires an object", 468 + )) 469 + } 470 + }; 471 + match ctx.gc.get(obj_ref) { 472 + Some(HeapObject::Object(data)) => match data.prototype { 473 + Some(proto) => Ok(Value::Object(proto)), 474 + None => Ok(Value::Null), 475 + }, 476 + _ => Ok(Value::Null), 477 + } 478 + } 479 + 480 + fn object_get_own_property_names( 481 + args: &[Value], 482 + ctx: &mut NativeContext, 483 + ) -> Result<Value, RuntimeError> { 484 + let obj_ref = match args.first() { 485 + Some(Value::Object(r)) | Some(Value::Function(r)) => *r, 486 + _ => { 487 + return Err(RuntimeError::type_error( 488 + "Object.getOwnPropertyNames requires an object", 489 + )) 490 + } 491 + }; 492 + let names = own_property_names(ctx.gc, obj_ref); 493 + Ok(make_string_array(ctx.gc, &names)) 494 + } 495 + 496 + fn object_get_own_prop_desc( 497 + args: &[Value], 498 + ctx: &mut NativeContext, 499 + ) -> Result<Value, RuntimeError> { 500 + let obj_ref = match args.first() { 501 + Some(Value::Object(r)) => *r, 502 + _ => return Ok(Value::Undefined), 503 + }; 504 + let key = args 505 + .get(1) 506 + .map(|v| v.to_js_string(ctx.gc)) 507 + .unwrap_or_default(); 508 + let prop = match ctx.gc.get(obj_ref) { 509 + Some(HeapObject::Object(data)) => data.properties.get(&key).cloned(), 510 + _ => None, 511 + }; 512 + match prop { 513 + Some(p) => { 514 + let mut desc = ObjectData::new(); 515 + desc.properties 516 + .insert("value".to_string(), Property::data(p.value)); 517 + desc.properties.insert( 518 + "writable".to_string(), 519 + Property::data(Value::Boolean(p.writable)), 520 + ); 521 + desc.properties.insert( 522 + "enumerable".to_string(), 523 + Property::data(Value::Boolean(p.enumerable)), 524 + ); 525 + desc.properties.insert( 526 + "configurable".to_string(), 527 + Property::data(Value::Boolean(p.configurable)), 528 + ); 529 + Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(desc)))) 530 + } 531 + None => Ok(Value::Undefined), 532 + } 533 + } 534 + 535 + fn object_define_property(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 536 + let obj_ref = match args.first() { 537 + Some(Value::Object(r)) => *r, 538 + _ => { 539 + return Err(RuntimeError::type_error( 540 + "Object.defineProperty requires an object", 541 + )) 542 + } 543 + }; 544 + let key = args 545 + .get(1) 546 + .map(|v| v.to_js_string(ctx.gc)) 547 + .unwrap_or_default(); 548 + let desc_ref = match args.get(2) { 549 + Some(Value::Object(r)) => *r, 550 + _ => { 551 + return Err(RuntimeError::type_error( 552 + "Property descriptor must be an object", 553 + )) 554 + } 555 + }; 556 + 557 + // Read descriptor properties. 558 + let (value, writable, enumerable, configurable) = { 559 + match ctx.gc.get(desc_ref) { 560 + Some(HeapObject::Object(desc_data)) => { 561 + let value = desc_data 562 + .properties 563 + .get("value") 564 + .map(|p| p.value.clone()) 565 + .unwrap_or(Value::Undefined); 566 + let writable = desc_data 567 + .properties 568 + .get("writable") 569 + .map(|p| p.value.to_boolean()) 570 + .unwrap_or(false); 571 + let enumerable = desc_data 572 + .properties 573 + .get("enumerable") 574 + .map(|p| p.value.to_boolean()) 575 + .unwrap_or(false); 576 + let configurable = desc_data 577 + .properties 578 + .get("configurable") 579 + .map(|p| p.value.to_boolean()) 580 + .unwrap_or(false); 581 + (value, writable, enumerable, configurable) 582 + } 583 + _ => (Value::Undefined, false, false, false), 584 + } 585 + }; 586 + 587 + if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 588 + data.properties.insert( 589 + key, 590 + Property { 591 + value, 592 + writable, 593 + enumerable, 594 + configurable, 595 + }, 596 + ); 597 + } 598 + Ok(Value::Object(obj_ref)) 599 + } 600 + 601 + fn object_freeze(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 602 + let obj_ref = match args.first() { 603 + Some(Value::Object(r)) => *r, 604 + Some(other) => return Ok(other.clone()), 605 + None => return Ok(Value::Undefined), 606 + }; 607 + if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 608 + data.extensible = false; 609 + for prop in data.properties.values_mut() { 610 + prop.writable = false; 611 + prop.configurable = false; 612 + } 613 + } 614 + Ok(Value::Object(obj_ref)) 615 + } 616 + 617 + fn object_seal(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 618 + let obj_ref = match args.first() { 619 + Some(Value::Object(r)) => *r, 620 + Some(other) => return Ok(other.clone()), 621 + None => return Ok(Value::Undefined), 622 + }; 623 + if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 624 + data.extensible = false; 625 + for prop in data.properties.values_mut() { 626 + prop.configurable = false; 627 + } 628 + } 629 + Ok(Value::Object(obj_ref)) 630 + } 631 + 632 + fn object_is_frozen(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 633 + let obj_ref = match args.first() { 634 + Some(Value::Object(r)) => *r, 635 + _ => return Ok(Value::Boolean(true)), 636 + }; 637 + match ctx.gc.get(obj_ref) { 638 + Some(HeapObject::Object(data)) => { 639 + if data.extensible { 640 + return Ok(Value::Boolean(false)); 641 + } 642 + let frozen = data 643 + .properties 644 + .values() 645 + .all(|p| !p.writable && !p.configurable); 646 + Ok(Value::Boolean(frozen)) 647 + } 648 + _ => Ok(Value::Boolean(true)), 649 + } 650 + } 651 + 652 + fn object_is_sealed(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 653 + let obj_ref = match args.first() { 654 + Some(Value::Object(r)) => *r, 655 + _ => return Ok(Value::Boolean(true)), 656 + }; 657 + match ctx.gc.get(obj_ref) { 658 + Some(HeapObject::Object(data)) => { 659 + if data.extensible { 660 + return Ok(Value::Boolean(false)); 661 + } 662 + let sealed = data.properties.values().all(|p| !p.configurable); 663 + Ok(Value::Boolean(sealed)) 664 + } 665 + _ => Ok(Value::Boolean(true)), 666 + } 667 + } 668 + 669 + fn object_from_entries(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 670 + let arr_ref = match args.first() { 671 + Some(Value::Object(r)) => *r, 672 + _ => { 673 + return Err(RuntimeError::type_error( 674 + "Object.fromEntries requires an iterable", 675 + )) 676 + } 677 + }; 678 + let len = array_length(ctx.gc, arr_ref); 679 + let mut obj = ObjectData::new(); 680 + for i in 0..len { 681 + let pair_val = array_get(ctx.gc, arr_ref, i); 682 + if let Value::Object(pair_ref) = pair_val { 683 + let key = array_get(ctx.gc, pair_ref, 0); 684 + let val = array_get(ctx.gc, pair_ref, 1); 685 + let key_str = key.to_js_string(ctx.gc); 686 + obj.properties.insert(key_str, Property::data(val)); 687 + } 688 + } 689 + Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj)))) 690 + } 691 + 692 + fn object_has_own(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 693 + let obj_ref = match args.first() { 694 + Some(Value::Object(r)) => *r, 695 + _ => return Ok(Value::Boolean(false)), 696 + }; 697 + let key = args 698 + .get(1) 699 + .map(|v| v.to_js_string(ctx.gc)) 700 + .unwrap_or_default(); 701 + match ctx.gc.get(obj_ref) { 702 + Some(HeapObject::Object(data)) => Ok(Value::Boolean(data.properties.contains_key(&key))), 703 + _ => Ok(Value::Boolean(false)), 704 + } 705 + } 706 + 707 + // ── Array helpers ──────────────────────────────────────────── 708 + 709 + /// Create a JS array from a slice of string values. 710 + fn make_string_array(gc: &mut Gc<HeapObject>, items: &[String]) -> Value { 711 + let mut obj = ObjectData::new(); 712 + for (i, s) in items.iter().enumerate() { 713 + obj.properties 714 + .insert(i.to_string(), Property::data(Value::String(s.clone()))); 715 + } 716 + obj.properties.insert( 717 + "length".to_string(), 718 + Property { 719 + value: Value::Number(items.len() as f64), 720 + writable: true, 721 + enumerable: false, 722 + configurable: false, 723 + }, 724 + ); 725 + Value::Object(gc.alloc(HeapObject::Object(obj))) 726 + } 727 + 728 + /// Create a JS array from a slice of Values. 729 + fn make_value_array(gc: &mut Gc<HeapObject>, items: &[Value]) -> Value { 730 + let mut obj = ObjectData::new(); 731 + for (i, v) in items.iter().enumerate() { 732 + obj.properties 733 + .insert(i.to_string(), Property::data(v.clone())); 734 + } 735 + obj.properties.insert( 736 + "length".to_string(), 737 + Property { 738 + value: Value::Number(items.len() as f64), 739 + writable: true, 740 + enumerable: false, 741 + configurable: false, 742 + }, 743 + ); 744 + Value::Object(gc.alloc(HeapObject::Object(obj))) 745 + } 746 + 747 + // ── Array.prototype ────────────────────────────────────────── 748 + 749 + fn init_array_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 750 + let push = make_native(gc, "push", array_push); 751 + set_builtin_prop(gc, proto, "push", Value::Function(push)); 752 + 753 + let pop = make_native(gc, "pop", array_pop); 754 + set_builtin_prop(gc, proto, "pop", Value::Function(pop)); 755 + 756 + let shift = make_native(gc, "shift", array_shift); 757 + set_builtin_prop(gc, proto, "shift", Value::Function(shift)); 758 + 759 + let unshift = make_native(gc, "unshift", array_unshift); 760 + set_builtin_prop(gc, proto, "unshift", Value::Function(unshift)); 761 + 762 + let index_of = make_native(gc, "indexOf", array_index_of); 763 + set_builtin_prop(gc, proto, "indexOf", Value::Function(index_of)); 764 + 765 + let last_index_of = make_native(gc, "lastIndexOf", array_last_index_of); 766 + set_builtin_prop(gc, proto, "lastIndexOf", Value::Function(last_index_of)); 767 + 768 + let includes = make_native(gc, "includes", array_includes); 769 + set_builtin_prop(gc, proto, "includes", Value::Function(includes)); 770 + 771 + let join = make_native(gc, "join", array_join); 772 + set_builtin_prop(gc, proto, "join", Value::Function(join)); 773 + 774 + let slice = make_native(gc, "slice", array_slice); 775 + set_builtin_prop(gc, proto, "slice", Value::Function(slice)); 776 + 777 + let concat = make_native(gc, "concat", array_concat); 778 + set_builtin_prop(gc, proto, "concat", Value::Function(concat)); 779 + 780 + let reverse = make_native(gc, "reverse", array_reverse); 781 + set_builtin_prop(gc, proto, "reverse", Value::Function(reverse)); 782 + 783 + let splice = make_native(gc, "splice", array_splice); 784 + set_builtin_prop(gc, proto, "splice", Value::Function(splice)); 785 + 786 + let fill = make_native(gc, "fill", array_fill); 787 + set_builtin_prop(gc, proto, "fill", Value::Function(fill)); 788 + 789 + let to_string = make_native(gc, "toString", array_to_string); 790 + set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 791 + 792 + let at = make_native(gc, "at", array_at); 793 + set_builtin_prop(gc, proto, "at", Value::Function(at)); 794 + } 795 + 796 + fn array_push(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 797 + let obj_ref = match ctx.this.gc_ref() { 798 + Some(r) => r, 799 + None => return Err(RuntimeError::type_error("push called on non-object")), 800 + }; 801 + let mut len = array_length(ctx.gc, obj_ref); 802 + for val in args { 803 + array_set(ctx.gc, obj_ref, len, val.clone()); 804 + len += 1; 805 + } 806 + set_array_length(ctx.gc, obj_ref, len); 807 + Ok(Value::Number(len as f64)) 808 + } 809 + 810 + fn array_pop(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 811 + let _ = args; 812 + let obj_ref = match ctx.this.gc_ref() { 813 + Some(r) => r, 814 + None => return Err(RuntimeError::type_error("pop called on non-object")), 815 + }; 816 + let len = array_length(ctx.gc, obj_ref); 817 + if len == 0 { 818 + return Ok(Value::Undefined); 819 + } 820 + let val = array_get(ctx.gc, obj_ref, len - 1); 821 + // Remove the last element. 822 + if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 823 + data.properties.remove(&(len - 1).to_string()); 824 + } 825 + set_array_length(ctx.gc, obj_ref, len - 1); 826 + Ok(val) 827 + } 828 + 829 + fn array_shift(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 830 + let _ = args; 831 + let obj_ref = match ctx.this.gc_ref() { 832 + Some(r) => r, 833 + None => return Err(RuntimeError::type_error("shift called on non-object")), 834 + }; 835 + let len = array_length(ctx.gc, obj_ref); 836 + if len == 0 { 837 + return Ok(Value::Undefined); 838 + } 839 + let first = array_get(ctx.gc, obj_ref, 0); 840 + // Shift all elements down. 841 + let mut vals = Vec::with_capacity(len - 1); 842 + for i in 1..len { 843 + vals.push(array_get(ctx.gc, obj_ref, i)); 844 + } 845 + if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 846 + // Remove all numeric keys. 847 + for i in 0..len { 848 + data.properties.remove(&i.to_string()); 849 + } 850 + // Re-insert shifted values. 851 + for (i, v) in vals.into_iter().enumerate() { 852 + data.properties.insert(i.to_string(), Property::data(v)); 853 + } 854 + } 855 + set_array_length(ctx.gc, obj_ref, len - 1); 856 + Ok(first) 857 + } 858 + 859 + fn array_unshift(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 860 + let obj_ref = match ctx.this.gc_ref() { 861 + Some(r) => r, 862 + None => return Err(RuntimeError::type_error("unshift called on non-object")), 863 + }; 864 + let len = array_length(ctx.gc, obj_ref); 865 + let insert_count = args.len(); 866 + // Read existing values. 867 + let mut existing = Vec::with_capacity(len); 868 + for i in 0..len { 869 + existing.push(array_get(ctx.gc, obj_ref, i)); 870 + } 871 + // Write new values at the start, then existing values after. 872 + if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 873 + for i in 0..len { 874 + data.properties.remove(&i.to_string()); 875 + } 876 + for (i, v) in args.iter().enumerate() { 877 + data.properties 878 + .insert(i.to_string(), Property::data(v.clone())); 879 + } 880 + for (i, v) in existing.into_iter().enumerate() { 881 + data.properties 882 + .insert((i + insert_count).to_string(), Property::data(v)); 883 + } 884 + } 885 + let new_len = len + insert_count; 886 + set_array_length(ctx.gc, obj_ref, new_len); 887 + Ok(Value::Number(new_len as f64)) 888 + } 889 + 890 + fn array_index_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 891 + let obj_ref = match ctx.this.gc_ref() { 892 + Some(r) => r, 893 + None => return Ok(Value::Number(-1.0)), 894 + }; 895 + let search = args.first().cloned().unwrap_or(Value::Undefined); 896 + let from = args 897 + .get(1) 898 + .map(|v| { 899 + let n = v.to_number() as i64; 900 + let len = array_length(ctx.gc, obj_ref) as i64; 901 + if n < 0 { 902 + (len + n).max(0) as usize 903 + } else { 904 + n as usize 905 + } 906 + }) 907 + .unwrap_or(0); 908 + let len = array_length(ctx.gc, obj_ref); 909 + for i in from..len { 910 + let elem = array_get(ctx.gc, obj_ref, i); 911 + if strict_eq_values(&elem, &search) { 912 + return Ok(Value::Number(i as f64)); 913 + } 914 + } 915 + Ok(Value::Number(-1.0)) 916 + } 917 + 918 + fn array_last_index_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 919 + let obj_ref = match ctx.this.gc_ref() { 920 + Some(r) => r, 921 + None => return Ok(Value::Number(-1.0)), 922 + }; 923 + let search = args.first().cloned().unwrap_or(Value::Undefined); 924 + let len = array_length(ctx.gc, obj_ref); 925 + if len == 0 { 926 + return Ok(Value::Number(-1.0)); 927 + } 928 + let from = args 929 + .get(1) 930 + .map(|v| { 931 + let n = v.to_number() as i64; 932 + if n < 0 { 933 + (len as i64 + n) as usize 934 + } else { 935 + (n as usize).min(len - 1) 936 + } 937 + }) 938 + .unwrap_or(len - 1); 939 + for i in (0..=from).rev() { 940 + let elem = array_get(ctx.gc, obj_ref, i); 941 + if strict_eq_values(&elem, &search) { 942 + return Ok(Value::Number(i as f64)); 943 + } 944 + } 945 + Ok(Value::Number(-1.0)) 946 + } 947 + 948 + fn array_includes(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 949 + let obj_ref = match ctx.this.gc_ref() { 950 + Some(r) => r, 951 + None => return Ok(Value::Boolean(false)), 952 + }; 953 + let search = args.first().cloned().unwrap_or(Value::Undefined); 954 + let len = array_length(ctx.gc, obj_ref); 955 + let from = args 956 + .get(1) 957 + .map(|v| { 958 + let n = v.to_number() as i64; 959 + if n < 0 { 960 + (len as i64 + n).max(0) as usize 961 + } else { 962 + n as usize 963 + } 964 + }) 965 + .unwrap_or(0); 966 + for i in from..len { 967 + let elem = array_get(ctx.gc, obj_ref, i); 968 + // includes uses SameValueZero (like === but NaN === NaN). 969 + if same_value_zero(&elem, &search) { 970 + return Ok(Value::Boolean(true)); 971 + } 972 + } 973 + Ok(Value::Boolean(false)) 974 + } 975 + 976 + fn array_join(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 977 + let obj_ref = match ctx.this.gc_ref() { 978 + Some(r) => r, 979 + None => return Ok(Value::String(String::new())), 980 + }; 981 + let sep = args 982 + .first() 983 + .map(|v| { 984 + if matches!(v, Value::Undefined) { 985 + ",".to_string() 986 + } else { 987 + v.to_js_string(ctx.gc) 988 + } 989 + }) 990 + .unwrap_or_else(|| ",".to_string()); 991 + let len = array_length(ctx.gc, obj_ref); 992 + let mut parts = Vec::with_capacity(len); 993 + for i in 0..len { 994 + let elem = array_get(ctx.gc, obj_ref, i); 995 + if matches!(elem, Value::Undefined | Value::Null) { 996 + parts.push(String::new()); 997 + } else { 998 + parts.push(elem.to_js_string(ctx.gc)); 999 + } 1000 + } 1001 + Ok(Value::String(parts.join(&sep))) 1002 + } 1003 + 1004 + fn array_slice(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1005 + let obj_ref = match ctx.this.gc_ref() { 1006 + Some(r) => r, 1007 + None => return Ok(Value::Undefined), 1008 + }; 1009 + let len = array_length(ctx.gc, obj_ref) as i64; 1010 + let start = args 1011 + .first() 1012 + .map(|v| { 1013 + let n = v.to_number() as i64; 1014 + if n < 0 { 1015 + (len + n).max(0) 1016 + } else { 1017 + n.min(len) 1018 + } 1019 + }) 1020 + .unwrap_or(0) as usize; 1021 + let end = args 1022 + .get(1) 1023 + .map(|v| { 1024 + if matches!(v, Value::Undefined) { 1025 + len 1026 + } else { 1027 + let n = v.to_number() as i64; 1028 + if n < 0 { 1029 + (len + n).max(0) 1030 + } else { 1031 + n.min(len) 1032 + } 1033 + } 1034 + }) 1035 + .unwrap_or(len) as usize; 1036 + 1037 + let mut items = Vec::new(); 1038 + for i in start..end { 1039 + items.push(array_get(ctx.gc, obj_ref, i)); 1040 + } 1041 + Ok(make_value_array(ctx.gc, &items)) 1042 + } 1043 + 1044 + fn array_concat(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1045 + let obj_ref = match ctx.this.gc_ref() { 1046 + Some(r) => r, 1047 + None => return Ok(Value::Undefined), 1048 + }; 1049 + let mut items = Vec::new(); 1050 + // First, add elements from `this`. 1051 + let len = array_length(ctx.gc, obj_ref); 1052 + for i in 0..len { 1053 + items.push(array_get(ctx.gc, obj_ref, i)); 1054 + } 1055 + // Then add from each argument. 1056 + for arg in args { 1057 + match arg { 1058 + Value::Object(r) => { 1059 + let arg_len = array_length(ctx.gc, *r); 1060 + if arg_len > 0 { 1061 + for i in 0..arg_len { 1062 + items.push(array_get(ctx.gc, *r, i)); 1063 + } 1064 + } else { 1065 + items.push(arg.clone()); 1066 + } 1067 + } 1068 + _ => items.push(arg.clone()), 1069 + } 1070 + } 1071 + Ok(make_value_array(ctx.gc, &items)) 1072 + } 1073 + 1074 + fn array_reverse(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1075 + let _ = args; 1076 + let obj_ref = match ctx.this.gc_ref() { 1077 + Some(r) => r, 1078 + None => return Ok(Value::Undefined), 1079 + }; 1080 + let len = array_length(ctx.gc, obj_ref); 1081 + // Read all values. 1082 + let mut vals: Vec<Value> = (0..len).map(|i| array_get(ctx.gc, obj_ref, i)).collect(); 1083 + vals.reverse(); 1084 + // Write back. 1085 + if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 1086 + for (i, v) in vals.into_iter().enumerate() { 1087 + data.properties.insert(i.to_string(), Property::data(v)); 1088 + } 1089 + } 1090 + Ok(ctx.this.clone()) 1091 + } 1092 + 1093 + fn array_splice(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1094 + let obj_ref = match ctx.this.gc_ref() { 1095 + Some(r) => r, 1096 + None => return Ok(Value::Undefined), 1097 + }; 1098 + let len = array_length(ctx.gc, obj_ref) as i64; 1099 + let start = args 1100 + .first() 1101 + .map(|v| { 1102 + let n = v.to_number() as i64; 1103 + if n < 0 { 1104 + (len + n).max(0) 1105 + } else { 1106 + n.min(len) 1107 + } 1108 + }) 1109 + .unwrap_or(0) as usize; 1110 + let delete_count = args 1111 + .get(1) 1112 + .map(|v| { 1113 + let n = v.to_number() as i64; 1114 + n.max(0).min(len - start as i64) as usize 1115 + }) 1116 + .unwrap_or((len - start as i64).max(0) as usize); 1117 + let insert_items: Vec<Value> = args.iter().skip(2).cloned().collect(); 1118 + 1119 + // Collect current values. 1120 + let all_vals: Vec<Value> = (0..len as usize) 1121 + .map(|i| array_get(ctx.gc, obj_ref, i)) 1122 + .collect(); 1123 + 1124 + // Build removed slice. 1125 + let removed: Vec<Value> = all_vals[start..start + delete_count].to_vec(); 1126 + 1127 + // Build new array content. 1128 + let mut new_vals = Vec::new(); 1129 + new_vals.extend_from_slice(&all_vals[..start]); 1130 + new_vals.extend(insert_items); 1131 + new_vals.extend_from_slice(&all_vals[start + delete_count..]); 1132 + 1133 + // Write back. 1134 + if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 1135 + // Remove all numeric keys. 1136 + let old_keys: Vec<String> = data 1137 + .properties 1138 + .keys() 1139 + .filter(|k| k.parse::<usize>().is_ok()) 1140 + .cloned() 1141 + .collect(); 1142 + for k in old_keys { 1143 + data.properties.remove(&k); 1144 + } 1145 + // Write new values. 1146 + for (i, v) in new_vals.iter().enumerate() { 1147 + data.properties 1148 + .insert(i.to_string(), Property::data(v.clone())); 1149 + } 1150 + } 1151 + set_array_length(ctx.gc, obj_ref, new_vals.len()); 1152 + Ok(make_value_array(ctx.gc, &removed)) 1153 + } 1154 + 1155 + fn array_fill(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1156 + let obj_ref = match ctx.this.gc_ref() { 1157 + Some(r) => r, 1158 + None => return Ok(Value::Undefined), 1159 + }; 1160 + let val = args.first().cloned().unwrap_or(Value::Undefined); 1161 + let len = array_length(ctx.gc, obj_ref) as i64; 1162 + let start = args 1163 + .get(1) 1164 + .map(|v| { 1165 + let n = v.to_number() as i64; 1166 + if n < 0 { 1167 + (len + n).max(0) 1168 + } else { 1169 + n.min(len) 1170 + } 1171 + }) 1172 + .unwrap_or(0) as usize; 1173 + let end = args 1174 + .get(2) 1175 + .map(|v| { 1176 + if matches!(v, Value::Undefined) { 1177 + len 1178 + } else { 1179 + let n = v.to_number() as i64; 1180 + if n < 0 { 1181 + (len + n).max(0) 1182 + } else { 1183 + n.min(len) 1184 + } 1185 + } 1186 + }) 1187 + .unwrap_or(len) as usize; 1188 + for i in start..end { 1189 + array_set(ctx.gc, obj_ref, i, val.clone()); 1190 + } 1191 + Ok(ctx.this.clone()) 1192 + } 1193 + 1194 + fn array_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1195 + // Array.prototype.toString is the same as join(","). 1196 + array_join( 1197 + &[Value::String(",".to_string())], 1198 + &mut NativeContext { 1199 + gc: ctx.gc, 1200 + this: ctx.this.clone(), 1201 + }, 1202 + ) 1203 + .or_else(|_| Ok(Value::String(String::new()))) 1204 + } 1205 + 1206 + fn array_at(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1207 + let obj_ref = match ctx.this.gc_ref() { 1208 + Some(r) => r, 1209 + None => return Ok(Value::Undefined), 1210 + }; 1211 + let len = array_length(ctx.gc, obj_ref) as i64; 1212 + let index = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 1213 + let actual = if index < 0 { len + index } else { index }; 1214 + if actual < 0 || actual >= len { 1215 + Ok(Value::Undefined) 1216 + } else { 1217 + Ok(array_get(ctx.gc, obj_ref, actual as usize)) 1218 + } 1219 + } 1220 + 1221 + // ── Array constructor + static methods ─────────────────────── 1222 + 1223 + fn init_array_constructor(gc: &mut Gc<HeapObject>, arr_proto: GcRef) -> GcRef { 1224 + let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { 1225 + name: "Array".to_string(), 1226 + kind: FunctionKind::Native(NativeFunc { 1227 + callback: array_constructor, 1228 + }), 1229 + prototype_obj: Some(arr_proto), 1230 + properties: HashMap::new(), 1231 + upvalues: Vec::new(), 1232 + }))); 1233 + 1234 + let is_array = make_native(gc, "isArray", array_is_array); 1235 + set_func_prop(gc, ctor, "isArray", Value::Function(is_array)); 1236 + 1237 + let from = make_native(gc, "from", array_from); 1238 + set_func_prop(gc, ctor, "from", Value::Function(from)); 1239 + 1240 + let of = make_native(gc, "of", array_of); 1241 + set_func_prop(gc, ctor, "of", Value::Function(of)); 1242 + 1243 + ctor 1244 + } 1245 + 1246 + fn array_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1247 + if args.len() == 1 { 1248 + if let Value::Number(n) = &args[0] { 1249 + let len = *n as usize; 1250 + let mut obj = ObjectData::new(); 1251 + obj.properties.insert( 1252 + "length".to_string(), 1253 + Property { 1254 + value: Value::Number(len as f64), 1255 + writable: true, 1256 + enumerable: false, 1257 + configurable: false, 1258 + }, 1259 + ); 1260 + return Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj)))); 1261 + } 1262 + } 1263 + Ok(make_value_array(ctx.gc, args)) 1264 + } 1265 + 1266 + fn array_is_array(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1267 + // An "array" is an object that has a numeric length property. 1268 + // This is a simplified check — real JS uses an internal [[Class]] slot. 1269 + match args.first() { 1270 + Some(Value::Object(r)) => match ctx.gc.get(*r) { 1271 + Some(HeapObject::Object(data)) => { 1272 + Ok(Value::Boolean(data.properties.contains_key("length"))) 1273 + } 1274 + _ => Ok(Value::Boolean(false)), 1275 + }, 1276 + _ => Ok(Value::Boolean(false)), 1277 + } 1278 + } 1279 + 1280 + fn array_from(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1281 + let iterable = args.first().cloned().unwrap_or(Value::Undefined); 1282 + match iterable { 1283 + Value::Object(r) => { 1284 + let len = array_length(ctx.gc, r); 1285 + let mut items = Vec::with_capacity(len); 1286 + for i in 0..len { 1287 + items.push(array_get(ctx.gc, r, i)); 1288 + } 1289 + Ok(make_value_array(ctx.gc, &items)) 1290 + } 1291 + Value::String(s) => { 1292 + let chars: Vec<Value> = s.chars().map(|c| Value::String(c.to_string())).collect(); 1293 + Ok(make_value_array(ctx.gc, &chars)) 1294 + } 1295 + _ => Ok(make_value_array(ctx.gc, &[])), 1296 + } 1297 + } 1298 + 1299 + fn array_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1300 + Ok(make_value_array(ctx.gc, args)) 1301 + } 1302 + 1303 + // ── Error constructors ─────────────────────────────────────── 1304 + 1305 + fn init_error_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 1306 + let to_string = make_native(gc, "toString", error_proto_to_string); 1307 + set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 1308 + } 1309 + 1310 + fn init_error_constructors(vm: &mut Vm, obj_proto: GcRef, err_proto: GcRef) { 1311 + // Base Error. 1312 + let error_ctor = make_error_constructor(&mut vm.gc, "Error", err_proto); 1313 + vm.set_global("Error", Value::Function(error_ctor)); 1314 + 1315 + // TypeError. 1316 + let te_proto = make_error_subclass_proto(&mut vm.gc, "TypeError", err_proto, obj_proto); 1317 + let te_ctor = make_error_constructor(&mut vm.gc, "TypeError", te_proto); 1318 + vm.set_global("TypeError", Value::Function(te_ctor)); 1319 + 1320 + // ReferenceError. 1321 + let re_proto = make_error_subclass_proto(&mut vm.gc, "ReferenceError", err_proto, obj_proto); 1322 + let re_ctor = make_error_constructor(&mut vm.gc, "ReferenceError", re_proto); 1323 + vm.set_global("ReferenceError", Value::Function(re_ctor)); 1324 + 1325 + // SyntaxError. 1326 + let se_proto = make_error_subclass_proto(&mut vm.gc, "SyntaxError", err_proto, obj_proto); 1327 + let se_ctor = make_error_constructor(&mut vm.gc, "SyntaxError", se_proto); 1328 + vm.set_global("SyntaxError", Value::Function(se_ctor)); 1329 + 1330 + // RangeError. 1331 + let rae_proto = make_error_subclass_proto(&mut vm.gc, "RangeError", err_proto, obj_proto); 1332 + let rae_ctor = make_error_constructor(&mut vm.gc, "RangeError", rae_proto); 1333 + vm.set_global("RangeError", Value::Function(rae_ctor)); 1334 + 1335 + // URIError. 1336 + let ue_proto = make_error_subclass_proto(&mut vm.gc, "URIError", err_proto, obj_proto); 1337 + let ue_ctor = make_error_constructor(&mut vm.gc, "URIError", ue_proto); 1338 + vm.set_global("URIError", Value::Function(ue_ctor)); 1339 + 1340 + // EvalError. 1341 + let ee_proto = make_error_subclass_proto(&mut vm.gc, "EvalError", err_proto, obj_proto); 1342 + let ee_ctor = make_error_constructor(&mut vm.gc, "EvalError", ee_proto); 1343 + vm.set_global("EvalError", Value::Function(ee_ctor)); 1344 + } 1345 + 1346 + fn make_error_subclass_proto( 1347 + gc: &mut Gc<HeapObject>, 1348 + name: &str, 1349 + parent_proto: GcRef, 1350 + _obj_proto: GcRef, 1351 + ) -> GcRef { 1352 + let mut data = ObjectData::new(); 1353 + data.prototype = Some(parent_proto); 1354 + data.properties.insert( 1355 + "name".to_string(), 1356 + Property::builtin(Value::String(name.to_string())), 1357 + ); 1358 + data.properties.insert( 1359 + "message".to_string(), 1360 + Property::builtin(Value::String(String::new())), 1361 + ); 1362 + gc.alloc(HeapObject::Object(data)) 1363 + } 1364 + 1365 + fn make_error_constructor(gc: &mut Gc<HeapObject>, name: &str, proto: GcRef) -> GcRef { 1366 + gc.alloc(HeapObject::Function(Box::new(FunctionData { 1367 + name: name.to_string(), 1368 + kind: FunctionKind::Native(NativeFunc { 1369 + callback: error_constructor, 1370 + }), 1371 + prototype_obj: Some(proto), 1372 + properties: HashMap::new(), 1373 + upvalues: Vec::new(), 1374 + }))) 1375 + } 1376 + 1377 + fn error_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1378 + let message = args 1379 + .first() 1380 + .map(|v| v.to_js_string(ctx.gc)) 1381 + .unwrap_or_default(); 1382 + let mut obj = ObjectData::new(); 1383 + obj.properties.insert( 1384 + "message".to_string(), 1385 + Property::data(Value::String(message)), 1386 + ); 1387 + // The "name" property comes from the prototype chain. 1388 + Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj)))) 1389 + } 1390 + 1391 + fn error_proto_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1392 + let obj_ref = match ctx.this.gc_ref() { 1393 + Some(r) => r, 1394 + None => return Ok(Value::String("Error".to_string())), 1395 + }; 1396 + let name = match ctx.gc.get(obj_ref) { 1397 + Some(HeapObject::Object(data)) => data 1398 + .properties 1399 + .get("name") 1400 + .map(|p| p.value.to_js_string(ctx.gc)) 1401 + .unwrap_or_else(|| "Error".to_string()), 1402 + _ => "Error".to_string(), 1403 + }; 1404 + let message = match ctx.gc.get(obj_ref) { 1405 + Some(HeapObject::Object(data)) => data 1406 + .properties 1407 + .get("message") 1408 + .map(|p| p.value.to_js_string(ctx.gc)) 1409 + .unwrap_or_default(), 1410 + _ => String::new(), 1411 + }; 1412 + if message.is_empty() { 1413 + Ok(Value::String(name)) 1414 + } else { 1415 + Ok(Value::String(format!("{name}: {message}"))) 1416 + } 1417 + } 1418 + 1419 + // ── Global utility functions ───────────────────────────────── 1420 + 1421 + fn init_global_functions(vm: &mut Vm) { 1422 + vm.define_native("parseInt", parse_int); 1423 + vm.define_native("parseFloat", parse_float); 1424 + vm.define_native("isNaN", is_nan); 1425 + vm.define_native("isFinite", is_finite); 1426 + } 1427 + 1428 + fn parse_int(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1429 + let s = args 1430 + .first() 1431 + .map(|v| v.to_js_string(ctx.gc)) 1432 + .unwrap_or_default(); 1433 + let radix = args.get(1).map(|v| v.to_number() as u32).unwrap_or(10); 1434 + let s = s.trim(); 1435 + if s.is_empty() { 1436 + return Ok(Value::Number(f64::NAN)); 1437 + } 1438 + let (neg, s) = if let Some(rest) = s.strip_prefix('-') { 1439 + (true, rest) 1440 + } else if let Some(rest) = s.strip_prefix('+') { 1441 + (false, rest) 1442 + } else { 1443 + (false, s) 1444 + }; 1445 + // Auto-detect hex. 1446 + let (radix, s) = if radix == 0 || radix == 16 { 1447 + if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { 1448 + (16, rest) 1449 + } else { 1450 + (if radix == 0 { 10 } else { radix }, s) 1451 + } 1452 + } else { 1453 + (radix, s) 1454 + }; 1455 + if !(2..=36).contains(&radix) { 1456 + return Ok(Value::Number(f64::NAN)); 1457 + } 1458 + // Parse digits until invalid. 1459 + let mut result: f64 = 0.0; 1460 + let mut found = false; 1461 + for c in s.chars() { 1462 + let digit = match c.to_digit(radix) { 1463 + Some(d) => d, 1464 + None => break, 1465 + }; 1466 + result = result * radix as f64 + digit as f64; 1467 + found = true; 1468 + } 1469 + if !found { 1470 + return Ok(Value::Number(f64::NAN)); 1471 + } 1472 + if neg { 1473 + result = -result; 1474 + } 1475 + Ok(Value::Number(result)) 1476 + } 1477 + 1478 + fn parse_float(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1479 + let s = args 1480 + .first() 1481 + .map(|v| v.to_js_string(ctx.gc)) 1482 + .unwrap_or_default(); 1483 + let s = s.trim(); 1484 + match s.parse::<f64>() { 1485 + Ok(n) => Ok(Value::Number(n)), 1486 + Err(_) => Ok(Value::Number(f64::NAN)), 1487 + } 1488 + } 1489 + 1490 + fn is_nan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1491 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 1492 + Ok(Value::Boolean(n.is_nan())) 1493 + } 1494 + 1495 + fn is_finite(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1496 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 1497 + Ok(Value::Boolean(n.is_finite())) 1498 + } 1499 + 1500 + // ── Strict equality (for Array.indexOf etc.) ───────────────── 1501 + 1502 + fn strict_eq_values(a: &Value, b: &Value) -> bool { 1503 + match (a, b) { 1504 + (Value::Undefined, Value::Undefined) => true, 1505 + (Value::Null, Value::Null) => true, 1506 + (Value::Number(x), Value::Number(y)) => x == y, 1507 + (Value::String(x), Value::String(y)) => x == y, 1508 + (Value::Boolean(x), Value::Boolean(y)) => x == y, 1509 + (Value::Object(x), Value::Object(y)) => x == y, 1510 + (Value::Function(x), Value::Function(y)) => x == y, 1511 + _ => false, 1512 + } 1513 + } 1514 + 1515 + /// SameValueZero (like === but NaN === NaN is true). 1516 + fn same_value_zero(a: &Value, b: &Value) -> bool { 1517 + match (a, b) { 1518 + (Value::Number(x), Value::Number(y)) => { 1519 + if x.is_nan() && y.is_nan() { 1520 + return true; 1521 + } 1522 + x == y 1523 + } 1524 + _ => strict_eq_values(a, b), 1525 + } 1526 + } 1527 + 1528 + // ── JS preamble for callback-based methods ─────────────────── 1529 + 1530 + fn init_js_preamble(vm: &mut Vm) { 1531 + // NOTE: All preamble methods capture `this` into a local `_t` variable 1532 + // because nested method calls (e.g. result.push()) clobber the global `this`. 1533 + let preamble = r#" 1534 + Array.prototype.forEach = function(cb) { 1535 + var _t = this; 1536 + for (var i = 0; i < _t.length; i = i + 1) { 1537 + cb(_t[i], i, _t); 1538 + } 1539 + }; 1540 + Array.prototype.map = function(cb) { 1541 + var _t = this; 1542 + var result = []; 1543 + for (var i = 0; i < _t.length; i = i + 1) { 1544 + result.push(cb(_t[i], i, _t)); 1545 + } 1546 + return result; 1547 + }; 1548 + Array.prototype.filter = function(cb) { 1549 + var _t = this; 1550 + var result = []; 1551 + for (var i = 0; i < _t.length; i = i + 1) { 1552 + if (cb(_t[i], i, _t)) { 1553 + result.push(_t[i]); 1554 + } 1555 + } 1556 + return result; 1557 + }; 1558 + Array.prototype.reduce = function(cb, init) { 1559 + var _t = this; 1560 + var acc = init; 1561 + var start = 0; 1562 + if (acc === undefined) { 1563 + acc = _t[0]; 1564 + start = 1; 1565 + } 1566 + for (var i = start; i < _t.length; i = i + 1) { 1567 + acc = cb(acc, _t[i], i, _t); 1568 + } 1569 + return acc; 1570 + }; 1571 + Array.prototype.reduceRight = function(cb, init) { 1572 + var _t = this; 1573 + var acc = init; 1574 + var start = _t.length - 1; 1575 + if (acc === undefined) { 1576 + acc = _t[start]; 1577 + start = start - 1; 1578 + } 1579 + for (var i = start; i >= 0; i = i - 1) { 1580 + acc = cb(acc, _t[i], i, _t); 1581 + } 1582 + return acc; 1583 + }; 1584 + Array.prototype.find = function(cb) { 1585 + var _t = this; 1586 + for (var i = 0; i < _t.length; i = i + 1) { 1587 + if (cb(_t[i], i, _t)) { return _t[i]; } 1588 + } 1589 + return undefined; 1590 + }; 1591 + Array.prototype.findIndex = function(cb) { 1592 + var _t = this; 1593 + for (var i = 0; i < _t.length; i = i + 1) { 1594 + if (cb(_t[i], i, _t)) { return i; } 1595 + } 1596 + return -1; 1597 + }; 1598 + Array.prototype.some = function(cb) { 1599 + var _t = this; 1600 + for (var i = 0; i < _t.length; i = i + 1) { 1601 + if (cb(_t[i], i, _t)) { return true; } 1602 + } 1603 + return false; 1604 + }; 1605 + Array.prototype.every = function(cb) { 1606 + var _t = this; 1607 + for (var i = 0; i < _t.length; i = i + 1) { 1608 + if (!cb(_t[i], i, _t)) { return false; } 1609 + } 1610 + return true; 1611 + }; 1612 + Array.prototype.sort = function(cmp) { 1613 + var _t = this; 1614 + var len = _t.length; 1615 + for (var i = 0; i < len; i = i + 1) { 1616 + for (var j = 0; j < len - i - 1; j = j + 1) { 1617 + var a = _t[j]; 1618 + var b = _t[j + 1]; 1619 + var order; 1620 + if (cmp) { 1621 + order = cmp(a, b); 1622 + } else { 1623 + var sa = "" + a; 1624 + var sb = "" + b; 1625 + if (sa > sb) { order = 1; } 1626 + else if (sa < sb) { order = -1; } 1627 + else { order = 0; } 1628 + } 1629 + if (order > 0) { 1630 + _t[j] = b; 1631 + _t[j + 1] = a; 1632 + } 1633 + } 1634 + } 1635 + return _t; 1636 + }; 1637 + Array.prototype.flat = function(depth) { 1638 + var _t = this; 1639 + if (depth === undefined) { depth = 1; } 1640 + var result = []; 1641 + for (var i = 0; i < _t.length; i = i + 1) { 1642 + var elem = _t[i]; 1643 + if (depth > 0 && Array.isArray(elem)) { 1644 + var sub = elem.flat(depth - 1); 1645 + for (var j = 0; j < sub.length; j = j + 1) { 1646 + result.push(sub[j]); 1647 + } 1648 + } else { 1649 + result.push(elem); 1650 + } 1651 + } 1652 + return result; 1653 + }; 1654 + Array.prototype.flatMap = function(cb) { 1655 + var _t = this; 1656 + return _t.map(cb).flat(); 1657 + }; 1658 + "#; 1659 + // Compile and execute the preamble. 1660 + let ast = match crate::parser::Parser::parse(preamble) { 1661 + Ok(ast) => ast, 1662 + Err(_) => return, // Silently skip if preamble fails to parse. 1663 + }; 1664 + let func = match crate::compiler::compile(&ast) { 1665 + Ok(func) => func, 1666 + Err(_) => return, 1667 + }; 1668 + let _ = vm.execute(&func); 1669 + }
+15
crates/js/src/compiler.rs
··· 2101 2101 fc.free_reg(val_reg); 2102 2102 } 2103 2103 } 2104 + // Set length property to the number of elements. 2105 + if !elements.is_empty() { 2106 + let len_name = fc.builder.add_name("length"); 2107 + let len_reg = fc.alloc_reg(); 2108 + if elements.len() <= 127 { 2109 + fc.builder.emit_load_int8(len_reg, elements.len() as i8); 2110 + } else { 2111 + let ci = fc 2112 + .builder 2113 + .add_constant(Constant::Number(elements.len() as f64)); 2114 + fc.builder.emit_reg_u16(Op::LoadConst, len_reg, ci); 2115 + } 2116 + fc.builder.emit_set_prop_name(dst, len_name, len_reg); 2117 + fc.free_reg(len_reg); 2118 + } 2104 2119 } 2105 2120 2106 2121 ExprKind::Object(properties) => {
+1
crates/js/src/lib.rs
··· 1 1 //! JavaScript engine — lexer, parser, bytecode, register VM, GC, JIT (AArch64). 2 2 3 3 pub mod ast; 4 + pub mod builtins; 4 5 pub mod bytecode; 5 6 pub mod compiler; 6 7 pub mod gc;
+522 -17
crates/js/src/vm.rs
··· 146 146 /// A native function callable from JS. 147 147 #[derive(Clone)] 148 148 pub struct NativeFunc { 149 - pub callback: fn(&[Value]) -> Result<Value, RuntimeError>, 149 + pub callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>, 150 + } 151 + 152 + /// Context passed to native functions, providing GC access and `this` binding. 153 + pub struct NativeContext<'a> { 154 + pub gc: &'a mut Gc<HeapObject>, 155 + pub this: Value, 150 156 } 151 157 152 158 // ── JS Value ────────────────────────────────────────────────── ··· 672 678 instruction_limit: Option<u64>, 673 679 /// Number of instructions executed so far. 674 680 instructions_executed: u64, 681 + /// Built-in Object.prototype (root of the prototype chain). 682 + pub object_prototype: Option<GcRef>, 683 + /// Built-in Array.prototype (set on newly created arrays). 684 + pub array_prototype: Option<GcRef>, 675 685 } 676 686 677 687 /// Maximum register file size. ··· 681 691 682 692 impl Vm { 683 693 pub fn new() -> Self { 684 - Self { 694 + let mut vm = Self { 685 695 registers: vec![Value::Undefined; 256], 686 696 frames: Vec::new(), 687 697 globals: HashMap::new(), 688 698 gc: Gc::new(), 689 699 instruction_limit: None, 690 700 instructions_executed: 0, 691 - } 701 + object_prototype: None, 702 + array_prototype: None, 703 + }; 704 + crate::builtins::init_builtins(&mut vm); 705 + vm 692 706 } 693 707 694 708 /// Set an instruction limit. The VM will return a RuntimeError after ··· 771 785 for &uv in &frame.upvalues { 772 786 roots.push(uv); 773 787 } 788 + } 789 + // Built-in prototype roots. 790 + if let Some(r) = self.object_prototype { 791 + roots.push(r); 792 + } 793 + if let Some(r) = self.array_prototype { 794 + roots.push(r); 774 795 } 775 796 roots 776 797 } ··· 1176 1197 }; 1177 1198 1178 1199 match call_info { 1179 - CallInfo::Native(callback) => match callback(&args) { 1180 - Ok(val) => { 1181 - self.registers[base + dst as usize] = val; 1182 - } 1183 - Err(err) => { 1184 - let err_val = err.to_value(&mut self.gc); 1185 - if !self.handle_exception(err_val) { 1186 - return Err(err); 1200 + CallInfo::Native(callback) => { 1201 + let this = self 1202 + .globals 1203 + .get("this") 1204 + .cloned() 1205 + .unwrap_or(Value::Undefined); 1206 + let mut ctx = NativeContext { 1207 + gc: &mut self.gc, 1208 + this, 1209 + }; 1210 + match callback(&args, &mut ctx) { 1211 + Ok(val) => { 1212 + self.registers[base + dst as usize] = val; 1213 + } 1214 + Err(err) => { 1215 + let err_val = err.to_value(&mut self.gc); 1216 + if !self.handle_exception(err_val) { 1217 + return Err(err); 1218 + } 1187 1219 } 1188 1220 } 1189 - }, 1221 + } 1190 1222 CallInfo::Bytecode(callee_func, callee_upvalues) => { 1191 1223 if self.frames.len() >= MAX_CALL_DEPTH { 1192 1224 let err = ··· 1360 1392 Op::CreateObject => { 1361 1393 let dst = Self::read_u8(&mut self.frames[fi]); 1362 1394 let base = self.frames[fi].base; 1363 - let gc_ref = self.gc.alloc(HeapObject::Object(ObjectData::new())); 1395 + let mut obj = ObjectData::new(); 1396 + obj.prototype = self.object_prototype; 1397 + let gc_ref = self.gc.alloc(HeapObject::Object(obj)); 1364 1398 self.registers[base + dst as usize] = Value::Object(gc_ref); 1365 1399 1366 1400 if self.gc.should_collect() { ··· 1372 1406 let dst = Self::read_u8(&mut self.frames[fi]); 1373 1407 let base = self.frames[fi].base; 1374 1408 let mut obj = ObjectData::new(); 1409 + obj.prototype = self.array_prototype; 1375 1410 obj.properties.insert( 1376 1411 "length".to_string(), 1377 1412 Property { ··· 1666 1701 pub fn define_native( 1667 1702 &mut self, 1668 1703 name: &str, 1669 - callback: fn(&[Value]) -> Result<Value, RuntimeError>, 1704 + callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>, 1670 1705 ) { 1671 1706 let gc_ref = self.gc.alloc(HeapObject::Function(Box::new(FunctionData { 1672 1707 name: name.to_string(), ··· 1698 1733 1699 1734 /// Internal enum to avoid holding a GC borrow across the call setup. 1700 1735 enum CallInfo { 1701 - Native(fn(&[Value]) -> Result<Value, RuntimeError>), 1736 + Native(fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>), 1702 1737 Bytecode(Function, Vec<GcRef>), 1703 1738 } 1704 1739 ··· 1894 1929 let func = b.finish(); 1895 1930 1896 1931 let mut vm = Vm::new(); 1897 - vm.define_native("double", |args| { 1932 + vm.define_native("double", |args, _ctx| { 1898 1933 let n = args.first().unwrap_or(&Value::Undefined).to_number(); 1899 1934 Ok(Value::Number(n * 2.0)) 1900 1935 }); ··· 2523 2558 let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { 2524 2559 name: "Foo".to_string(), 2525 2560 kind: FunctionKind::Native(NativeFunc { 2526 - callback: |_| Ok(Value::Undefined), 2561 + callback: |_, _ctx| Ok(Value::Undefined), 2527 2562 }), 2528 2563 prototype_obj: Some(proto), 2529 2564 properties: HashMap::new(), ··· 2748 2783 match eval(src).unwrap() { 2749 2784 Value::Number(n) => assert_eq!(n, 10.0), 2750 2785 v => panic!("expected 10, got {v:?}"), 2786 + } 2787 + } 2788 + 2789 + // ── Object built-in tests ──────────────────────────────── 2790 + 2791 + #[test] 2792 + fn test_object_keys() { 2793 + let src = r#" 2794 + var obj = {}; 2795 + obj.a = 1; 2796 + obj.b = 2; 2797 + obj.c = 3; 2798 + var k = Object.keys(obj); 2799 + k.length 2800 + "#; 2801 + match eval(src).unwrap() { 2802 + Value::Number(n) => assert_eq!(n, 3.0), 2803 + v => panic!("expected 3, got {v:?}"), 2804 + } 2805 + } 2806 + 2807 + #[test] 2808 + fn test_object_values() { 2809 + let src = r#" 2810 + var obj = {}; 2811 + obj.x = 10; 2812 + var v = Object.values(obj); 2813 + v[0] 2814 + "#; 2815 + match eval(src).unwrap() { 2816 + Value::Number(n) => assert_eq!(n, 10.0), 2817 + v => panic!("expected 10, got {v:?}"), 2818 + } 2819 + } 2820 + 2821 + #[test] 2822 + fn test_object_entries() { 2823 + let src = r#" 2824 + var obj = {}; 2825 + obj.x = 42; 2826 + var e = Object.entries(obj); 2827 + e[0][1] 2828 + "#; 2829 + match eval(src).unwrap() { 2830 + Value::Number(n) => assert_eq!(n, 42.0), 2831 + v => panic!("expected 42, got {v:?}"), 2832 + } 2833 + } 2834 + 2835 + #[test] 2836 + fn test_object_assign() { 2837 + let src = r#" 2838 + var a = {}; 2839 + a.x = 1; 2840 + var b = {}; 2841 + b.y = 2; 2842 + var c = Object.assign(a, b); 2843 + c.y 2844 + "#; 2845 + match eval(src).unwrap() { 2846 + Value::Number(n) => assert_eq!(n, 2.0), 2847 + v => panic!("expected 2, got {v:?}"), 2848 + } 2849 + } 2850 + 2851 + #[test] 2852 + fn test_object_create() { 2853 + let src = r#" 2854 + var proto = {}; 2855 + proto.greet = "hello"; 2856 + var child = Object.create(proto); 2857 + child.greet 2858 + "#; 2859 + match eval(src).unwrap() { 2860 + Value::String(s) => assert_eq!(s, "hello"), 2861 + v => panic!("expected 'hello', got {v:?}"), 2862 + } 2863 + } 2864 + 2865 + #[test] 2866 + fn test_object_is() { 2867 + let src = "Object.is(NaN, NaN)"; 2868 + match eval(src).unwrap() { 2869 + Value::Boolean(b) => assert!(b), 2870 + v => panic!("expected true, got {v:?}"), 2871 + } 2872 + } 2873 + 2874 + #[test] 2875 + fn test_object_freeze() { 2876 + let src = r#" 2877 + var obj = {}; 2878 + obj.x = 1; 2879 + Object.freeze(obj); 2880 + Object.isFrozen(obj) 2881 + "#; 2882 + match eval(src).unwrap() { 2883 + Value::Boolean(b) => assert!(b), 2884 + v => panic!("expected true, got {v:?}"), 2885 + } 2886 + } 2887 + 2888 + #[test] 2889 + fn test_object_has_own_property() { 2890 + let src = r#" 2891 + var obj = {}; 2892 + obj.x = 1; 2893 + obj.hasOwnProperty("x") 2894 + "#; 2895 + match eval(src).unwrap() { 2896 + Value::Boolean(b) => assert!(b), 2897 + v => panic!("expected true, got {v:?}"), 2898 + } 2899 + } 2900 + 2901 + // ── Array built-in tests ───────────────────────────────── 2902 + 2903 + #[test] 2904 + fn test_array_push_pop() { 2905 + let src = r#" 2906 + var arr = [1, 2, 3]; 2907 + arr.push(4); 2908 + arr.pop() 2909 + "#; 2910 + match eval(src).unwrap() { 2911 + Value::Number(n) => assert_eq!(n, 4.0), 2912 + v => panic!("expected 4, got {v:?}"), 2913 + } 2914 + } 2915 + 2916 + #[test] 2917 + fn test_array_push_length() { 2918 + let src = r#" 2919 + var arr = []; 2920 + arr.push(10); 2921 + arr.push(20); 2922 + arr.length 2923 + "#; 2924 + match eval(src).unwrap() { 2925 + Value::Number(n) => assert_eq!(n, 2.0), 2926 + v => panic!("expected 2, got {v:?}"), 2927 + } 2928 + } 2929 + 2930 + #[test] 2931 + fn test_array_shift_unshift() { 2932 + let src = r#" 2933 + var arr = [1, 2, 3]; 2934 + arr.unshift(0); 2935 + arr.shift() 2936 + "#; 2937 + match eval(src).unwrap() { 2938 + Value::Number(n) => assert_eq!(n, 0.0), 2939 + v => panic!("expected 0, got {v:?}"), 2940 + } 2941 + } 2942 + 2943 + #[test] 2944 + fn test_array_index_of() { 2945 + let src = r#" 2946 + var arr = [10, 20, 30]; 2947 + arr.indexOf(20) 2948 + "#; 2949 + match eval(src).unwrap() { 2950 + Value::Number(n) => assert_eq!(n, 1.0), 2951 + v => panic!("expected 1, got {v:?}"), 2952 + } 2953 + } 2954 + 2955 + #[test] 2956 + fn test_array_includes() { 2957 + let src = r#" 2958 + var arr = [1, 2, 3]; 2959 + arr.includes(2) 2960 + "#; 2961 + match eval(src).unwrap() { 2962 + Value::Boolean(b) => assert!(b), 2963 + v => panic!("expected true, got {v:?}"), 2964 + } 2965 + } 2966 + 2967 + #[test] 2968 + fn test_array_join() { 2969 + let src = r#" 2970 + var arr = [1, 2, 3]; 2971 + arr.join("-") 2972 + "#; 2973 + match eval(src).unwrap() { 2974 + Value::String(s) => assert_eq!(s, "1-2-3"), 2975 + v => panic!("expected '1-2-3', got {v:?}"), 2976 + } 2977 + } 2978 + 2979 + #[test] 2980 + fn test_array_slice() { 2981 + let src = r#" 2982 + var arr = [1, 2, 3, 4, 5]; 2983 + var s = arr.slice(1, 3); 2984 + s.length 2985 + "#; 2986 + match eval(src).unwrap() { 2987 + Value::Number(n) => assert_eq!(n, 2.0), 2988 + v => panic!("expected 2, got {v:?}"), 2989 + } 2990 + } 2991 + 2992 + #[test] 2993 + fn test_array_concat() { 2994 + let src = r#" 2995 + var a = [1, 2]; 2996 + var b = [3, 4]; 2997 + var c = a.concat(b); 2998 + c.length 2999 + "#; 3000 + match eval(src).unwrap() { 3001 + Value::Number(n) => assert_eq!(n, 4.0), 3002 + v => panic!("expected 4, got {v:?}"), 3003 + } 3004 + } 3005 + 3006 + #[test] 3007 + fn test_array_reverse() { 3008 + let src = r#" 3009 + var arr = [1, 2, 3]; 3010 + arr.reverse(); 3011 + arr[0] 3012 + "#; 3013 + match eval(src).unwrap() { 3014 + Value::Number(n) => assert_eq!(n, 3.0), 3015 + v => panic!("expected 3, got {v:?}"), 3016 + } 3017 + } 3018 + 3019 + #[test] 3020 + fn test_array_splice() { 3021 + let src = r#" 3022 + var arr = [1, 2, 3, 4, 5]; 3023 + var removed = arr.splice(1, 2); 3024 + removed.length 3025 + "#; 3026 + match eval(src).unwrap() { 3027 + Value::Number(n) => assert_eq!(n, 2.0), 3028 + v => panic!("expected 2, got {v:?}"), 3029 + } 3030 + } 3031 + 3032 + #[test] 3033 + fn test_array_is_array() { 3034 + let src = "Array.isArray([1, 2, 3])"; 3035 + match eval(src).unwrap() { 3036 + Value::Boolean(b) => assert!(b), 3037 + v => panic!("expected true, got {v:?}"), 3038 + } 3039 + } 3040 + 3041 + #[test] 3042 + fn test_array_map() { 3043 + let src = r#" 3044 + var arr = [1, 2, 3]; 3045 + var doubled = arr.map(function(x) { return x * 2; }); 3046 + doubled[1] 3047 + "#; 3048 + match eval(src).unwrap() { 3049 + Value::Number(n) => assert_eq!(n, 4.0), 3050 + v => panic!("expected 4, got {v:?}"), 3051 + } 3052 + } 3053 + 3054 + #[test] 3055 + fn test_array_filter() { 3056 + let src = r#" 3057 + var arr = [1, 2, 3, 4, 5]; 3058 + var evens = arr.filter(function(x) { return x % 2 === 0; }); 3059 + evens.length 3060 + "#; 3061 + match eval(src).unwrap() { 3062 + Value::Number(n) => assert_eq!(n, 2.0), 3063 + v => panic!("expected 2, got {v:?}"), 3064 + } 3065 + } 3066 + 3067 + #[test] 3068 + fn test_array_reduce() { 3069 + let src = r#" 3070 + var arr = [1, 2, 3, 4]; 3071 + arr.reduce(function(acc, x) { return acc + x; }, 0) 3072 + "#; 3073 + match eval(src).unwrap() { 3074 + Value::Number(n) => assert_eq!(n, 10.0), 3075 + v => panic!("expected 10, got {v:?}"), 3076 + } 3077 + } 3078 + 3079 + #[test] 3080 + fn test_array_foreach() { 3081 + let src = r#" 3082 + var arr = [1, 2, 3]; 3083 + var sum = 0; 3084 + arr.forEach(function(x) { sum = sum + x; }); 3085 + sum 3086 + "#; 3087 + match eval(src).unwrap() { 3088 + Value::Number(n) => assert_eq!(n, 6.0), 3089 + v => panic!("expected 6, got {v:?}"), 3090 + } 3091 + } 3092 + 3093 + #[test] 3094 + fn test_array_find() { 3095 + let src = r#" 3096 + var arr = [1, 2, 3, 4]; 3097 + arr.find(function(x) { return x > 2; }) 3098 + "#; 3099 + match eval(src).unwrap() { 3100 + Value::Number(n) => assert_eq!(n, 3.0), 3101 + v => panic!("expected 3, got {v:?}"), 3102 + } 3103 + } 3104 + 3105 + #[test] 3106 + fn test_array_find_index() { 3107 + let src = r#" 3108 + var arr = [1, 2, 3, 4]; 3109 + arr.findIndex(function(x) { return x > 2; }) 3110 + "#; 3111 + match eval(src).unwrap() { 3112 + Value::Number(n) => assert_eq!(n, 2.0), 3113 + v => panic!("expected 2, got {v:?}"), 3114 + } 3115 + } 3116 + 3117 + #[test] 3118 + fn test_array_some_every() { 3119 + let src = r#" 3120 + var arr = [2, 4, 6]; 3121 + var all_even = arr.every(function(x) { return x % 2 === 0; }); 3122 + var has_big = arr.some(function(x) { return x > 10; }); 3123 + all_even && !has_big 3124 + "#; 3125 + match eval(src).unwrap() { 3126 + Value::Boolean(b) => assert!(b), 3127 + v => panic!("expected true, got {v:?}"), 3128 + } 3129 + } 3130 + 3131 + #[test] 3132 + fn test_array_sort() { 3133 + let src = r#" 3134 + var arr = [3, 1, 2]; 3135 + arr.sort(); 3136 + arr[0] 3137 + "#; 3138 + match eval(src).unwrap() { 3139 + Value::Number(n) => assert_eq!(n, 1.0), 3140 + v => panic!("expected 1, got {v:?}"), 3141 + } 3142 + } 3143 + 3144 + #[test] 3145 + fn test_array_sort_custom() { 3146 + let src = r#" 3147 + var arr = [3, 1, 2]; 3148 + arr.sort(function(a, b) { return a - b; }); 3149 + arr[2] 3150 + "#; 3151 + match eval(src).unwrap() { 3152 + Value::Number(n) => assert_eq!(n, 3.0), 3153 + v => panic!("expected 3, got {v:?}"), 3154 + } 3155 + } 3156 + 3157 + #[test] 3158 + fn test_array_from() { 3159 + let src = r#" 3160 + var arr = Array.from("abc"); 3161 + arr.length 3162 + "#; 3163 + match eval(src).unwrap() { 3164 + Value::Number(n) => assert_eq!(n, 3.0), 3165 + v => panic!("expected 3, got {v:?}"), 3166 + } 3167 + } 3168 + 3169 + #[test] 3170 + fn test_array_from_array() { 3171 + let src = r#" 3172 + var orig = [10, 20, 30]; 3173 + var copy = Array.from(orig); 3174 + copy[2] 3175 + "#; 3176 + match eval(src).unwrap() { 3177 + Value::Number(n) => assert_eq!(n, 30.0), 3178 + v => panic!("expected 30, got {v:?}"), 3179 + } 3180 + } 3181 + 3182 + #[test] 3183 + fn test_array_flat() { 3184 + let src = r#" 3185 + var arr = [[1, 2], [3, 4]]; 3186 + var flat = arr.flat(); 3187 + flat.length 3188 + "#; 3189 + match eval(src).unwrap() { 3190 + Value::Number(n) => assert_eq!(n, 4.0), 3191 + v => panic!("expected 4, got {v:?}"), 3192 + } 3193 + } 3194 + 3195 + // ── Error built-in tests ───────────────────────────────── 3196 + 3197 + #[test] 3198 + fn test_error_constructor() { 3199 + let src = r#" 3200 + var e = new Error("oops"); 3201 + e.message 3202 + "#; 3203 + match eval(src).unwrap() { 3204 + Value::String(s) => assert_eq!(s, "oops"), 3205 + v => panic!("expected 'oops', got {v:?}"), 3206 + } 3207 + } 3208 + 3209 + #[test] 3210 + fn test_type_error_constructor() { 3211 + let src = r#" 3212 + var e = new TypeError("bad type"); 3213 + e.message 3214 + "#; 3215 + match eval(src).unwrap() { 3216 + Value::String(s) => assert_eq!(s, "bad type"), 3217 + v => panic!("expected 'bad type', got {v:?}"), 3218 + } 3219 + } 3220 + 3221 + // ── Global function tests ──────────────────────────────── 3222 + 3223 + #[test] 3224 + fn test_parse_int() { 3225 + let src = "parseInt('42')"; 3226 + match eval(src).unwrap() { 3227 + Value::Number(n) => assert_eq!(n, 42.0), 3228 + v => panic!("expected 42, got {v:?}"), 3229 + } 3230 + } 3231 + 3232 + #[test] 3233 + fn test_parse_int_hex() { 3234 + let src = "parseInt('0xFF', 16)"; 3235 + match eval(src).unwrap() { 3236 + Value::Number(n) => assert_eq!(n, 255.0), 3237 + v => panic!("expected 255, got {v:?}"), 3238 + } 3239 + } 3240 + 3241 + #[test] 3242 + fn test_is_nan() { 3243 + let src = "isNaN(NaN)"; 3244 + match eval(src).unwrap() { 3245 + Value::Boolean(b) => assert!(b), 3246 + v => panic!("expected true, got {v:?}"), 3247 + } 3248 + } 3249 + 3250 + #[test] 3251 + fn test_is_finite() { 3252 + let src = "isFinite(42)"; 3253 + match eval(src).unwrap() { 3254 + Value::Boolean(b) => assert!(b), 3255 + v => panic!("expected true, got {v:?}"), 2751 3256 } 2752 3257 } 2753 3258 }