we (web engine): Experimental web browser project to understand the limits of Claude
at encoding-sniffing 6827 lines 239 kB view raw
1//! Built-in JavaScript objects and functions: Object, Array, Function, Error, 2//! String, Number, Boolean, Symbol, Math, Date, JSON, Promise. 3//! 4//! Registers constructors, static methods, and prototype methods as globals 5//! in the VM. Callback-based array methods (map, filter, etc.) are defined 6//! via a JS preamble executed at init time. 7 8use crate::gc::{Gc, GcRef}; 9use crate::vm::*; 10use std::cell::RefCell; 11use std::collections::{HashMap, HashSet}; 12use std::time::{SystemTime, UNIX_EPOCH}; 13 14/// Native callback type alias to satisfy clippy::type_complexity. 15type NativeMethod = ( 16 &'static str, 17 fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>, 18); 19 20// ── Helpers ────────────────────────────────────────────────── 21 22/// Create a native function GcRef. 23pub fn make_native( 24 gc: &mut Gc<HeapObject>, 25 name: &str, 26 callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>, 27) -> GcRef { 28 gc.alloc(HeapObject::Function(Box::new(FunctionData { 29 name: name.to_string(), 30 kind: FunctionKind::Native(NativeFunc { callback }), 31 prototype_obj: None, 32 properties: HashMap::new(), 33 upvalues: Vec::new(), 34 }))) 35} 36 37/// Set a non-enumerable property on an object. 38pub fn set_builtin_prop(gc: &mut Gc<HeapObject>, obj: GcRef, key: &str, val: Value) { 39 if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { 40 data.properties 41 .insert(key.to_string(), Property::builtin(val)); 42 } 43} 44 45/// Set a non-enumerable property on a function. 46fn set_func_prop(gc: &mut Gc<HeapObject>, func: GcRef, key: &str, val: Value) { 47 if let Some(HeapObject::Function(fdata)) = gc.get_mut(func) { 48 fdata 49 .properties 50 .insert(key.to_string(), Property::builtin(val)); 51 } 52} 53 54/// Get own enumerable string keys of an object (for Object.keys, etc.). 55fn own_enumerable_keys(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> { 56 match gc.get(obj_ref) { 57 Some(HeapObject::Object(data)) => { 58 let mut int_keys: Vec<(u32, String)> = Vec::new(); 59 let mut str_keys: Vec<String> = Vec::new(); 60 for (k, prop) in &data.properties { 61 if prop.enumerable { 62 if let Ok(idx) = k.parse::<u32>() { 63 int_keys.push((idx, k.clone())); 64 } else { 65 str_keys.push(k.clone()); 66 } 67 } 68 } 69 int_keys.sort_by_key(|(idx, _)| *idx); 70 let mut result: Vec<String> = int_keys.into_iter().map(|(_, k)| k).collect(); 71 result.extend(str_keys); 72 result 73 } 74 _ => Vec::new(), 75 } 76} 77 78/// Get all own property names (enumerable or not) of an object. 79fn own_property_names(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> { 80 match gc.get(obj_ref) { 81 Some(HeapObject::Object(data)) => { 82 let mut int_keys: Vec<(u32, String)> = Vec::new(); 83 let mut str_keys: Vec<String> = Vec::new(); 84 for k in data.properties.keys() { 85 if let Ok(idx) = k.parse::<u32>() { 86 int_keys.push((idx, k.clone())); 87 } else { 88 str_keys.push(k.clone()); 89 } 90 } 91 int_keys.sort_by_key(|(idx, _)| *idx); 92 let mut result: Vec<String> = int_keys.into_iter().map(|(_, k)| k).collect(); 93 result.extend(str_keys); 94 result 95 } 96 _ => Vec::new(), 97 } 98} 99 100/// Read the "length" property of an array-like object as usize. 101fn array_length(gc: &Gc<HeapObject>, obj: GcRef) -> usize { 102 match gc.get(obj) { 103 Some(HeapObject::Object(data)) => match data.properties.get("length") { 104 Some(prop) => prop.value.to_number() as usize, 105 None => 0, 106 }, 107 _ => 0, 108 } 109} 110 111/// Set the "length" property on an array-like object. 112fn set_array_length(gc: &mut Gc<HeapObject>, obj: GcRef, len: usize) { 113 if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { 114 if let Some(prop) = data.properties.get_mut("length") { 115 prop.value = Value::Number(len as f64); 116 } else { 117 data.properties.insert( 118 "length".to_string(), 119 Property { 120 value: Value::Number(len as f64), 121 writable: true, 122 enumerable: false, 123 configurable: false, 124 }, 125 ); 126 } 127 } 128} 129 130/// Check whether an object has a "length" property (i.e. is array-like). 131fn array_length_exists(gc: &Gc<HeapObject>, obj: GcRef) -> bool { 132 match gc.get(obj) { 133 Some(HeapObject::Object(data)) => data.properties.contains_key("length"), 134 _ => false, 135 } 136} 137 138/// Get an element by index from an array-like object. 139fn array_get(gc: &Gc<HeapObject>, obj: GcRef, idx: usize) -> Value { 140 match gc.get(obj) { 141 Some(HeapObject::Object(data)) => { 142 let key = idx.to_string(); 143 data.properties 144 .get(&key) 145 .map(|p| p.value.clone()) 146 .unwrap_or(Value::Undefined) 147 } 148 _ => Value::Undefined, 149 } 150} 151 152/// Set an element by index on an array-like object. 153fn array_set(gc: &mut Gc<HeapObject>, obj: GcRef, idx: usize, val: Value) { 154 if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { 155 data.properties.insert(idx.to_string(), Property::data(val)); 156 } 157} 158 159// ── Initialization ─────────────────────────────────────────── 160 161/// Initialize all built-in objects and register them in the VM. 162pub fn init_builtins(vm: &mut Vm) { 163 // Create Object.prototype first (root of the prototype chain). 164 let obj_proto = vm.gc.alloc(HeapObject::Object(ObjectData::new())); 165 init_object_prototype(&mut vm.gc, obj_proto); 166 167 // Create Array.prototype (inherits from Object.prototype). 168 let mut arr_proto_data = ObjectData::new(); 169 arr_proto_data.prototype = Some(obj_proto); 170 let arr_proto = vm.gc.alloc(HeapObject::Object(arr_proto_data)); 171 init_array_prototype(&mut vm.gc, arr_proto); 172 173 // Create Error.prototype (inherits from Object.prototype). 174 let mut err_proto_data = ObjectData::new(); 175 err_proto_data.prototype = Some(obj_proto); 176 err_proto_data.properties.insert( 177 "name".to_string(), 178 Property::builtin(Value::String("Error".to_string())), 179 ); 180 err_proto_data.properties.insert( 181 "message".to_string(), 182 Property::builtin(Value::String(String::new())), 183 ); 184 let err_proto = vm.gc.alloc(HeapObject::Object(err_proto_data)); 185 init_error_prototype(&mut vm.gc, err_proto); 186 187 // Create String.prototype (inherits from Object.prototype). 188 let mut str_proto_data = ObjectData::new(); 189 str_proto_data.prototype = Some(obj_proto); 190 let str_proto = vm.gc.alloc(HeapObject::Object(str_proto_data)); 191 init_string_prototype(&mut vm.gc, str_proto); 192 193 // Create Number.prototype (inherits from Object.prototype). 194 let mut num_proto_data = ObjectData::new(); 195 num_proto_data.prototype = Some(obj_proto); 196 let num_proto = vm.gc.alloc(HeapObject::Object(num_proto_data)); 197 init_number_prototype(&mut vm.gc, num_proto); 198 199 // Create Boolean.prototype (inherits from Object.prototype). 200 let mut bool_proto_data = ObjectData::new(); 201 bool_proto_data.prototype = Some(obj_proto); 202 let bool_proto = vm.gc.alloc(HeapObject::Object(bool_proto_data)); 203 init_boolean_prototype(&mut vm.gc, bool_proto); 204 205 // Store prototypes in VM for use by CreateArray/CreateObject and auto-boxing. 206 vm.object_prototype = Some(obj_proto); 207 vm.array_prototype = Some(arr_proto); 208 vm.string_prototype = Some(str_proto); 209 vm.number_prototype = Some(num_proto); 210 vm.boolean_prototype = Some(bool_proto); 211 212 // Create and register Object constructor. 213 let obj_ctor = init_object_constructor(&mut vm.gc, obj_proto); 214 vm.set_global("Object", Value::Function(obj_ctor)); 215 216 // Create and register Array constructor. 217 let arr_ctor = init_array_constructor(&mut vm.gc, arr_proto); 218 vm.set_global("Array", Value::Function(arr_ctor)); 219 220 // Create and register Error constructors. 221 init_error_constructors(vm, err_proto); 222 223 // Create and register String constructor. 224 let str_ctor = init_string_constructor(&mut vm.gc, str_proto); 225 vm.set_global("String", Value::Function(str_ctor)); 226 227 // Create and register Number constructor. 228 let num_ctor = init_number_constructor(&mut vm.gc, num_proto); 229 vm.set_global("Number", Value::Function(num_ctor)); 230 231 // Create and register Boolean constructor. 232 let bool_ctor = init_boolean_constructor(&mut vm.gc, bool_proto); 233 vm.set_global("Boolean", Value::Function(bool_ctor)); 234 235 // Create and register Symbol factory. 236 init_symbol_builtins(vm); 237 238 // Create and register Math object (static methods only, not a constructor). 239 init_math_object(vm); 240 241 // Create and register Date constructor. 242 init_date_builtins(vm); 243 244 // Create and register RegExp constructor. 245 init_regexp_builtins(vm); 246 247 // Create and register Map, Set, WeakMap, WeakSet constructors. 248 init_map_set_builtins(vm); 249 250 // Create and register Promise (prototype methods + native helpers). 251 init_promise_builtins(vm); 252 253 // Create and register JSON object (static methods only). 254 init_json_object(vm); 255 256 // Create and register console object. 257 init_console_object(vm); 258 259 // Register global utility functions. 260 init_global_functions(vm); 261 262 // Execute JS preamble for callback-based methods. 263 init_js_preamble(vm); 264} 265 266// ── Object.prototype ───────────────────────────────────────── 267 268fn init_object_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 269 let has_own = make_native(gc, "hasOwnProperty", object_proto_has_own_property); 270 set_builtin_prop(gc, proto, "hasOwnProperty", Value::Function(has_own)); 271 272 let to_string = make_native(gc, "toString", object_proto_to_string); 273 set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 274 275 let value_of = make_native(gc, "valueOf", object_proto_value_of); 276 set_builtin_prop(gc, proto, "valueOf", Value::Function(value_of)); 277} 278 279fn object_proto_has_own_property( 280 args: &[Value], 281 ctx: &mut NativeContext, 282) -> Result<Value, RuntimeError> { 283 let key = args 284 .first() 285 .map(|v| v.to_js_string(ctx.gc)) 286 .unwrap_or_default(); 287 match ctx.this.gc_ref() { 288 Some(obj_ref) => match ctx.gc.get(obj_ref) { 289 Some(HeapObject::Object(data)) => { 290 Ok(Value::Boolean(data.properties.contains_key(&key))) 291 } 292 Some(HeapObject::Function(fdata)) => { 293 Ok(Value::Boolean(fdata.properties.contains_key(&key))) 294 } 295 _ => Ok(Value::Boolean(false)), 296 }, 297 None => Ok(Value::Boolean(false)), 298 } 299} 300 301fn object_proto_to_string( 302 _args: &[Value], 303 _ctx: &mut NativeContext, 304) -> Result<Value, RuntimeError> { 305 Ok(Value::String("[object Object]".to_string())) 306} 307 308fn object_proto_value_of(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 309 Ok(ctx.this.clone()) 310} 311 312// ── Object constructor + static methods ────────────────────── 313 314fn init_object_constructor(gc: &mut Gc<HeapObject>, obj_proto: GcRef) -> GcRef { 315 let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { 316 name: "Object".to_string(), 317 kind: FunctionKind::Native(NativeFunc { 318 callback: object_constructor, 319 }), 320 prototype_obj: Some(obj_proto), 321 properties: HashMap::new(), 322 upvalues: Vec::new(), 323 }))); 324 325 // Static methods. 326 let keys = make_native(gc, "keys", object_keys); 327 set_func_prop(gc, ctor, "keys", Value::Function(keys)); 328 329 let values = make_native(gc, "values", object_values); 330 set_func_prop(gc, ctor, "values", Value::Function(values)); 331 332 let entries = make_native(gc, "entries", object_entries); 333 set_func_prop(gc, ctor, "entries", Value::Function(entries)); 334 335 let assign = make_native(gc, "assign", object_assign); 336 set_func_prop(gc, ctor, "assign", Value::Function(assign)); 337 338 let create = make_native(gc, "create", object_create); 339 set_func_prop(gc, ctor, "create", Value::Function(create)); 340 341 let is = make_native(gc, "is", object_is); 342 set_func_prop(gc, ctor, "is", Value::Function(is)); 343 344 let get_proto = make_native(gc, "getPrototypeOf", object_get_prototype_of); 345 set_func_prop(gc, ctor, "getPrototypeOf", Value::Function(get_proto)); 346 347 let get_own_names = make_native(gc, "getOwnPropertyNames", object_get_own_property_names); 348 set_func_prop( 349 gc, 350 ctor, 351 "getOwnPropertyNames", 352 Value::Function(get_own_names), 353 ); 354 355 let get_own_desc = make_native(gc, "getOwnPropertyDescriptor", object_get_own_prop_desc); 356 set_func_prop( 357 gc, 358 ctor, 359 "getOwnPropertyDescriptor", 360 Value::Function(get_own_desc), 361 ); 362 363 let define_prop = make_native(gc, "defineProperty", object_define_property); 364 set_func_prop(gc, ctor, "defineProperty", Value::Function(define_prop)); 365 366 let freeze = make_native(gc, "freeze", object_freeze); 367 set_func_prop(gc, ctor, "freeze", Value::Function(freeze)); 368 369 let seal = make_native(gc, "seal", object_seal); 370 set_func_prop(gc, ctor, "seal", Value::Function(seal)); 371 372 let is_frozen = make_native(gc, "isFrozen", object_is_frozen); 373 set_func_prop(gc, ctor, "isFrozen", Value::Function(is_frozen)); 374 375 let is_sealed = make_native(gc, "isSealed", object_is_sealed); 376 set_func_prop(gc, ctor, "isSealed", Value::Function(is_sealed)); 377 378 let from_entries = make_native(gc, "fromEntries", object_from_entries); 379 set_func_prop(gc, ctor, "fromEntries", Value::Function(from_entries)); 380 381 let has_own = make_native(gc, "hasOwn", object_has_own); 382 set_func_prop(gc, ctor, "hasOwn", Value::Function(has_own)); 383 384 ctor 385} 386 387fn object_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 388 match args.first() { 389 Some(Value::Object(_)) | Some(Value::Function(_)) => Ok(args[0].clone()), 390 Some(Value::Null) | Some(Value::Undefined) | None => { 391 let obj = ctx.gc.alloc(HeapObject::Object(ObjectData::new())); 392 Ok(Value::Object(obj)) 393 } 394 _ => { 395 // Primitive wrapping (simplified): return a new object. 396 let obj = ctx.gc.alloc(HeapObject::Object(ObjectData::new())); 397 Ok(Value::Object(obj)) 398 } 399 } 400} 401 402fn object_keys(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 403 let obj_ref = match args.first() { 404 Some(Value::Object(r)) | Some(Value::Function(r)) => *r, 405 _ => return Err(RuntimeError::type_error("Object.keys requires an object")), 406 }; 407 let keys = own_enumerable_keys(ctx.gc, obj_ref); 408 Ok(make_string_array(ctx.gc, &keys)) 409} 410 411fn object_values(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 412 let obj_ref = match args.first() { 413 Some(Value::Object(r)) => *r, 414 _ => return Err(RuntimeError::type_error("Object.values requires an object")), 415 }; 416 let keys = own_enumerable_keys(ctx.gc, obj_ref); 417 let values: Vec<Value> = keys 418 .iter() 419 .map(|k| { 420 ctx.gc 421 .get(obj_ref) 422 .and_then(|ho| match ho { 423 HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), 424 _ => None, 425 }) 426 .unwrap_or(Value::Undefined) 427 }) 428 .collect(); 429 Ok(make_value_array(ctx.gc, &values)) 430} 431 432fn object_entries(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 433 let obj_ref = match args.first() { 434 Some(Value::Object(r)) => *r, 435 _ => { 436 return Err(RuntimeError::type_error( 437 "Object.entries requires an object", 438 )) 439 } 440 }; 441 let keys = own_enumerable_keys(ctx.gc, obj_ref); 442 let mut entries = Vec::new(); 443 for k in &keys { 444 let val = ctx 445 .gc 446 .get(obj_ref) 447 .and_then(|ho| match ho { 448 HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), 449 _ => None, 450 }) 451 .unwrap_or(Value::Undefined); 452 // Create a [key, value] pair array. 453 let pair = make_value_array(ctx.gc, &[Value::String(k.clone()), val]); 454 entries.push(pair); 455 } 456 Ok(make_value_array(ctx.gc, &entries)) 457} 458 459fn object_assign(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 460 let target_ref = match args.first() { 461 Some(Value::Object(r)) => *r, 462 _ => { 463 return Err(RuntimeError::type_error( 464 "Object.assign requires an object target", 465 )) 466 } 467 }; 468 for source in args.iter().skip(1) { 469 let src_ref = match source { 470 Value::Object(r) => *r, 471 Value::Null | Value::Undefined => continue, 472 _ => continue, 473 }; 474 let keys = own_enumerable_keys(ctx.gc, src_ref); 475 for k in &keys { 476 let val = ctx 477 .gc 478 .get(src_ref) 479 .and_then(|ho| match ho { 480 HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), 481 _ => None, 482 }) 483 .unwrap_or(Value::Undefined); 484 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(target_ref) { 485 data.properties.insert(k.clone(), Property::data(val)); 486 } 487 } 488 } 489 Ok(Value::Object(target_ref)) 490} 491 492fn object_create(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 493 let proto = match args.first() { 494 Some(Value::Object(r)) => Some(*r), 495 Some(Value::Null) => None, 496 _ => { 497 return Err(RuntimeError::type_error( 498 "Object.create prototype must be an object or null", 499 )) 500 } 501 }; 502 let mut obj = ObjectData::new(); 503 obj.prototype = proto; 504 let gc_ref = ctx.gc.alloc(HeapObject::Object(obj)); 505 Ok(Value::Object(gc_ref)) 506} 507 508fn object_is(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 509 let x = args.first().cloned().unwrap_or(Value::Undefined); 510 let y = args.get(1).cloned().unwrap_or(Value::Undefined); 511 Ok(Value::Boolean(same_value(&x, &y))) 512} 513 514/// SameValue algorithm (ECMA-262 §7.2.10). 515fn same_value(x: &Value, y: &Value) -> bool { 516 match (x, y) { 517 (Value::Number(a), Value::Number(b)) => { 518 if a.is_nan() && b.is_nan() { 519 return true; 520 } 521 if *a == 0.0 && *b == 0.0 { 522 return a.is_sign_positive() == b.is_sign_positive(); 523 } 524 a == b 525 } 526 (Value::Undefined, Value::Undefined) => true, 527 (Value::Null, Value::Null) => true, 528 (Value::Boolean(a), Value::Boolean(b)) => a == b, 529 (Value::String(a), Value::String(b)) => a == b, 530 (Value::Object(a), Value::Object(b)) => a == b, 531 (Value::Function(a), Value::Function(b)) => a == b, 532 _ => false, 533 } 534} 535 536fn object_get_prototype_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 537 let obj_ref = match args.first() { 538 Some(Value::Object(r)) => *r, 539 _ => { 540 return Err(RuntimeError::type_error( 541 "Object.getPrototypeOf requires an object", 542 )) 543 } 544 }; 545 match ctx.gc.get(obj_ref) { 546 Some(HeapObject::Object(data)) => match data.prototype { 547 Some(proto) => Ok(Value::Object(proto)), 548 None => Ok(Value::Null), 549 }, 550 _ => Ok(Value::Null), 551 } 552} 553 554fn object_get_own_property_names( 555 args: &[Value], 556 ctx: &mut NativeContext, 557) -> Result<Value, RuntimeError> { 558 let obj_ref = match args.first() { 559 Some(Value::Object(r)) | Some(Value::Function(r)) => *r, 560 _ => { 561 return Err(RuntimeError::type_error( 562 "Object.getOwnPropertyNames requires an object", 563 )) 564 } 565 }; 566 let names = own_property_names(ctx.gc, obj_ref); 567 Ok(make_string_array(ctx.gc, &names)) 568} 569 570fn object_get_own_prop_desc( 571 args: &[Value], 572 ctx: &mut NativeContext, 573) -> Result<Value, RuntimeError> { 574 let obj_ref = match args.first() { 575 Some(Value::Object(r)) => *r, 576 _ => return Ok(Value::Undefined), 577 }; 578 let key = args 579 .get(1) 580 .map(|v| v.to_js_string(ctx.gc)) 581 .unwrap_or_default(); 582 let prop = match ctx.gc.get(obj_ref) { 583 Some(HeapObject::Object(data)) => data.properties.get(&key).cloned(), 584 _ => None, 585 }; 586 match prop { 587 Some(p) => { 588 let mut desc = ObjectData::new(); 589 desc.properties 590 .insert("value".to_string(), Property::data(p.value)); 591 desc.properties.insert( 592 "writable".to_string(), 593 Property::data(Value::Boolean(p.writable)), 594 ); 595 desc.properties.insert( 596 "enumerable".to_string(), 597 Property::data(Value::Boolean(p.enumerable)), 598 ); 599 desc.properties.insert( 600 "configurable".to_string(), 601 Property::data(Value::Boolean(p.configurable)), 602 ); 603 Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(desc)))) 604 } 605 None => Ok(Value::Undefined), 606 } 607} 608 609fn object_define_property(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 610 let obj_ref = match args.first() { 611 Some(Value::Object(r)) => *r, 612 _ => { 613 return Err(RuntimeError::type_error( 614 "Object.defineProperty requires an object", 615 )) 616 } 617 }; 618 let key = args 619 .get(1) 620 .map(|v| v.to_js_string(ctx.gc)) 621 .unwrap_or_default(); 622 let desc_ref = match args.get(2) { 623 Some(Value::Object(r)) => *r, 624 _ => { 625 return Err(RuntimeError::type_error( 626 "Property descriptor must be an object", 627 )) 628 } 629 }; 630 631 // Read descriptor properties. 632 let (value, writable, enumerable, configurable) = { 633 match ctx.gc.get(desc_ref) { 634 Some(HeapObject::Object(desc_data)) => { 635 let value = desc_data 636 .properties 637 .get("value") 638 .map(|p| p.value.clone()) 639 .unwrap_or(Value::Undefined); 640 let writable = desc_data 641 .properties 642 .get("writable") 643 .map(|p| p.value.to_boolean()) 644 .unwrap_or(false); 645 let enumerable = desc_data 646 .properties 647 .get("enumerable") 648 .map(|p| p.value.to_boolean()) 649 .unwrap_or(false); 650 let configurable = desc_data 651 .properties 652 .get("configurable") 653 .map(|p| p.value.to_boolean()) 654 .unwrap_or(false); 655 (value, writable, enumerable, configurable) 656 } 657 _ => (Value::Undefined, false, false, false), 658 } 659 }; 660 661 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 662 data.properties.insert( 663 key, 664 Property { 665 value, 666 writable, 667 enumerable, 668 configurable, 669 }, 670 ); 671 } 672 Ok(Value::Object(obj_ref)) 673} 674 675fn object_freeze(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 676 let obj_ref = match args.first() { 677 Some(Value::Object(r)) => *r, 678 Some(other) => return Ok(other.clone()), 679 None => return Ok(Value::Undefined), 680 }; 681 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 682 data.extensible = false; 683 for prop in data.properties.values_mut() { 684 prop.writable = false; 685 prop.configurable = false; 686 } 687 } 688 Ok(Value::Object(obj_ref)) 689} 690 691fn object_seal(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 692 let obj_ref = match args.first() { 693 Some(Value::Object(r)) => *r, 694 Some(other) => return Ok(other.clone()), 695 None => return Ok(Value::Undefined), 696 }; 697 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 698 data.extensible = false; 699 for prop in data.properties.values_mut() { 700 prop.configurable = false; 701 } 702 } 703 Ok(Value::Object(obj_ref)) 704} 705 706fn object_is_frozen(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 707 let obj_ref = match args.first() { 708 Some(Value::Object(r)) => *r, 709 _ => return Ok(Value::Boolean(true)), 710 }; 711 match ctx.gc.get(obj_ref) { 712 Some(HeapObject::Object(data)) => { 713 if data.extensible { 714 return Ok(Value::Boolean(false)); 715 } 716 let frozen = data 717 .properties 718 .values() 719 .all(|p| !p.writable && !p.configurable); 720 Ok(Value::Boolean(frozen)) 721 } 722 _ => Ok(Value::Boolean(true)), 723 } 724} 725 726fn object_is_sealed(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 727 let obj_ref = match args.first() { 728 Some(Value::Object(r)) => *r, 729 _ => return Ok(Value::Boolean(true)), 730 }; 731 match ctx.gc.get(obj_ref) { 732 Some(HeapObject::Object(data)) => { 733 if data.extensible { 734 return Ok(Value::Boolean(false)); 735 } 736 let sealed = data.properties.values().all(|p| !p.configurable); 737 Ok(Value::Boolean(sealed)) 738 } 739 _ => Ok(Value::Boolean(true)), 740 } 741} 742 743fn object_from_entries(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 744 let arr_ref = match args.first() { 745 Some(Value::Object(r)) => *r, 746 _ => { 747 return Err(RuntimeError::type_error( 748 "Object.fromEntries requires an iterable", 749 )) 750 } 751 }; 752 let len = array_length(ctx.gc, arr_ref); 753 let mut obj = ObjectData::new(); 754 for i in 0..len { 755 let pair_val = array_get(ctx.gc, arr_ref, i); 756 if let Value::Object(pair_ref) = pair_val { 757 let key = array_get(ctx.gc, pair_ref, 0); 758 let val = array_get(ctx.gc, pair_ref, 1); 759 let key_str = key.to_js_string(ctx.gc); 760 obj.properties.insert(key_str, Property::data(val)); 761 } 762 } 763 Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj)))) 764} 765 766fn object_has_own(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 767 let obj_ref = match args.first() { 768 Some(Value::Object(r)) => *r, 769 _ => return Ok(Value::Boolean(false)), 770 }; 771 let key = args 772 .get(1) 773 .map(|v| v.to_js_string(ctx.gc)) 774 .unwrap_or_default(); 775 match ctx.gc.get(obj_ref) { 776 Some(HeapObject::Object(data)) => Ok(Value::Boolean(data.properties.contains_key(&key))), 777 _ => Ok(Value::Boolean(false)), 778 } 779} 780 781// ── Array helpers ──────────────────────────────────────────── 782 783/// Create a JS array from a slice of string values. 784fn make_string_array(gc: &mut Gc<HeapObject>, items: &[String]) -> Value { 785 let mut obj = ObjectData::new(); 786 for (i, s) in items.iter().enumerate() { 787 obj.properties 788 .insert(i.to_string(), Property::data(Value::String(s.clone()))); 789 } 790 obj.properties.insert( 791 "length".to_string(), 792 Property { 793 value: Value::Number(items.len() as f64), 794 writable: true, 795 enumerable: false, 796 configurable: false, 797 }, 798 ); 799 Value::Object(gc.alloc(HeapObject::Object(obj))) 800} 801 802/// Create a JS array from a slice of Values. 803fn make_value_array(gc: &mut Gc<HeapObject>, items: &[Value]) -> Value { 804 let mut obj = ObjectData::new(); 805 for (i, v) in items.iter().enumerate() { 806 obj.properties 807 .insert(i.to_string(), Property::data(v.clone())); 808 } 809 obj.properties.insert( 810 "length".to_string(), 811 Property { 812 value: Value::Number(items.len() as f64), 813 writable: true, 814 enumerable: false, 815 configurable: false, 816 }, 817 ); 818 Value::Object(gc.alloc(HeapObject::Object(obj))) 819} 820 821// ── Array.prototype ────────────────────────────────────────── 822 823fn init_array_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 824 let push = make_native(gc, "push", array_push); 825 set_builtin_prop(gc, proto, "push", Value::Function(push)); 826 827 let pop = make_native(gc, "pop", array_pop); 828 set_builtin_prop(gc, proto, "pop", Value::Function(pop)); 829 830 let shift = make_native(gc, "shift", array_shift); 831 set_builtin_prop(gc, proto, "shift", Value::Function(shift)); 832 833 let unshift = make_native(gc, "unshift", array_unshift); 834 set_builtin_prop(gc, proto, "unshift", Value::Function(unshift)); 835 836 let index_of = make_native(gc, "indexOf", array_index_of); 837 set_builtin_prop(gc, proto, "indexOf", Value::Function(index_of)); 838 839 let last_index_of = make_native(gc, "lastIndexOf", array_last_index_of); 840 set_builtin_prop(gc, proto, "lastIndexOf", Value::Function(last_index_of)); 841 842 let includes = make_native(gc, "includes", array_includes); 843 set_builtin_prop(gc, proto, "includes", Value::Function(includes)); 844 845 let join = make_native(gc, "join", array_join); 846 set_builtin_prop(gc, proto, "join", Value::Function(join)); 847 848 let slice = make_native(gc, "slice", array_slice); 849 set_builtin_prop(gc, proto, "slice", Value::Function(slice)); 850 851 let concat = make_native(gc, "concat", array_concat); 852 set_builtin_prop(gc, proto, "concat", Value::Function(concat)); 853 854 let reverse = make_native(gc, "reverse", array_reverse); 855 set_builtin_prop(gc, proto, "reverse", Value::Function(reverse)); 856 857 let splice = make_native(gc, "splice", array_splice); 858 set_builtin_prop(gc, proto, "splice", Value::Function(splice)); 859 860 let fill = make_native(gc, "fill", array_fill); 861 set_builtin_prop(gc, proto, "fill", Value::Function(fill)); 862 863 let to_string = make_native(gc, "toString", array_to_string); 864 set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 865 866 let at = make_native(gc, "at", array_at); 867 set_builtin_prop(gc, proto, "at", Value::Function(at)); 868 869 // @@iterator: returns an array iterator (values). 870 let iter = make_native(gc, "[Symbol.iterator]", array_iterator); 871 set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter)); 872 873 // Array.prototype.keys/values/entries 874 let keys_fn = make_native(gc, "keys", array_keys_iter); 875 set_builtin_prop(gc, proto, "keys", Value::Function(keys_fn)); 876 let values_fn = make_native(gc, "values", array_values_iter); 877 set_builtin_prop(gc, proto, "values", Value::Function(values_fn)); 878 let entries_fn = make_native(gc, "entries", array_entries_iter); 879 set_builtin_prop(gc, proto, "entries", Value::Function(entries_fn)); 880} 881 882fn array_push(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 883 let obj_ref = match ctx.this.gc_ref() { 884 Some(r) => r, 885 None => return Err(RuntimeError::type_error("push called on non-object")), 886 }; 887 let mut len = array_length(ctx.gc, obj_ref); 888 for val in args { 889 array_set(ctx.gc, obj_ref, len, val.clone()); 890 len += 1; 891 } 892 set_array_length(ctx.gc, obj_ref, len); 893 Ok(Value::Number(len as f64)) 894} 895 896fn array_pop(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 897 let _ = args; 898 let obj_ref = match ctx.this.gc_ref() { 899 Some(r) => r, 900 None => return Err(RuntimeError::type_error("pop called on non-object")), 901 }; 902 let len = array_length(ctx.gc, obj_ref); 903 if len == 0 { 904 return Ok(Value::Undefined); 905 } 906 let val = array_get(ctx.gc, obj_ref, len - 1); 907 // Remove the last element. 908 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 909 data.properties.remove(&(len - 1).to_string()); 910 } 911 set_array_length(ctx.gc, obj_ref, len - 1); 912 Ok(val) 913} 914 915fn array_shift(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 916 let _ = args; 917 let obj_ref = match ctx.this.gc_ref() { 918 Some(r) => r, 919 None => return Err(RuntimeError::type_error("shift called on non-object")), 920 }; 921 let len = array_length(ctx.gc, obj_ref); 922 if len == 0 { 923 return Ok(Value::Undefined); 924 } 925 let first = array_get(ctx.gc, obj_ref, 0); 926 // Shift all elements down. 927 let mut vals = Vec::with_capacity(len - 1); 928 for i in 1..len { 929 vals.push(array_get(ctx.gc, obj_ref, i)); 930 } 931 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 932 // Remove all numeric keys. 933 for i in 0..len { 934 data.properties.remove(&i.to_string()); 935 } 936 // Re-insert shifted values. 937 for (i, v) in vals.into_iter().enumerate() { 938 data.properties.insert(i.to_string(), Property::data(v)); 939 } 940 } 941 set_array_length(ctx.gc, obj_ref, len - 1); 942 Ok(first) 943} 944 945fn array_unshift(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 946 let obj_ref = match ctx.this.gc_ref() { 947 Some(r) => r, 948 None => return Err(RuntimeError::type_error("unshift called on non-object")), 949 }; 950 let len = array_length(ctx.gc, obj_ref); 951 let insert_count = args.len(); 952 // Read existing values. 953 let mut existing = Vec::with_capacity(len); 954 for i in 0..len { 955 existing.push(array_get(ctx.gc, obj_ref, i)); 956 } 957 // Write new values at the start, then existing values after. 958 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 959 for i in 0..len { 960 data.properties.remove(&i.to_string()); 961 } 962 for (i, v) in args.iter().enumerate() { 963 data.properties 964 .insert(i.to_string(), Property::data(v.clone())); 965 } 966 for (i, v) in existing.into_iter().enumerate() { 967 data.properties 968 .insert((i + insert_count).to_string(), Property::data(v)); 969 } 970 } 971 let new_len = len + insert_count; 972 set_array_length(ctx.gc, obj_ref, new_len); 973 Ok(Value::Number(new_len as f64)) 974} 975 976fn array_index_of(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::Number(-1.0)), 980 }; 981 let search = args.first().cloned().unwrap_or(Value::Undefined); 982 let from = args 983 .get(1) 984 .map(|v| { 985 let n = v.to_number() as i64; 986 let len = array_length(ctx.gc, obj_ref) as i64; 987 if n < 0 { 988 (len + n).max(0) as usize 989 } else { 990 n as usize 991 } 992 }) 993 .unwrap_or(0); 994 let len = array_length(ctx.gc, obj_ref); 995 for i in from..len { 996 let elem = array_get(ctx.gc, obj_ref, i); 997 if strict_eq_values(&elem, &search) { 998 return Ok(Value::Number(i as f64)); 999 } 1000 } 1001 Ok(Value::Number(-1.0)) 1002} 1003 1004fn array_last_index_of(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::Number(-1.0)), 1008 }; 1009 let search = args.first().cloned().unwrap_or(Value::Undefined); 1010 let len = array_length(ctx.gc, obj_ref); 1011 if len == 0 { 1012 return Ok(Value::Number(-1.0)); 1013 } 1014 let from = args 1015 .get(1) 1016 .map(|v| { 1017 let n = v.to_number() as i64; 1018 if n < 0 { 1019 (len as i64 + n) as usize 1020 } else { 1021 (n as usize).min(len - 1) 1022 } 1023 }) 1024 .unwrap_or(len - 1); 1025 for i in (0..=from).rev() { 1026 let elem = array_get(ctx.gc, obj_ref, i); 1027 if strict_eq_values(&elem, &search) { 1028 return Ok(Value::Number(i as f64)); 1029 } 1030 } 1031 Ok(Value::Number(-1.0)) 1032} 1033 1034fn array_includes(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1035 let obj_ref = match ctx.this.gc_ref() { 1036 Some(r) => r, 1037 None => return Ok(Value::Boolean(false)), 1038 }; 1039 let search = args.first().cloned().unwrap_or(Value::Undefined); 1040 let len = array_length(ctx.gc, obj_ref); 1041 let from = args 1042 .get(1) 1043 .map(|v| { 1044 let n = v.to_number() as i64; 1045 if n < 0 { 1046 (len as i64 + n).max(0) as usize 1047 } else { 1048 n as usize 1049 } 1050 }) 1051 .unwrap_or(0); 1052 for i in from..len { 1053 let elem = array_get(ctx.gc, obj_ref, i); 1054 // includes uses SameValueZero (like === but NaN === NaN). 1055 if same_value_zero(&elem, &search) { 1056 return Ok(Value::Boolean(true)); 1057 } 1058 } 1059 Ok(Value::Boolean(false)) 1060} 1061 1062fn array_join(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1063 let obj_ref = match ctx.this.gc_ref() { 1064 Some(r) => r, 1065 None => return Ok(Value::String(String::new())), 1066 }; 1067 let sep = args 1068 .first() 1069 .map(|v| { 1070 if matches!(v, Value::Undefined) { 1071 ",".to_string() 1072 } else { 1073 v.to_js_string(ctx.gc) 1074 } 1075 }) 1076 .unwrap_or_else(|| ",".to_string()); 1077 let len = array_length(ctx.gc, obj_ref); 1078 let mut parts = Vec::with_capacity(len); 1079 for i in 0..len { 1080 let elem = array_get(ctx.gc, obj_ref, i); 1081 if matches!(elem, Value::Undefined | Value::Null) { 1082 parts.push(String::new()); 1083 } else { 1084 parts.push(elem.to_js_string(ctx.gc)); 1085 } 1086 } 1087 Ok(Value::String(parts.join(&sep))) 1088} 1089 1090fn array_slice(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1091 let obj_ref = match ctx.this.gc_ref() { 1092 Some(r) => r, 1093 None => return Ok(Value::Undefined), 1094 }; 1095 let len = array_length(ctx.gc, obj_ref) as i64; 1096 let start = args 1097 .first() 1098 .map(|v| { 1099 let n = v.to_number() as i64; 1100 if n < 0 { 1101 (len + n).max(0) 1102 } else { 1103 n.min(len) 1104 } 1105 }) 1106 .unwrap_or(0) as usize; 1107 let end = args 1108 .get(1) 1109 .map(|v| { 1110 if matches!(v, Value::Undefined) { 1111 len 1112 } else { 1113 let n = v.to_number() as i64; 1114 if n < 0 { 1115 (len + n).max(0) 1116 } else { 1117 n.min(len) 1118 } 1119 } 1120 }) 1121 .unwrap_or(len) as usize; 1122 1123 let mut items = Vec::new(); 1124 for i in start..end { 1125 items.push(array_get(ctx.gc, obj_ref, i)); 1126 } 1127 Ok(make_value_array(ctx.gc, &items)) 1128} 1129 1130fn array_concat(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1131 let obj_ref = match ctx.this.gc_ref() { 1132 Some(r) => r, 1133 None => return Ok(Value::Undefined), 1134 }; 1135 let mut items = Vec::new(); 1136 // First, add elements from `this`. 1137 let len = array_length(ctx.gc, obj_ref); 1138 for i in 0..len { 1139 items.push(array_get(ctx.gc, obj_ref, i)); 1140 } 1141 // Then add from each argument. 1142 for arg in args { 1143 match arg { 1144 Value::Object(r) => { 1145 let arg_len = array_length(ctx.gc, *r); 1146 // Only spread array-like objects (those with a length property). 1147 if array_length_exists(ctx.gc, *r) { 1148 for i in 0..arg_len { 1149 items.push(array_get(ctx.gc, *r, i)); 1150 } 1151 } else { 1152 items.push(arg.clone()); 1153 } 1154 } 1155 _ => items.push(arg.clone()), 1156 } 1157 } 1158 Ok(make_value_array(ctx.gc, &items)) 1159} 1160 1161fn array_reverse(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1162 let _ = args; 1163 let obj_ref = match ctx.this.gc_ref() { 1164 Some(r) => r, 1165 None => return Ok(Value::Undefined), 1166 }; 1167 let len = array_length(ctx.gc, obj_ref); 1168 // Read all values. 1169 let mut vals: Vec<Value> = (0..len).map(|i| array_get(ctx.gc, obj_ref, i)).collect(); 1170 vals.reverse(); 1171 // Write back. 1172 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 1173 for (i, v) in vals.into_iter().enumerate() { 1174 data.properties.insert(i.to_string(), Property::data(v)); 1175 } 1176 } 1177 Ok(ctx.this.clone()) 1178} 1179 1180fn array_splice(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1181 let obj_ref = match ctx.this.gc_ref() { 1182 Some(r) => r, 1183 None => return Ok(Value::Undefined), 1184 }; 1185 let len = array_length(ctx.gc, obj_ref) as i64; 1186 let start = args 1187 .first() 1188 .map(|v| { 1189 let n = v.to_number() as i64; 1190 if n < 0 { 1191 (len + n).max(0) 1192 } else { 1193 n.min(len) 1194 } 1195 }) 1196 .unwrap_or(0) as usize; 1197 let delete_count = args 1198 .get(1) 1199 .map(|v| { 1200 let n = v.to_number() as i64; 1201 n.max(0).min(len - start as i64) as usize 1202 }) 1203 .unwrap_or((len - start as i64).max(0) as usize); 1204 let insert_items: Vec<Value> = args.iter().skip(2).cloned().collect(); 1205 1206 // Collect current values. 1207 let all_vals: Vec<Value> = (0..len as usize) 1208 .map(|i| array_get(ctx.gc, obj_ref, i)) 1209 .collect(); 1210 1211 // Build removed slice. 1212 let removed: Vec<Value> = all_vals[start..start + delete_count].to_vec(); 1213 1214 // Build new array content. 1215 let mut new_vals = Vec::new(); 1216 new_vals.extend_from_slice(&all_vals[..start]); 1217 new_vals.extend(insert_items); 1218 new_vals.extend_from_slice(&all_vals[start + delete_count..]); 1219 1220 // Write back. 1221 if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { 1222 // Remove all numeric keys. 1223 let old_keys: Vec<String> = data 1224 .properties 1225 .keys() 1226 .filter(|k| k.parse::<usize>().is_ok()) 1227 .cloned() 1228 .collect(); 1229 for k in old_keys { 1230 data.properties.remove(&k); 1231 } 1232 // Write new values. 1233 for (i, v) in new_vals.iter().enumerate() { 1234 data.properties 1235 .insert(i.to_string(), Property::data(v.clone())); 1236 } 1237 } 1238 set_array_length(ctx.gc, obj_ref, new_vals.len()); 1239 Ok(make_value_array(ctx.gc, &removed)) 1240} 1241 1242fn array_fill(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1243 let obj_ref = match ctx.this.gc_ref() { 1244 Some(r) => r, 1245 None => return Ok(Value::Undefined), 1246 }; 1247 let val = args.first().cloned().unwrap_or(Value::Undefined); 1248 let len = array_length(ctx.gc, obj_ref) as i64; 1249 let start = args 1250 .get(1) 1251 .map(|v| { 1252 let n = v.to_number() as i64; 1253 if n < 0 { 1254 (len + n).max(0) 1255 } else { 1256 n.min(len) 1257 } 1258 }) 1259 .unwrap_or(0) as usize; 1260 let end = args 1261 .get(2) 1262 .map(|v| { 1263 if matches!(v, Value::Undefined) { 1264 len 1265 } else { 1266 let n = v.to_number() as i64; 1267 if n < 0 { 1268 (len + n).max(0) 1269 } else { 1270 n.min(len) 1271 } 1272 } 1273 }) 1274 .unwrap_or(len) as usize; 1275 for i in start..end { 1276 array_set(ctx.gc, obj_ref, i, val.clone()); 1277 } 1278 Ok(ctx.this.clone()) 1279} 1280 1281fn array_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1282 // Array.prototype.toString is the same as join(","). 1283 array_join( 1284 &[Value::String(",".to_string())], 1285 &mut NativeContext { 1286 gc: ctx.gc, 1287 this: ctx.this.clone(), 1288 console_output: ctx.console_output, 1289 dom_bridge: ctx.dom_bridge, 1290 }, 1291 ) 1292 .or_else(|_| Ok(Value::String(String::new()))) 1293} 1294 1295fn array_at(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1296 let obj_ref = match ctx.this.gc_ref() { 1297 Some(r) => r, 1298 None => return Ok(Value::Undefined), 1299 }; 1300 let len = array_length(ctx.gc, obj_ref) as i64; 1301 let index = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 1302 let actual = if index < 0 { len + index } else { index }; 1303 if actual < 0 || actual >= len { 1304 Ok(Value::Undefined) 1305 } else { 1306 Ok(array_get(ctx.gc, obj_ref, actual as usize)) 1307 } 1308} 1309 1310// ── Array constructor + static methods ─────────────────────── 1311 1312fn init_array_constructor(gc: &mut Gc<HeapObject>, arr_proto: GcRef) -> GcRef { 1313 let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { 1314 name: "Array".to_string(), 1315 kind: FunctionKind::Native(NativeFunc { 1316 callback: array_constructor, 1317 }), 1318 prototype_obj: Some(arr_proto), 1319 properties: HashMap::new(), 1320 upvalues: Vec::new(), 1321 }))); 1322 1323 let is_array = make_native(gc, "isArray", array_is_array); 1324 set_func_prop(gc, ctor, "isArray", Value::Function(is_array)); 1325 1326 let from = make_native(gc, "from", array_from); 1327 set_func_prop(gc, ctor, "from", Value::Function(from)); 1328 1329 let of = make_native(gc, "of", array_of); 1330 set_func_prop(gc, ctor, "of", Value::Function(of)); 1331 1332 ctor 1333} 1334 1335fn array_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1336 if args.len() == 1 { 1337 if let Value::Number(n) = &args[0] { 1338 let len = *n as usize; 1339 let mut obj = ObjectData::new(); 1340 obj.properties.insert( 1341 "length".to_string(), 1342 Property { 1343 value: Value::Number(len as f64), 1344 writable: true, 1345 enumerable: false, 1346 configurable: false, 1347 }, 1348 ); 1349 return Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj)))); 1350 } 1351 } 1352 Ok(make_value_array(ctx.gc, args)) 1353} 1354 1355fn array_is_array(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1356 // An "array" is an object that has a numeric length property. 1357 // This is a simplified check — real JS uses an internal [[Class]] slot. 1358 match args.first() { 1359 Some(Value::Object(r)) => match ctx.gc.get(*r) { 1360 Some(HeapObject::Object(data)) => { 1361 Ok(Value::Boolean(data.properties.contains_key("length"))) 1362 } 1363 _ => Ok(Value::Boolean(false)), 1364 }, 1365 _ => Ok(Value::Boolean(false)), 1366 } 1367} 1368 1369fn array_from(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1370 let iterable = args.first().cloned().unwrap_or(Value::Undefined); 1371 match iterable { 1372 Value::Object(r) => { 1373 let len = array_length(ctx.gc, r); 1374 let mut items = Vec::with_capacity(len); 1375 for i in 0..len { 1376 items.push(array_get(ctx.gc, r, i)); 1377 } 1378 Ok(make_value_array(ctx.gc, &items)) 1379 } 1380 Value::String(s) => { 1381 let chars: Vec<Value> = s.chars().map(|c| Value::String(c.to_string())).collect(); 1382 Ok(make_value_array(ctx.gc, &chars)) 1383 } 1384 _ => Ok(make_value_array(ctx.gc, &[])), 1385 } 1386} 1387 1388fn array_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1389 Ok(make_value_array(ctx.gc, args)) 1390} 1391 1392// ── Error constructors ─────────────────────────────────────── 1393 1394fn init_error_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 1395 let to_string = make_native(gc, "toString", error_proto_to_string); 1396 set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 1397} 1398 1399fn init_error_constructors(vm: &mut Vm, err_proto: GcRef) { 1400 // Base Error. 1401 let error_ctor = make_error_constructor(&mut vm.gc, "Error", err_proto); 1402 vm.set_global("Error", Value::Function(error_ctor)); 1403 1404 // TypeError. 1405 let te_proto = make_error_subclass_proto(&mut vm.gc, "TypeError", err_proto); 1406 let te_ctor = make_error_constructor(&mut vm.gc, "TypeError", te_proto); 1407 vm.set_global("TypeError", Value::Function(te_ctor)); 1408 1409 // ReferenceError. 1410 let re_proto = make_error_subclass_proto(&mut vm.gc, "ReferenceError", err_proto); 1411 let re_ctor = make_error_constructor(&mut vm.gc, "ReferenceError", re_proto); 1412 vm.set_global("ReferenceError", Value::Function(re_ctor)); 1413 1414 // SyntaxError. 1415 let se_proto = make_error_subclass_proto(&mut vm.gc, "SyntaxError", err_proto); 1416 let se_ctor = make_error_constructor(&mut vm.gc, "SyntaxError", se_proto); 1417 vm.set_global("SyntaxError", Value::Function(se_ctor)); 1418 1419 // RangeError. 1420 let rae_proto = make_error_subclass_proto(&mut vm.gc, "RangeError", err_proto); 1421 let rae_ctor = make_error_constructor(&mut vm.gc, "RangeError", rae_proto); 1422 vm.set_global("RangeError", Value::Function(rae_ctor)); 1423 1424 // URIError. 1425 let ue_proto = make_error_subclass_proto(&mut vm.gc, "URIError", err_proto); 1426 let ue_ctor = make_error_constructor(&mut vm.gc, "URIError", ue_proto); 1427 vm.set_global("URIError", Value::Function(ue_ctor)); 1428 1429 // EvalError. 1430 let ee_proto = make_error_subclass_proto(&mut vm.gc, "EvalError", err_proto); 1431 let ee_ctor = make_error_constructor(&mut vm.gc, "EvalError", ee_proto); 1432 vm.set_global("EvalError", Value::Function(ee_ctor)); 1433} 1434 1435fn make_error_subclass_proto(gc: &mut Gc<HeapObject>, name: &str, parent_proto: GcRef) -> GcRef { 1436 let mut data = ObjectData::new(); 1437 data.prototype = Some(parent_proto); 1438 data.properties.insert( 1439 "name".to_string(), 1440 Property::builtin(Value::String(name.to_string())), 1441 ); 1442 data.properties.insert( 1443 "message".to_string(), 1444 Property::builtin(Value::String(String::new())), 1445 ); 1446 gc.alloc(HeapObject::Object(data)) 1447} 1448 1449fn make_error_constructor(gc: &mut Gc<HeapObject>, name: &str, proto: GcRef) -> GcRef { 1450 gc.alloc(HeapObject::Function(Box::new(FunctionData { 1451 name: name.to_string(), 1452 kind: FunctionKind::Native(NativeFunc { 1453 callback: error_constructor, 1454 }), 1455 prototype_obj: Some(proto), 1456 properties: HashMap::new(), 1457 upvalues: Vec::new(), 1458 }))) 1459} 1460 1461fn error_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1462 let message = args 1463 .first() 1464 .map(|v| v.to_js_string(ctx.gc)) 1465 .unwrap_or_default(); 1466 let mut obj = ObjectData::new(); 1467 obj.properties.insert( 1468 "message".to_string(), 1469 Property::data(Value::String(message)), 1470 ); 1471 // The "name" property comes from the prototype chain. 1472 Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj)))) 1473} 1474 1475fn error_proto_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1476 let obj_ref = match ctx.this.gc_ref() { 1477 Some(r) => r, 1478 None => return Ok(Value::String("Error".to_string())), 1479 }; 1480 let name = match ctx.gc.get(obj_ref) { 1481 Some(HeapObject::Object(data)) => data 1482 .properties 1483 .get("name") 1484 .map(|p| p.value.to_js_string(ctx.gc)) 1485 .unwrap_or_else(|| "Error".to_string()), 1486 _ => "Error".to_string(), 1487 }; 1488 let message = match ctx.gc.get(obj_ref) { 1489 Some(HeapObject::Object(data)) => data 1490 .properties 1491 .get("message") 1492 .map(|p| p.value.to_js_string(ctx.gc)) 1493 .unwrap_or_default(), 1494 _ => String::new(), 1495 }; 1496 if message.is_empty() { 1497 Ok(Value::String(name)) 1498 } else { 1499 Ok(Value::String(format!("{name}: {message}"))) 1500 } 1501} 1502 1503// ── Array iterators ────────────────────────────────────────── 1504 1505/// Helper: create an iterator object that yields values from a closure. 1506/// `state` is a GcRef to an object with `__items__` (array) and `__idx__` (number). 1507fn make_simple_iterator( 1508 gc: &mut Gc<HeapObject>, 1509 items: GcRef, 1510 next_fn: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>, 1511) -> Value { 1512 let mut obj = ObjectData::new(); 1513 obj.properties.insert( 1514 "__items__".to_string(), 1515 Property::builtin(Value::Object(items)), 1516 ); 1517 obj.properties 1518 .insert("__idx__".to_string(), Property::builtin(Value::Number(0.0))); 1519 let next = gc.alloc(HeapObject::Function(Box::new(FunctionData { 1520 name: "next".to_string(), 1521 kind: FunctionKind::Native(NativeFunc { callback: next_fn }), 1522 prototype_obj: None, 1523 properties: HashMap::new(), 1524 upvalues: Vec::new(), 1525 }))); 1526 obj.properties 1527 .insert("next".to_string(), Property::builtin(Value::Function(next))); 1528 1529 // @@iterator returns self. 1530 let self_iter = gc.alloc(HeapObject::Function(Box::new(FunctionData { 1531 name: "[Symbol.iterator]".to_string(), 1532 kind: FunctionKind::Native(NativeFunc { 1533 callback: iter_self, 1534 }), 1535 prototype_obj: None, 1536 properties: HashMap::new(), 1537 upvalues: Vec::new(), 1538 }))); 1539 obj.properties.insert( 1540 "@@iterator".to_string(), 1541 Property::builtin(Value::Function(self_iter)), 1542 ); 1543 1544 let r = gc.alloc(HeapObject::Object(obj)); 1545 Value::Object(r) 1546} 1547 1548fn iter_self(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1549 Ok(ctx.this.clone()) 1550} 1551 1552fn make_iterator_result_native(gc: &mut Gc<HeapObject>, value: Value, done: bool) -> Value { 1553 let mut obj = ObjectData::new(); 1554 obj.properties 1555 .insert("value".to_string(), Property::data(value)); 1556 obj.properties 1557 .insert("done".to_string(), Property::data(Value::Boolean(done))); 1558 let r = gc.alloc(HeapObject::Object(obj)); 1559 Value::Object(r) 1560} 1561 1562/// Array.prototype[@@iterator]() — same as values(). 1563fn array_iterator(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1564 array_values_iter(_args, ctx) 1565} 1566 1567/// Array.prototype.values() — returns iterator over values. 1568fn array_values_iter(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1569 let obj_ref = ctx 1570 .this 1571 .gc_ref() 1572 .ok_or_else(|| RuntimeError::type_error("values called on non-object"))?; 1573 Ok(make_simple_iterator(ctx.gc, obj_ref, array_values_next)) 1574} 1575 1576fn array_values_next(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1577 let iter_ref = ctx 1578 .this 1579 .gc_ref() 1580 .ok_or_else(|| RuntimeError::type_error("next called on non-iterator"))?; 1581 1582 let (items_ref, idx) = get_iter_state(ctx.gc, iter_ref); 1583 let items_ref = match items_ref { 1584 Some(r) => r, 1585 None => return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)), 1586 }; 1587 1588 let len = array_length(ctx.gc, items_ref); 1589 if idx >= len { 1590 return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)); 1591 } 1592 1593 let val = array_get(ctx.gc, items_ref, idx); 1594 set_iter_idx(ctx.gc, iter_ref, idx + 1); 1595 Ok(make_iterator_result_native(ctx.gc, val, false)) 1596} 1597 1598/// Array.prototype.keys() — returns iterator over indices. 1599fn array_keys_iter(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1600 let obj_ref = ctx 1601 .this 1602 .gc_ref() 1603 .ok_or_else(|| RuntimeError::type_error("keys called on non-object"))?; 1604 Ok(make_simple_iterator(ctx.gc, obj_ref, array_keys_next)) 1605} 1606 1607fn array_keys_next(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1608 let iter_ref = ctx 1609 .this 1610 .gc_ref() 1611 .ok_or_else(|| RuntimeError::type_error("next called on non-iterator"))?; 1612 let (items_ref, idx) = get_iter_state(ctx.gc, iter_ref); 1613 let items_ref = match items_ref { 1614 Some(r) => r, 1615 None => return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)), 1616 }; 1617 let len = array_length(ctx.gc, items_ref); 1618 if idx >= len { 1619 return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)); 1620 } 1621 set_iter_idx(ctx.gc, iter_ref, idx + 1); 1622 Ok(make_iterator_result_native( 1623 ctx.gc, 1624 Value::Number(idx as f64), 1625 false, 1626 )) 1627} 1628 1629/// Array.prototype.entries() — returns iterator over [index, value] pairs. 1630fn array_entries_iter(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1631 let obj_ref = ctx 1632 .this 1633 .gc_ref() 1634 .ok_or_else(|| RuntimeError::type_error("entries called on non-object"))?; 1635 Ok(make_simple_iterator(ctx.gc, obj_ref, array_entries_next)) 1636} 1637 1638fn array_entries_next(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1639 let iter_ref = ctx 1640 .this 1641 .gc_ref() 1642 .ok_or_else(|| RuntimeError::type_error("next called on non-iterator"))?; 1643 let (items_ref, idx) = get_iter_state(ctx.gc, iter_ref); 1644 let items_ref = match items_ref { 1645 Some(r) => r, 1646 None => return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)), 1647 }; 1648 let len = array_length(ctx.gc, items_ref); 1649 if idx >= len { 1650 return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)); 1651 } 1652 let val = array_get(ctx.gc, items_ref, idx); 1653 set_iter_idx(ctx.gc, iter_ref, idx + 1); 1654 1655 // Create [index, value] pair array. 1656 let mut pair = ObjectData::new(); 1657 pair.properties 1658 .insert("0".to_string(), Property::data(Value::Number(idx as f64))); 1659 pair.properties.insert("1".to_string(), Property::data(val)); 1660 pair.properties.insert( 1661 "length".to_string(), 1662 Property { 1663 value: Value::Number(2.0), 1664 writable: true, 1665 enumerable: false, 1666 configurable: false, 1667 }, 1668 ); 1669 let pair_ref = ctx.gc.alloc(HeapObject::Object(pair)); 1670 Ok(make_iterator_result_native( 1671 ctx.gc, 1672 Value::Object(pair_ref), 1673 false, 1674 )) 1675} 1676 1677/// Helper to read __items__ and __idx__ from an iterator state object. 1678fn get_iter_state(gc: &Gc<HeapObject>, iter_ref: GcRef) -> (Option<GcRef>, usize) { 1679 match gc.get(iter_ref) { 1680 Some(HeapObject::Object(data)) => { 1681 let items = data 1682 .properties 1683 .get("__items__") 1684 .and_then(|p| p.value.gc_ref()); 1685 let idx = data 1686 .properties 1687 .get("__idx__") 1688 .map(|p| p.value.to_number() as usize) 1689 .unwrap_or(0); 1690 (items, idx) 1691 } 1692 _ => (None, 0), 1693 } 1694} 1695 1696/// Helper to update __idx__ on an iterator state object. 1697fn set_iter_idx(gc: &mut Gc<HeapObject>, iter_ref: GcRef, idx: usize) { 1698 if let Some(HeapObject::Object(data)) = gc.get_mut(iter_ref) { 1699 data.properties.insert( 1700 "__idx__".to_string(), 1701 Property::builtin(Value::Number(idx as f64)), 1702 ); 1703 } 1704} 1705 1706// ── String built-in ────────────────────────────────────────── 1707 1708fn init_string_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 1709 let methods: &[NativeMethod] = &[ 1710 ("charAt", string_proto_char_at), 1711 ("charCodeAt", string_proto_char_code_at), 1712 ("codePointAt", string_proto_code_point_at), 1713 ("concat", string_proto_concat), 1714 ("slice", string_proto_slice), 1715 ("substring", string_proto_substring), 1716 ("substr", string_proto_substr), 1717 ("indexOf", string_proto_index_of), 1718 ("lastIndexOf", string_proto_last_index_of), 1719 ("includes", string_proto_includes), 1720 ("startsWith", string_proto_starts_with), 1721 ("endsWith", string_proto_ends_with), 1722 ("trim", string_proto_trim), 1723 ("trimStart", string_proto_trim_start), 1724 ("trimEnd", string_proto_trim_end), 1725 ("padStart", string_proto_pad_start), 1726 ("padEnd", string_proto_pad_end), 1727 ("repeat", string_proto_repeat), 1728 ("split", string_proto_split), 1729 ("replace", string_proto_replace), 1730 ("replaceAll", string_proto_replace_all), 1731 ("match", string_proto_match), 1732 ("matchAll", string_proto_match_all), 1733 ("search", string_proto_search), 1734 ("toLowerCase", string_proto_to_lower_case), 1735 ("toUpperCase", string_proto_to_upper_case), 1736 ("at", string_proto_at), 1737 ("toString", string_proto_to_string), 1738 ("valueOf", string_proto_value_of), 1739 ]; 1740 for &(name, callback) in methods { 1741 let f = make_native(gc, name, callback); 1742 set_builtin_prop(gc, proto, name, Value::Function(f)); 1743 } 1744 1745 // @@iterator: iterates over characters. 1746 let iter_fn = make_native(gc, "[Symbol.iterator]", string_iterator); 1747 set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter_fn)); 1748} 1749 1750/// String.prototype[@@iterator]() — returns an iterator over characters. 1751fn string_iterator(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1752 let s = ctx.this.to_js_string(ctx.gc); 1753 1754 // Store the string's characters in an array-like object. 1755 let mut items = ObjectData::new(); 1756 for (i, ch) in s.chars().enumerate() { 1757 items 1758 .properties 1759 .insert(i.to_string(), Property::data(Value::String(ch.to_string()))); 1760 } 1761 items.properties.insert( 1762 "length".to_string(), 1763 Property { 1764 value: Value::Number(s.chars().count() as f64), 1765 writable: true, 1766 enumerable: false, 1767 configurable: false, 1768 }, 1769 ); 1770 let items_ref = ctx.gc.alloc(HeapObject::Object(items)); 1771 Ok(make_simple_iterator(ctx.gc, items_ref, array_values_next)) 1772} 1773 1774fn init_string_constructor(gc: &mut Gc<HeapObject>, str_proto: GcRef) -> GcRef { 1775 let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { 1776 name: "String".to_string(), 1777 kind: FunctionKind::Native(NativeFunc { 1778 callback: string_constructor, 1779 }), 1780 prototype_obj: Some(str_proto), 1781 properties: HashMap::new(), 1782 upvalues: Vec::new(), 1783 }))); 1784 let from_char_code = make_native(gc, "fromCharCode", string_from_char_code); 1785 set_func_prop(gc, ctor, "fromCharCode", Value::Function(from_char_code)); 1786 let from_code_point = make_native(gc, "fromCodePoint", string_from_code_point); 1787 set_func_prop(gc, ctor, "fromCodePoint", Value::Function(from_code_point)); 1788 ctor 1789} 1790 1791fn string_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1792 let s = args 1793 .first() 1794 .map(|v| v.to_js_string(ctx.gc)) 1795 .unwrap_or_default(); 1796 Ok(Value::String(s)) 1797} 1798 1799fn string_from_char_code(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1800 let s: String = args 1801 .iter() 1802 .filter_map(|v| { 1803 let code = v.to_number() as u32; 1804 char::from_u32(code) 1805 }) 1806 .collect(); 1807 Ok(Value::String(s)) 1808} 1809 1810fn string_from_code_point(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1811 let mut s = String::new(); 1812 for v in args { 1813 let code = v.to_number() as u32; 1814 match char::from_u32(code) { 1815 Some(c) => s.push(c), 1816 None => { 1817 return Err(RuntimeError::range_error(format!( 1818 "Invalid code point {code}" 1819 ))) 1820 } 1821 } 1822 } 1823 Ok(Value::String(s)) 1824} 1825 1826/// Helper: extract the string from `this` for String.prototype methods. 1827fn this_string(ctx: &NativeContext) -> String { 1828 ctx.this.to_js_string(ctx.gc) 1829} 1830 1831/// Helper: get chars as a Vec for index-based operations. 1832fn str_chars(s: &str) -> Vec<char> { 1833 s.chars().collect() 1834} 1835 1836fn string_proto_char_at(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1837 let s = this_string(ctx); 1838 let chars = str_chars(&s); 1839 let idx = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 1840 if idx < 0 || idx as usize >= chars.len() { 1841 Ok(Value::String(String::new())) 1842 } else { 1843 Ok(Value::String(chars[idx as usize].to_string())) 1844 } 1845} 1846 1847fn string_proto_char_code_at( 1848 args: &[Value], 1849 ctx: &mut NativeContext, 1850) -> Result<Value, RuntimeError> { 1851 let s = this_string(ctx); 1852 let chars = str_chars(&s); 1853 let idx = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 1854 if idx < 0 || idx as usize >= chars.len() { 1855 Ok(Value::Number(f64::NAN)) 1856 } else { 1857 Ok(Value::Number(chars[idx as usize] as u32 as f64)) 1858 } 1859} 1860 1861fn string_proto_code_point_at( 1862 args: &[Value], 1863 ctx: &mut NativeContext, 1864) -> Result<Value, RuntimeError> { 1865 let s = this_string(ctx); 1866 let chars = str_chars(&s); 1867 let idx = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 1868 if idx < 0 || idx as usize >= chars.len() { 1869 Ok(Value::Undefined) 1870 } else { 1871 Ok(Value::Number(chars[idx as usize] as u32 as f64)) 1872 } 1873} 1874 1875fn string_proto_concat(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1876 let mut s = this_string(ctx); 1877 for arg in args { 1878 s.push_str(&arg.to_js_string(ctx.gc)); 1879 } 1880 Ok(Value::String(s)) 1881} 1882 1883fn string_proto_slice(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1884 let s = this_string(ctx); 1885 let chars = str_chars(&s); 1886 let len = chars.len() as i64; 1887 let start = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 1888 let end = args.get(1).map(|v| v.to_number() as i64).unwrap_or(len); 1889 let start = if start < 0 { 1890 (len + start).max(0) as usize 1891 } else { 1892 start.min(len) as usize 1893 }; 1894 let end = if end < 0 { 1895 (len + end).max(0) as usize 1896 } else { 1897 end.min(len) as usize 1898 }; 1899 if start >= end { 1900 Ok(Value::String(String::new())) 1901 } else { 1902 Ok(Value::String(chars[start..end].iter().collect())) 1903 } 1904} 1905 1906fn string_proto_substring(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1907 let s = this_string(ctx); 1908 let chars = str_chars(&s); 1909 let len = chars.len() as i64; 1910 let a = args 1911 .first() 1912 .map(|v| v.to_number() as i64) 1913 .unwrap_or(0) 1914 .clamp(0, len) as usize; 1915 let b = args 1916 .get(1) 1917 .map(|v| v.to_number() as i64) 1918 .unwrap_or(len) 1919 .clamp(0, len) as usize; 1920 let (start, end) = if a <= b { (a, b) } else { (b, a) }; 1921 Ok(Value::String(chars[start..end].iter().collect())) 1922} 1923 1924fn string_proto_substr(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1925 let s = this_string(ctx); 1926 let chars = str_chars(&s); 1927 let len = chars.len() as i64; 1928 let start = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 1929 let start = if start < 0 { 1930 (len + start).max(0) as usize 1931 } else { 1932 start.min(len) as usize 1933 }; 1934 let count = args.get(1).map(|v| v.to_number() as i64).unwrap_or(len) as usize; 1935 let end = (start + count).min(chars.len()); 1936 Ok(Value::String(chars[start..end].iter().collect())) 1937} 1938 1939fn string_proto_index_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1940 let s = this_string(ctx); 1941 let search = args 1942 .first() 1943 .map(|v| v.to_js_string(ctx.gc)) 1944 .unwrap_or_default(); 1945 let from = args.get(1).map(|v| v.to_number() as usize).unwrap_or(0); 1946 let chars = str_chars(&s); 1947 let search_chars = str_chars(&search); 1948 if search_chars.is_empty() { 1949 return Ok(Value::Number(from.min(chars.len()) as f64)); 1950 } 1951 for i in from..chars.len() { 1952 if i + search_chars.len() <= chars.len() 1953 && chars[i..i + search_chars.len()] == search_chars[..] 1954 { 1955 return Ok(Value::Number(i as f64)); 1956 } 1957 } 1958 Ok(Value::Number(-1.0)) 1959} 1960 1961fn string_proto_last_index_of( 1962 args: &[Value], 1963 ctx: &mut NativeContext, 1964) -> Result<Value, RuntimeError> { 1965 let s = this_string(ctx); 1966 let search = args 1967 .first() 1968 .map(|v| v.to_js_string(ctx.gc)) 1969 .unwrap_or_default(); 1970 let chars = str_chars(&s); 1971 let search_chars = str_chars(&search); 1972 let from = args 1973 .get(1) 1974 .map(|v| { 1975 let n = v.to_number(); 1976 if n.is_nan() { 1977 chars.len() 1978 } else { 1979 n as usize 1980 } 1981 }) 1982 .unwrap_or(chars.len()); 1983 if search_chars.is_empty() { 1984 return Ok(Value::Number(from.min(chars.len()) as f64)); 1985 } 1986 let max_start = from.min(chars.len().saturating_sub(search_chars.len())); 1987 for i in (0..=max_start).rev() { 1988 if i + search_chars.len() <= chars.len() 1989 && chars[i..i + search_chars.len()] == search_chars[..] 1990 { 1991 return Ok(Value::Number(i as f64)); 1992 } 1993 } 1994 Ok(Value::Number(-1.0)) 1995} 1996 1997fn string_proto_includes(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1998 let s = this_string(ctx); 1999 let search = args 2000 .first() 2001 .map(|v| v.to_js_string(ctx.gc)) 2002 .unwrap_or_default(); 2003 let from = args.get(1).map(|v| v.to_number() as usize).unwrap_or(0); 2004 let chars = str_chars(&s); 2005 let search_chars = str_chars(&search); 2006 if search_chars.is_empty() { 2007 return Ok(Value::Boolean(true)); 2008 } 2009 for i in from..chars.len() { 2010 if i + search_chars.len() <= chars.len() 2011 && chars[i..i + search_chars.len()] == search_chars[..] 2012 { 2013 return Ok(Value::Boolean(true)); 2014 } 2015 } 2016 Ok(Value::Boolean(false)) 2017} 2018 2019fn string_proto_starts_with( 2020 args: &[Value], 2021 ctx: &mut NativeContext, 2022) -> Result<Value, RuntimeError> { 2023 let s = this_string(ctx); 2024 let search = args 2025 .first() 2026 .map(|v| v.to_js_string(ctx.gc)) 2027 .unwrap_or_default(); 2028 let pos = args.get(1).map(|v| v.to_number() as usize).unwrap_or(0); 2029 let chars = str_chars(&s); 2030 let search_chars = str_chars(&search); 2031 if pos + search_chars.len() > chars.len() { 2032 return Ok(Value::Boolean(false)); 2033 } 2034 Ok(Value::Boolean( 2035 chars[pos..pos + search_chars.len()] == search_chars[..], 2036 )) 2037} 2038 2039fn string_proto_ends_with(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2040 let s = this_string(ctx); 2041 let search = args 2042 .first() 2043 .map(|v| v.to_js_string(ctx.gc)) 2044 .unwrap_or_default(); 2045 let chars = str_chars(&s); 2046 let search_chars = str_chars(&search); 2047 let end_pos = args 2048 .get(1) 2049 .map(|v| (v.to_number() as usize).min(chars.len())) 2050 .unwrap_or(chars.len()); 2051 if search_chars.len() > end_pos { 2052 return Ok(Value::Boolean(false)); 2053 } 2054 let start = end_pos - search_chars.len(); 2055 Ok(Value::Boolean(chars[start..end_pos] == search_chars[..])) 2056} 2057 2058fn string_proto_trim(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2059 let _ = args; 2060 Ok(Value::String(this_string(ctx).trim().to_string())) 2061} 2062 2063fn string_proto_trim_start(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2064 let _ = args; 2065 Ok(Value::String(this_string(ctx).trim_start().to_string())) 2066} 2067 2068fn string_proto_trim_end(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2069 let _ = args; 2070 Ok(Value::String(this_string(ctx).trim_end().to_string())) 2071} 2072 2073fn string_proto_pad_start(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2074 let s = this_string(ctx); 2075 let target_len = args.first().map(|v| v.to_number() as usize).unwrap_or(0); 2076 let fill = args 2077 .get(1) 2078 .map(|v| v.to_js_string(ctx.gc)) 2079 .unwrap_or_else(|| " ".to_string()); 2080 let chars = str_chars(&s); 2081 if chars.len() >= target_len || fill.is_empty() { 2082 return Ok(Value::String(s)); 2083 } 2084 let fill_chars = str_chars(&fill); 2085 let needed = target_len - chars.len(); 2086 let mut pad = String::new(); 2087 for i in 0..needed { 2088 pad.push(fill_chars[i % fill_chars.len()]); 2089 } 2090 pad.push_str(&s); 2091 Ok(Value::String(pad)) 2092} 2093 2094fn string_proto_pad_end(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2095 let s = this_string(ctx); 2096 let target_len = args.first().map(|v| v.to_number() as usize).unwrap_or(0); 2097 let fill = args 2098 .get(1) 2099 .map(|v| v.to_js_string(ctx.gc)) 2100 .unwrap_or_else(|| " ".to_string()); 2101 let chars = str_chars(&s); 2102 if chars.len() >= target_len || fill.is_empty() { 2103 return Ok(Value::String(s)); 2104 } 2105 let fill_chars = str_chars(&fill); 2106 let needed = target_len - chars.len(); 2107 let mut result = s; 2108 for i in 0..needed { 2109 result.push(fill_chars[i % fill_chars.len()]); 2110 } 2111 Ok(Value::String(result)) 2112} 2113 2114fn string_proto_repeat(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2115 let s = this_string(ctx); 2116 let count = args.first().map(|v| v.to_number()).unwrap_or(0.0); 2117 if count < 0.0 || count.is_infinite() { 2118 return Err(RuntimeError::range_error("Invalid count value")); 2119 } 2120 Ok(Value::String(s.repeat(count as usize))) 2121} 2122 2123fn string_proto_split(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2124 let s = this_string(ctx); 2125 if args.is_empty() || matches!(args.first(), Some(Value::Undefined)) { 2126 return Ok(make_value_array(ctx.gc, &[Value::String(s)])); 2127 } 2128 let limit = args 2129 .get(1) 2130 .map(|v| v.to_number() as usize) 2131 .unwrap_or(usize::MAX); 2132 2133 // Check if separator is a RegExp. 2134 if let Some(arg0) = args.first() { 2135 if is_regexp(ctx.gc, arg0) { 2136 return string_split_regexp(ctx.gc, &s, arg0, limit); 2137 } 2138 } 2139 2140 let sep = args[0].to_js_string(ctx.gc); 2141 if sep.is_empty() { 2142 let items: Vec<Value> = str_chars(&s) 2143 .into_iter() 2144 .take(limit) 2145 .map(|c| Value::String(c.to_string())) 2146 .collect(); 2147 return Ok(make_value_array(ctx.gc, &items)); 2148 } 2149 let mut items = Vec::new(); 2150 let mut start = 0; 2151 let sep_len = sep.len(); 2152 while let Some(pos) = s[start..].find(&sep) { 2153 if items.len() >= limit { 2154 break; 2155 } 2156 items.push(Value::String(s[start..start + pos].to_string())); 2157 start += pos + sep_len; 2158 } 2159 if items.len() < limit { 2160 items.push(Value::String(s[start..].to_string())); 2161 } 2162 Ok(make_value_array(ctx.gc, &items)) 2163} 2164 2165fn string_split_regexp( 2166 gc: &mut Gc<HeapObject>, 2167 s: &str, 2168 regexp: &Value, 2169 limit: usize, 2170) -> Result<Value, RuntimeError> { 2171 use crate::regex::{exec, CompiledRegex}; 2172 2173 let pattern = regexp_get_pattern(gc, regexp).unwrap_or_default(); 2174 let flags_str = regexp_get_flags(gc, regexp).unwrap_or_default(); 2175 let compiled = CompiledRegex::new(&pattern, &flags_str).map_err(RuntimeError::syntax_error)?; 2176 let chars: Vec<char> = s.chars().collect(); 2177 2178 let mut items = Vec::new(); 2179 let mut last_end = 0usize; 2180 2181 loop { 2182 if items.len() >= limit { 2183 break; 2184 } 2185 match exec(&compiled, s, last_end) { 2186 Some(m) => { 2187 // Avoid infinite loop on zero-length matches. 2188 if m.start == m.end && m.start == last_end { 2189 if last_end >= chars.len() { 2190 break; 2191 } 2192 items.push(Value::String(chars[last_end].to_string())); 2193 last_end += 1; 2194 continue; 2195 } 2196 let piece: String = chars[last_end..m.start].iter().collect(); 2197 items.push(Value::String(piece)); 2198 // Add capturing groups. 2199 for i in 1..m.captures.len() { 2200 if items.len() >= limit { 2201 break; 2202 } 2203 match m.captures[i] { 2204 Some((cs, ce)) => { 2205 let cap: String = chars[cs..ce].iter().collect(); 2206 items.push(Value::String(cap)); 2207 } 2208 None => items.push(Value::Undefined), 2209 } 2210 } 2211 last_end = m.end; 2212 } 2213 None => break, 2214 } 2215 } 2216 if items.len() < limit { 2217 let rest: String = chars[last_end..].iter().collect(); 2218 items.push(Value::String(rest)); 2219 } 2220 Ok(make_value_array(gc, &items)) 2221} 2222 2223fn string_proto_replace(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2224 let s = this_string(ctx); 2225 2226 // Check if search argument is a RegExp. 2227 if let Some(arg0) = args.first() { 2228 if is_regexp(ctx.gc, arg0) { 2229 let replacement = args 2230 .get(1) 2231 .map(|v| v.to_js_string(ctx.gc)) 2232 .unwrap_or_default(); 2233 return string_replace_regexp(ctx.gc, &s, arg0, &replacement); 2234 } 2235 } 2236 2237 let search = args 2238 .first() 2239 .map(|v| v.to_js_string(ctx.gc)) 2240 .unwrap_or_default(); 2241 let replacement = args 2242 .get(1) 2243 .map(|v| v.to_js_string(ctx.gc)) 2244 .unwrap_or_default(); 2245 // Replace only the first occurrence. 2246 if let Some(pos) = s.find(&search) { 2247 let mut result = String::with_capacity(s.len()); 2248 result.push_str(&s[..pos]); 2249 result.push_str(&replacement); 2250 result.push_str(&s[pos + search.len()..]); 2251 Ok(Value::String(result)) 2252 } else { 2253 Ok(Value::String(s)) 2254 } 2255} 2256 2257fn string_replace_regexp( 2258 gc: &mut Gc<HeapObject>, 2259 s: &str, 2260 regexp: &Value, 2261 replacement: &str, 2262) -> Result<Value, RuntimeError> { 2263 use crate::regex::{exec, CompiledRegex}; 2264 2265 let pattern = regexp_get_pattern(gc, regexp).unwrap_or_default(); 2266 let flags_str = regexp_get_flags(gc, regexp).unwrap_or_default(); 2267 let compiled = CompiledRegex::new(&pattern, &flags_str).map_err(RuntimeError::syntax_error)?; 2268 let is_global = compiled.flags.global; 2269 let chars: Vec<char> = s.chars().collect(); 2270 let mut result = String::new(); 2271 let mut last_end = 0usize; 2272 2273 while let Some(m) = exec(&compiled, s, last_end) { 2274 // Append text before match. 2275 let before: String = chars[last_end..m.start].iter().collect(); 2276 result.push_str(&before); 2277 // Process replacement with $-substitutions. 2278 let matched: String = chars[m.start..m.end].iter().collect(); 2279 result.push_str(&apply_replacement( 2280 replacement, 2281 &matched, 2282 &m.captures, 2283 &chars, 2284 )); 2285 last_end = m.end; 2286 if !is_global { 2287 break; 2288 } 2289 // Avoid infinite loop on zero-length match. 2290 if m.start == m.end { 2291 if last_end < chars.len() { 2292 result.push(chars[last_end]); 2293 last_end += 1; 2294 } else { 2295 break; 2296 } 2297 } 2298 } 2299 let rest: String = chars[last_end..].iter().collect(); 2300 result.push_str(&rest); 2301 Ok(Value::String(result)) 2302} 2303 2304/// Apply replacement string with $-substitutions ($&, $1, etc.). 2305fn apply_replacement( 2306 replacement: &str, 2307 matched: &str, 2308 captures: &[Option<(usize, usize)>], 2309 chars: &[char], 2310) -> String { 2311 let rep_chars: Vec<char> = replacement.chars().collect(); 2312 let mut result = String::new(); 2313 let mut i = 0; 2314 while i < rep_chars.len() { 2315 if rep_chars[i] == '$' && i + 1 < rep_chars.len() { 2316 match rep_chars[i + 1] { 2317 '$' => { 2318 result.push('$'); 2319 i += 2; 2320 } 2321 '&' => { 2322 result.push_str(matched); 2323 i += 2; 2324 } 2325 '`' => { 2326 // $` — text before match. 2327 if let Some(Some((start, _))) = captures.first() { 2328 let before: String = chars[..*start].iter().collect(); 2329 result.push_str(&before); 2330 } 2331 i += 2; 2332 } 2333 '\'' => { 2334 // $' — text after match. 2335 if let Some(Some((_, end))) = captures.first() { 2336 let after: String = chars[*end..].iter().collect(); 2337 result.push_str(&after); 2338 } 2339 i += 2; 2340 } 2341 d if d.is_ascii_digit() => { 2342 // $1, $12 etc. 2343 let mut num_str = String::new(); 2344 let mut j = i + 1; 2345 while j < rep_chars.len() && rep_chars[j].is_ascii_digit() { 2346 num_str.push(rep_chars[j]); 2347 j += 1; 2348 } 2349 if let Ok(idx) = num_str.parse::<usize>() { 2350 if idx > 0 && idx < captures.len() { 2351 if let Some((s, e)) = captures[idx] { 2352 let cap: String = chars[s..e].iter().collect(); 2353 result.push_str(&cap); 2354 } 2355 } 2356 } 2357 i = j; 2358 } 2359 _ => { 2360 result.push('$'); 2361 i += 1; 2362 } 2363 } 2364 } else { 2365 result.push(rep_chars[i]); 2366 i += 1; 2367 } 2368 } 2369 result 2370} 2371 2372fn string_proto_replace_all( 2373 args: &[Value], 2374 ctx: &mut NativeContext, 2375) -> Result<Value, RuntimeError> { 2376 let s = this_string(ctx); 2377 2378 // If search is a RegExp, it must have the global flag. 2379 if let Some(arg0) = args.first() { 2380 if is_regexp(ctx.gc, arg0) { 2381 let flags = regexp_get_flags(ctx.gc, arg0).unwrap_or_default(); 2382 if !flags.contains('g') { 2383 return Err(RuntimeError::type_error( 2384 "String.prototype.replaceAll called with a non-global RegExp argument", 2385 )); 2386 } 2387 let replacement = args 2388 .get(1) 2389 .map(|v| v.to_js_string(ctx.gc)) 2390 .unwrap_or_default(); 2391 return string_replace_regexp(ctx.gc, &s, arg0, &replacement); 2392 } 2393 } 2394 2395 let search = args 2396 .first() 2397 .map(|v| v.to_js_string(ctx.gc)) 2398 .unwrap_or_default(); 2399 let replacement = args 2400 .get(1) 2401 .map(|v| v.to_js_string(ctx.gc)) 2402 .unwrap_or_default(); 2403 Ok(Value::String(s.replace(&search, &replacement))) 2404} 2405 2406fn string_proto_match(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2407 let s = this_string(ctx); 2408 if args.is_empty() { 2409 return Ok(Value::Null); 2410 } 2411 2412 let arg0 = &args[0]; 2413 // If arg is not a RegExp, create one. 2414 let regexp_val = if is_regexp(ctx.gc, arg0) { 2415 arg0.clone() 2416 } else { 2417 let pattern = arg0.to_js_string(ctx.gc); 2418 let proto = REGEXP_PROTO.with(|cell| cell.get()); 2419 make_regexp_obj(ctx.gc, &pattern, "", proto).map_err(RuntimeError::syntax_error)? 2420 }; 2421 2422 let is_global = regexp_get_flags(ctx.gc, &regexp_val) 2423 .map(|f| f.contains('g')) 2424 .unwrap_or(false); 2425 2426 if !is_global { 2427 // Non-global: return exec result. 2428 return regexp_exec_internal(ctx.gc, &regexp_val, &s); 2429 } 2430 2431 // Global: collect all matches. 2432 regexp_set_last_index(ctx.gc, &regexp_val, 0.0); 2433 let mut matches = Vec::new(); 2434 loop { 2435 let result = regexp_exec_internal(ctx.gc, &regexp_val, &s)?; 2436 if matches!(result, Value::Null) { 2437 break; 2438 } 2439 // Get the matched string (index 0 of the result array). 2440 if let Value::Object(r) = &result { 2441 if let Some(HeapObject::Object(data)) = ctx.gc.get(*r) { 2442 if let Some(prop) = data.properties.get("0") { 2443 matches.push(prop.value.clone()); 2444 // Advance past zero-length matches. 2445 let match_str = prop.value.to_js_string(ctx.gc); 2446 if match_str.is_empty() { 2447 let li = regexp_get_last_index(ctx.gc, &regexp_val); 2448 regexp_set_last_index(ctx.gc, &regexp_val, li + 1.0); 2449 } 2450 } 2451 } 2452 } 2453 } 2454 if matches.is_empty() { 2455 Ok(Value::Null) 2456 } else { 2457 Ok(make_value_array(ctx.gc, &matches)) 2458 } 2459} 2460 2461fn string_proto_match_all(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2462 let s = this_string(ctx); 2463 if args.is_empty() { 2464 return Ok(make_value_array(ctx.gc, &[])); 2465 } 2466 2467 let arg0 = &args[0]; 2468 // If arg is a RegExp, it must have global flag. 2469 let regexp_val = if is_regexp(ctx.gc, arg0) { 2470 let flags = regexp_get_flags(ctx.gc, arg0).unwrap_or_default(); 2471 if !flags.contains('g') { 2472 return Err(RuntimeError::type_error( 2473 "String.prototype.matchAll called with a non-global RegExp argument", 2474 )); 2475 } 2476 arg0.clone() 2477 } else { 2478 let pattern = arg0.to_js_string(ctx.gc); 2479 let proto = REGEXP_PROTO.with(|cell| cell.get()); 2480 make_regexp_obj(ctx.gc, &pattern, "g", proto).map_err(RuntimeError::syntax_error)? 2481 }; 2482 2483 // Collect all match results. 2484 regexp_set_last_index(ctx.gc, &regexp_val, 0.0); 2485 let mut results = Vec::new(); 2486 loop { 2487 let result = regexp_exec_internal(ctx.gc, &regexp_val, &s)?; 2488 if matches!(result, Value::Null) { 2489 break; 2490 } 2491 // Advance past zero-length matches. 2492 if let Value::Object(r) = &result { 2493 if let Some(HeapObject::Object(data)) = ctx.gc.get(*r) { 2494 if let Some(prop) = data.properties.get("0") { 2495 let match_str = prop.value.to_js_string(ctx.gc); 2496 if match_str.is_empty() { 2497 let li = regexp_get_last_index(ctx.gc, &regexp_val); 2498 regexp_set_last_index(ctx.gc, &regexp_val, li + 1.0); 2499 } 2500 } 2501 } 2502 } 2503 results.push(result); 2504 } 2505 Ok(make_value_array(ctx.gc, &results)) 2506} 2507 2508fn string_proto_search(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2509 let s = this_string(ctx); 2510 if args.is_empty() { 2511 return Ok(Value::Number(0.0)); // /(?:)/ matches at 0. 2512 } 2513 2514 let arg0 = &args[0]; 2515 let regexp_val = if is_regexp(ctx.gc, arg0) { 2516 arg0.clone() 2517 } else { 2518 let pattern = arg0.to_js_string(ctx.gc); 2519 let proto = REGEXP_PROTO.with(|cell| cell.get()); 2520 make_regexp_obj(ctx.gc, &pattern, "", proto).map_err(RuntimeError::syntax_error)? 2521 }; 2522 2523 // search always starts from 0 and ignores global/lastIndex. 2524 // Save and restore lastIndex so exec_internal's global/sticky handling doesn't interfere. 2525 let saved_last_index = regexp_get_last_index(ctx.gc, &regexp_val); 2526 regexp_set_last_index(ctx.gc, &regexp_val, 0.0); 2527 let result = regexp_exec_internal(ctx.gc, &regexp_val, &s)?; 2528 regexp_set_last_index(ctx.gc, &regexp_val, saved_last_index); 2529 match result { 2530 Value::Null => Ok(Value::Number(-1.0)), 2531 Value::Object(r) => { 2532 let idx = match ctx.gc.get(r) { 2533 Some(HeapObject::Object(data)) => data 2534 .properties 2535 .get("index") 2536 .map(|p| p.value.to_number()) 2537 .unwrap_or(-1.0), 2538 _ => -1.0, 2539 }; 2540 Ok(Value::Number(idx)) 2541 } 2542 _ => Ok(Value::Number(-1.0)), 2543 } 2544} 2545 2546fn string_proto_to_lower_case( 2547 args: &[Value], 2548 ctx: &mut NativeContext, 2549) -> Result<Value, RuntimeError> { 2550 let _ = args; 2551 Ok(Value::String(this_string(ctx).to_lowercase())) 2552} 2553 2554fn string_proto_to_upper_case( 2555 args: &[Value], 2556 ctx: &mut NativeContext, 2557) -> Result<Value, RuntimeError> { 2558 let _ = args; 2559 Ok(Value::String(this_string(ctx).to_uppercase())) 2560} 2561 2562fn string_proto_at(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2563 let s = this_string(ctx); 2564 let chars = str_chars(&s); 2565 let idx = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 2566 let actual = if idx < 0 { 2567 chars.len() as i64 + idx 2568 } else { 2569 idx 2570 }; 2571 if actual < 0 || actual as usize >= chars.len() { 2572 Ok(Value::Undefined) 2573 } else { 2574 Ok(Value::String(chars[actual as usize].to_string())) 2575 } 2576} 2577 2578fn string_proto_to_string(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2579 let _ = args; 2580 Ok(Value::String(this_string(ctx))) 2581} 2582 2583fn string_proto_value_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2584 let _ = args; 2585 Ok(Value::String(this_string(ctx))) 2586} 2587 2588// ── Number built-in ────────────────────────────────────────── 2589 2590fn init_number_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 2591 let methods: &[NativeMethod] = &[ 2592 ("toString", number_proto_to_string), 2593 ("valueOf", number_proto_value_of), 2594 ("toFixed", number_proto_to_fixed), 2595 ("toPrecision", number_proto_to_precision), 2596 ("toExponential", number_proto_to_exponential), 2597 ]; 2598 for &(name, callback) in methods { 2599 let f = make_native(gc, name, callback); 2600 set_builtin_prop(gc, proto, name, Value::Function(f)); 2601 } 2602} 2603 2604fn init_number_constructor(gc: &mut Gc<HeapObject>, num_proto: GcRef) -> GcRef { 2605 let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { 2606 name: "Number".to_string(), 2607 kind: FunctionKind::Native(NativeFunc { 2608 callback: number_constructor, 2609 }), 2610 prototype_obj: Some(num_proto), 2611 properties: HashMap::new(), 2612 upvalues: Vec::new(), 2613 }))); 2614 // Static methods. 2615 let is_nan = make_native(gc, "isNaN", number_is_nan); 2616 set_func_prop(gc, ctor, "isNaN", Value::Function(is_nan)); 2617 let is_finite = make_native(gc, "isFinite", number_is_finite); 2618 set_func_prop(gc, ctor, "isFinite", Value::Function(is_finite)); 2619 let is_integer = make_native(gc, "isInteger", number_is_integer); 2620 set_func_prop(gc, ctor, "isInteger", Value::Function(is_integer)); 2621 let is_safe_integer = make_native(gc, "isSafeInteger", number_is_safe_integer); 2622 set_func_prop(gc, ctor, "isSafeInteger", Value::Function(is_safe_integer)); 2623 let parse_int_fn = make_native(gc, "parseInt", crate::builtins::parse_int); 2624 set_func_prop(gc, ctor, "parseInt", Value::Function(parse_int_fn)); 2625 let parse_float_fn = make_native(gc, "parseFloat", crate::builtins::parse_float); 2626 set_func_prop(gc, ctor, "parseFloat", Value::Function(parse_float_fn)); 2627 // Constants. 2628 set_func_prop(gc, ctor, "EPSILON", Value::Number(f64::EPSILON)); 2629 set_func_prop( 2630 gc, 2631 ctor, 2632 "MAX_SAFE_INTEGER", 2633 Value::Number(9007199254740991.0), 2634 ); 2635 set_func_prop( 2636 gc, 2637 ctor, 2638 "MIN_SAFE_INTEGER", 2639 Value::Number(-9007199254740991.0), 2640 ); 2641 set_func_prop(gc, ctor, "MAX_VALUE", Value::Number(f64::MAX)); 2642 set_func_prop(gc, ctor, "MIN_VALUE", Value::Number(f64::MIN_POSITIVE)); 2643 set_func_prop(gc, ctor, "NaN", Value::Number(f64::NAN)); 2644 set_func_prop(gc, ctor, "POSITIVE_INFINITY", Value::Number(f64::INFINITY)); 2645 set_func_prop( 2646 gc, 2647 ctor, 2648 "NEGATIVE_INFINITY", 2649 Value::Number(f64::NEG_INFINITY), 2650 ); 2651 ctor 2652} 2653 2654fn number_constructor(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2655 let n = args.first().map(|v| v.to_number()).unwrap_or(0.0); 2656 Ok(Value::Number(n)) 2657} 2658 2659fn number_is_nan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2660 match args.first() { 2661 Some(Value::Number(n)) => Ok(Value::Boolean(n.is_nan())), 2662 _ => Ok(Value::Boolean(false)), 2663 } 2664} 2665 2666fn number_is_finite(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2667 match args.first() { 2668 Some(Value::Number(n)) => Ok(Value::Boolean(n.is_finite())), 2669 _ => Ok(Value::Boolean(false)), 2670 } 2671} 2672 2673fn number_is_integer(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2674 match args.first() { 2675 Some(Value::Number(n)) => Ok(Value::Boolean(n.is_finite() && n.trunc() == *n)), 2676 _ => Ok(Value::Boolean(false)), 2677 } 2678} 2679 2680fn number_is_safe_integer(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2681 match args.first() { 2682 Some(Value::Number(n)) => { 2683 let safe = n.is_finite() && n.trunc() == *n && n.abs() <= 9007199254740991.0; 2684 Ok(Value::Boolean(safe)) 2685 } 2686 _ => Ok(Value::Boolean(false)), 2687 } 2688} 2689 2690/// Helper: extract the number from `this` for Number.prototype methods. 2691fn this_number(ctx: &NativeContext) -> f64 { 2692 ctx.this.to_number() 2693} 2694 2695fn number_proto_to_string(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2696 let n = this_number(ctx); 2697 let radix = args.first().map(|v| v.to_number() as u32).unwrap_or(10); 2698 if !(2..=36).contains(&radix) { 2699 return Err(RuntimeError::range_error( 2700 "toString() radix must be between 2 and 36", 2701 )); 2702 } 2703 if radix == 10 { 2704 return Ok(Value::String(Value::Number(n).to_js_string(ctx.gc))); 2705 } 2706 if n.is_nan() { 2707 return Ok(Value::String("NaN".to_string())); 2708 } 2709 if n.is_infinite() { 2710 return Ok(Value::String(if n > 0.0 { 2711 "Infinity".to_string() 2712 } else { 2713 "-Infinity".to_string() 2714 })); 2715 } 2716 // Integer path for non-decimal radix. 2717 let neg = n < 0.0; 2718 let abs = n.abs() as u64; 2719 let mut digits = Vec::new(); 2720 let mut val = abs; 2721 if val == 0 { 2722 digits.push('0'); 2723 } else { 2724 while val > 0 { 2725 let d = (val % radix as u64) as u32; 2726 digits.push(char::from_digit(d, radix).unwrap_or('?')); 2727 val /= radix as u64; 2728 } 2729 } 2730 digits.reverse(); 2731 let mut result = String::new(); 2732 if neg { 2733 result.push('-'); 2734 } 2735 result.extend(digits); 2736 Ok(Value::String(result)) 2737} 2738 2739fn number_proto_value_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2740 let _ = args; 2741 Ok(Value::Number(this_number(ctx))) 2742} 2743 2744fn number_proto_to_fixed(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2745 let n = this_number(ctx); 2746 let digits = args.first().map(|v| v.to_number() as usize).unwrap_or(0); 2747 if digits > 100 { 2748 return Err(RuntimeError::range_error( 2749 "toFixed() digits argument must be between 0 and 100", 2750 )); 2751 } 2752 Ok(Value::String(format!("{n:.digits$}"))) 2753} 2754 2755fn number_proto_to_precision( 2756 args: &[Value], 2757 ctx: &mut NativeContext, 2758) -> Result<Value, RuntimeError> { 2759 let n = this_number(ctx); 2760 if args.is_empty() || matches!(args.first(), Some(Value::Undefined)) { 2761 return Ok(Value::String(Value::Number(n).to_js_string(ctx.gc))); 2762 } 2763 let prec = args[0].to_number() as usize; 2764 if !(1..=100).contains(&prec) { 2765 return Err(RuntimeError::range_error( 2766 "toPrecision() argument must be between 1 and 100", 2767 )); 2768 } 2769 Ok(Value::String(format!("{n:.prec$e}"))) 2770} 2771 2772fn number_proto_to_exponential( 2773 args: &[Value], 2774 ctx: &mut NativeContext, 2775) -> Result<Value, RuntimeError> { 2776 let n = this_number(ctx); 2777 let digits = args.first().map(|v| v.to_number() as usize).unwrap_or(6); 2778 if digits > 100 { 2779 return Err(RuntimeError::range_error( 2780 "toExponential() argument must be between 0 and 100", 2781 )); 2782 } 2783 Ok(Value::String(format!("{n:.digits$e}"))) 2784} 2785 2786// ── Boolean built-in ───────────────────────────────────────── 2787 2788fn init_boolean_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 2789 let to_string = make_native(gc, "toString", boolean_proto_to_string); 2790 set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); 2791 let value_of = make_native(gc, "valueOf", boolean_proto_value_of); 2792 set_builtin_prop(gc, proto, "valueOf", Value::Function(value_of)); 2793} 2794 2795fn init_boolean_constructor(gc: &mut Gc<HeapObject>, bool_proto: GcRef) -> GcRef { 2796 gc.alloc(HeapObject::Function(Box::new(FunctionData { 2797 name: "Boolean".to_string(), 2798 kind: FunctionKind::Native(NativeFunc { 2799 callback: boolean_constructor, 2800 }), 2801 prototype_obj: Some(bool_proto), 2802 properties: HashMap::new(), 2803 upvalues: Vec::new(), 2804 }))) 2805} 2806 2807fn boolean_constructor(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2808 let b = args.first().map(|v| v.to_boolean()).unwrap_or(false); 2809 Ok(Value::Boolean(b)) 2810} 2811 2812fn boolean_proto_to_string(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2813 let _ = args; 2814 Ok(Value::String( 2815 if ctx.this.to_boolean() { 2816 "true" 2817 } else { 2818 "false" 2819 } 2820 .to_string(), 2821 )) 2822} 2823 2824fn boolean_proto_value_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2825 let _ = args; 2826 Ok(Value::Boolean(ctx.this.to_boolean())) 2827} 2828 2829// ── Symbol built-in ────────────────────────────────────────── 2830 2831/// Global symbol ID counter. Each Symbol() call increments this. 2832static SYMBOL_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); 2833 2834// Thread-local Date prototype GcRef for use in date_constructor. 2835// The Date constructor callback doesn't have VM access, so we store the 2836// prototype here during init and read it back during construction. 2837thread_local! { 2838 static DATE_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 2839} 2840 2841fn init_symbol_builtins(vm: &mut Vm) { 2842 // Create a function object so we can hang static props on it. 2843 let gc_ref = make_native(&mut vm.gc, "Symbol", symbol_factory); 2844 2845 // Well-known symbols as string constants. 2846 let well_known = [ 2847 ("iterator", "@@iterator"), 2848 ("toPrimitive", "@@toPrimitive"), 2849 ("toStringTag", "@@toStringTag"), 2850 ("hasInstance", "@@hasInstance"), 2851 ]; 2852 for (name, value) in well_known { 2853 set_func_prop(&mut vm.gc, gc_ref, name, Value::String(value.to_string())); 2854 } 2855 2856 // Symbol.for() and Symbol.keyFor(). 2857 let sym_for = make_native(&mut vm.gc, "for", symbol_for); 2858 set_func_prop(&mut vm.gc, gc_ref, "for", Value::Function(sym_for)); 2859 let sym_key_for = make_native(&mut vm.gc, "keyFor", symbol_key_for); 2860 set_func_prop(&mut vm.gc, gc_ref, "keyFor", Value::Function(sym_key_for)); 2861 2862 vm.set_global("Symbol", Value::Function(gc_ref)); 2863 2864 // Register global NaN and Infinity constants. 2865 vm.set_global("NaN", Value::Number(f64::NAN)); 2866 vm.set_global("Infinity", Value::Number(f64::INFINITY)); 2867 vm.set_global("undefined", Value::Undefined); 2868} 2869 2870fn symbol_factory(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2871 let desc = args 2872 .first() 2873 .map(|v| v.to_js_string(ctx.gc)) 2874 .unwrap_or_default(); 2875 let id = SYMBOL_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed); 2876 // Return a unique string representation. Real Symbol is a distinct type, 2877 // but this pragmatic approach works for property keys and identity checks. 2878 Ok(Value::String(format!("@@sym_{id}_{desc}"))) 2879} 2880 2881fn symbol_for(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2882 let key = args 2883 .first() 2884 .map(|v| v.to_js_string(ctx.gc)) 2885 .unwrap_or_default(); 2886 // Deterministic: same key always produces same symbol string. 2887 Ok(Value::String(format!("@@global_{key}"))) 2888} 2889 2890fn symbol_key_for(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2891 let sym = args 2892 .first() 2893 .map(|v| v.to_js_string(ctx.gc)) 2894 .unwrap_or_default(); 2895 if let Some(key) = sym.strip_prefix("@@global_") { 2896 Ok(Value::String(key.to_string())) 2897 } else { 2898 Ok(Value::Undefined) 2899 } 2900} 2901 2902// ── Math object ────────────────────────────────────────────── 2903 2904/// Simple xorshift64 PRNG state (no crypto requirement). 2905static PRNG_STATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); 2906 2907fn prng_next() -> f64 { 2908 let mut s = PRNG_STATE.load(std::sync::atomic::Ordering::Relaxed); 2909 if s == 0 { 2910 // Seed from system time. 2911 s = SystemTime::now() 2912 .duration_since(UNIX_EPOCH) 2913 .map(|d| d.as_nanos() as u64) 2914 .unwrap_or(123456789); 2915 } 2916 s ^= s << 13; 2917 s ^= s >> 7; 2918 s ^= s << 17; 2919 PRNG_STATE.store(s, std::sync::atomic::Ordering::Relaxed); 2920 // Map to [0, 1). 2921 (s >> 11) as f64 / ((1u64 << 53) as f64) 2922} 2923 2924fn init_math_object(vm: &mut Vm) { 2925 let mut data = ObjectData::new(); 2926 if let Some(proto) = vm.object_prototype { 2927 data.prototype = Some(proto); 2928 } 2929 let math_ref = vm.gc.alloc(HeapObject::Object(data)); 2930 2931 // Constants. 2932 let constants: &[(&str, f64)] = &[ 2933 ("E", std::f64::consts::E), 2934 ("LN2", std::f64::consts::LN_2), 2935 ("LN10", std::f64::consts::LN_10), 2936 ("LOG2E", std::f64::consts::LOG2_E), 2937 ("LOG10E", std::f64::consts::LOG10_E), 2938 ("PI", std::f64::consts::PI), 2939 ("SQRT1_2", std::f64::consts::FRAC_1_SQRT_2), 2940 ("SQRT2", std::f64::consts::SQRT_2), 2941 ]; 2942 for &(name, val) in constants { 2943 set_builtin_prop(&mut vm.gc, math_ref, name, Value::Number(val)); 2944 } 2945 2946 // Methods. 2947 let methods: &[NativeMethod] = &[ 2948 ("abs", math_abs), 2949 ("ceil", math_ceil), 2950 ("floor", math_floor), 2951 ("round", math_round), 2952 ("trunc", math_trunc), 2953 ("max", math_max), 2954 ("min", math_min), 2955 ("pow", math_pow), 2956 ("sqrt", math_sqrt), 2957 ("cbrt", math_cbrt), 2958 ("hypot", math_hypot), 2959 ("sin", math_sin), 2960 ("cos", math_cos), 2961 ("tan", math_tan), 2962 ("asin", math_asin), 2963 ("acos", math_acos), 2964 ("atan", math_atan), 2965 ("atan2", math_atan2), 2966 ("exp", math_exp), 2967 ("log", math_log), 2968 ("log2", math_log2), 2969 ("log10", math_log10), 2970 ("expm1", math_expm1), 2971 ("log1p", math_log1p), 2972 ("sign", math_sign), 2973 ("clz32", math_clz32), 2974 ("fround", math_fround), 2975 ("random", math_random), 2976 ("imul", math_imul), 2977 ]; 2978 for &(name, cb) in methods { 2979 let f = make_native(&mut vm.gc, name, cb); 2980 set_builtin_prop(&mut vm.gc, math_ref, name, Value::Function(f)); 2981 } 2982 2983 vm.set_global("Math", Value::Object(math_ref)); 2984} 2985 2986fn math_abs(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2987 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2988 Ok(Value::Number(n.abs())) 2989} 2990 2991fn math_ceil(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2992 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2993 Ok(Value::Number(n.ceil())) 2994} 2995 2996fn math_floor(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2997 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2998 Ok(Value::Number(n.floor())) 2999} 3000 3001fn math_round(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3002 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3003 // JS Math.round rounds .5 up (toward +Infinity). 3004 Ok(Value::Number(n.round())) 3005} 3006 3007fn math_trunc(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3008 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3009 Ok(Value::Number(n.trunc())) 3010} 3011 3012fn math_max(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3013 if args.is_empty() { 3014 return Ok(Value::Number(f64::NEG_INFINITY)); 3015 } 3016 let mut result = f64::NEG_INFINITY; 3017 for arg in args { 3018 let n = arg.to_number(); 3019 if n.is_nan() { 3020 return Ok(Value::Number(f64::NAN)); 3021 } 3022 if n > result { 3023 result = n; 3024 } 3025 } 3026 Ok(Value::Number(result)) 3027} 3028 3029fn math_min(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3030 if args.is_empty() { 3031 return Ok(Value::Number(f64::INFINITY)); 3032 } 3033 let mut result = f64::INFINITY; 3034 for arg in args { 3035 let n = arg.to_number(); 3036 if n.is_nan() { 3037 return Ok(Value::Number(f64::NAN)); 3038 } 3039 if n < result { 3040 result = n; 3041 } 3042 } 3043 Ok(Value::Number(result)) 3044} 3045 3046fn math_pow(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3047 let base = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3048 let exp = args.get(1).map(|v| v.to_number()).unwrap_or(f64::NAN); 3049 Ok(Value::Number(base.powf(exp))) 3050} 3051 3052fn math_sqrt(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3053 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3054 Ok(Value::Number(n.sqrt())) 3055} 3056 3057fn math_cbrt(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3058 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3059 Ok(Value::Number(n.cbrt())) 3060} 3061 3062fn math_hypot(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3063 if args.is_empty() { 3064 return Ok(Value::Number(0.0)); 3065 } 3066 let mut sum = 0.0f64; 3067 for arg in args { 3068 let n = arg.to_number(); 3069 if n.is_infinite() { 3070 return Ok(Value::Number(f64::INFINITY)); 3071 } 3072 if n.is_nan() { 3073 return Ok(Value::Number(f64::NAN)); 3074 } 3075 sum += n * n; 3076 } 3077 Ok(Value::Number(sum.sqrt())) 3078} 3079 3080fn math_sin(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3081 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3082 Ok(Value::Number(n.sin())) 3083} 3084 3085fn math_cos(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3086 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3087 Ok(Value::Number(n.cos())) 3088} 3089 3090fn math_tan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3091 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3092 Ok(Value::Number(n.tan())) 3093} 3094 3095fn math_asin(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3096 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3097 Ok(Value::Number(n.asin())) 3098} 3099 3100fn math_acos(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3101 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3102 Ok(Value::Number(n.acos())) 3103} 3104 3105fn math_atan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3106 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3107 Ok(Value::Number(n.atan())) 3108} 3109 3110fn math_atan2(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3111 let y = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3112 let x = args.get(1).map(|v| v.to_number()).unwrap_or(f64::NAN); 3113 Ok(Value::Number(y.atan2(x))) 3114} 3115 3116fn math_exp(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3117 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3118 Ok(Value::Number(n.exp())) 3119} 3120 3121fn math_log(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3122 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3123 Ok(Value::Number(n.ln())) 3124} 3125 3126fn math_log2(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3127 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3128 Ok(Value::Number(n.log2())) 3129} 3130 3131fn math_log10(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3132 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3133 Ok(Value::Number(n.log10())) 3134} 3135 3136fn math_expm1(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3137 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3138 Ok(Value::Number(n.exp_m1())) 3139} 3140 3141fn math_log1p(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3142 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3143 Ok(Value::Number(n.ln_1p())) 3144} 3145 3146fn math_sign(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3147 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3148 if n.is_nan() { 3149 Ok(Value::Number(f64::NAN)) 3150 } else if n == 0.0 { 3151 // Preserve -0 and +0. 3152 Ok(Value::Number(n)) 3153 } else if n > 0.0 { 3154 Ok(Value::Number(1.0)) 3155 } else { 3156 Ok(Value::Number(-1.0)) 3157 } 3158} 3159 3160fn math_clz32(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3161 let n = args.first().map(|v| v.to_number()).unwrap_or(0.0); 3162 let i = n as u32; 3163 Ok(Value::Number(i.leading_zeros() as f64)) 3164} 3165 3166fn math_fround(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3167 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3168 Ok(Value::Number((n as f32) as f64)) 3169} 3170 3171fn math_random(_args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3172 Ok(Value::Number(prng_next())) 3173} 3174 3175fn math_imul(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3176 // ToUint32 then reinterpret as i32 per spec. 3177 let a = args.first().map(|v| v.to_number()).unwrap_or(0.0) as u32 as i32; 3178 let b = args.get(1).map(|v| v.to_number()).unwrap_or(0.0) as u32 as i32; 3179 Ok(Value::Number(a.wrapping_mul(b) as f64)) 3180} 3181 3182// ── Date built-in ──────────────────────────────────────────── 3183 3184/// Milliseconds since Unix epoch from SystemTime. 3185fn now_ms() -> f64 { 3186 SystemTime::now() 3187 .duration_since(UNIX_EPOCH) 3188 .map(|d| d.as_millis() as f64) 3189 .unwrap_or(0.0) 3190} 3191 3192/// Calendar components from a timestamp (milliseconds since epoch). 3193/// Returns (year, month0, day, hours, minutes, seconds, ms, weekday) in UTC. 3194fn ms_to_utc_components(ms: f64) -> (i64, i64, i64, i64, i64, i64, i64, i64) { 3195 let total_ms = ms as i64; 3196 let ms_part = ((total_ms % 1000) + 1000) % 1000; 3197 let mut days = total_ms.div_euclid(86_400_000); 3198 // Weekday: Jan 1 1970 was Thursday (4). 3199 let weekday = ((days % 7) + 4 + 7) % 7; 3200 3201 let secs_in_day = (total_ms.rem_euclid(86_400_000)) / 1000; 3202 let hours = secs_in_day / 3600; 3203 let minutes = (secs_in_day % 3600) / 60; 3204 let seconds = secs_in_day % 60; 3205 3206 // Convert days since epoch to year/month/day using a civil calendar algorithm. 3207 // Shift epoch to March 1, 2000. 3208 days += 719_468; 3209 let era = if days >= 0 { 3210 days / 146_097 3211 } else { 3212 (days - 146_096) / 146_097 3213 }; 3214 let doe = days - era * 146_097; // day of era [0, 146096] 3215 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365; 3216 let y = yoe + era * 400; 3217 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); 3218 let mp = (5 * doy + 2) / 153; 3219 let d = doy - (153 * mp + 2) / 5 + 1; 3220 let m = if mp < 10 { mp + 3 } else { mp - 9 }; 3221 let y = if m <= 2 { y + 1 } else { y }; 3222 3223 (y, m - 1, d, hours, minutes, seconds, ms_part, weekday) 3224} 3225 3226/// Convert UTC components to milliseconds since epoch. 3227fn utc_components_to_ms(year: i64, month: i64, day: i64, h: i64, min: i64, s: i64, ms: i64) -> f64 { 3228 // Normalize month overflow. 3229 let y = year + month.div_euclid(12); 3230 let m = month.rem_euclid(12) + 1; // 1-based 3231 3232 // Civil calendar to days (inverse of above). 3233 let (y2, m2) = if m <= 2 { (y - 1, m + 9) } else { (y, m - 3) }; 3234 let era = if y2 >= 0 { y2 / 400 } else { (y2 - 399) / 400 }; 3235 let yoe = y2 - era * 400; 3236 let doy = (153 * m2 + 2) / 5 + day - 1; 3237 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; 3238 let days = era * 146_097 + doe - 719_468; 3239 3240 (days * 86_400_000 + h * 3_600_000 + min * 60_000 + s * 1000 + ms) as f64 3241} 3242 3243/// Parse a subset of ISO 8601 date strings (YYYY-MM-DDTHH:MM:SS.sssZ). 3244fn parse_date_string(s: &str) -> Option<f64> { 3245 let s = s.trim(); 3246 // Try YYYY-MM-DDTHH:MM:SS.sssZ or YYYY-MM-DD or YYYY 3247 let parts: Vec<&str> = s.splitn(2, 'T').collect(); 3248 let date_part = parts[0]; 3249 let time_part = parts.get(1).copied().unwrap_or(""); 3250 3251 let date_fields: Vec<&str> = date_part.split('-').collect(); 3252 if date_fields.is_empty() { 3253 return None; 3254 } 3255 3256 let year: i64 = date_fields[0].parse().ok()?; 3257 let month: i64 = date_fields.get(1).and_then(|s| s.parse().ok()).unwrap_or(1); 3258 let day: i64 = date_fields.get(2).and_then(|s| s.parse().ok()).unwrap_or(1); 3259 3260 if !(1..=12).contains(&month) || !(1..=31).contains(&day) { 3261 return None; 3262 } 3263 3264 let (h, min, sec, ms) = if time_part.is_empty() { 3265 (0, 0, 0, 0) 3266 } else { 3267 // Strip trailing Z. 3268 let tp = time_part.strip_suffix('Z').unwrap_or(time_part); 3269 // Split seconds from milliseconds. 3270 let (time_str, ms_val) = if let Some((t, m)) = tp.split_once('.') { 3271 let ms_str = &m[..m.len().min(3)]; 3272 let mut ms_val: i64 = ms_str.parse().unwrap_or(0); 3273 // Pad to 3 digits if needed. 3274 for _ in ms_str.len()..3 { 3275 ms_val *= 10; 3276 } 3277 (t, ms_val) 3278 } else { 3279 (tp, 0i64) 3280 }; 3281 let time_fields: Vec<&str> = time_str.split(':').collect(); 3282 let h: i64 = time_fields 3283 .first() 3284 .and_then(|s| s.parse().ok()) 3285 .unwrap_or(0); 3286 let min: i64 = time_fields.get(1).and_then(|s| s.parse().ok()).unwrap_or(0); 3287 let sec: i64 = time_fields.get(2).and_then(|s| s.parse().ok()).unwrap_or(0); 3288 (h, min, sec, ms_val) 3289 }; 3290 3291 Some(utc_components_to_ms(year, month - 1, day, h, min, sec, ms)) 3292} 3293 3294static DAY_NAMES: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 3295static MONTH_NAMES: [&str; 12] = [ 3296 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 3297]; 3298 3299fn init_date_builtins(vm: &mut Vm) { 3300 // Date.prototype. 3301 let mut date_proto_data = ObjectData::new(); 3302 if let Some(proto) = vm.object_prototype { 3303 date_proto_data.prototype = Some(proto); 3304 } 3305 let date_proto = vm.gc.alloc(HeapObject::Object(date_proto_data)); 3306 init_date_prototype(&mut vm.gc, date_proto); 3307 3308 // Store prototype for constructor access. 3309 vm.date_prototype = Some(date_proto); 3310 DATE_PROTO.with(|cell| cell.set(Some(date_proto))); 3311 3312 // Date constructor function. 3313 let ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 3314 name: "Date".to_string(), 3315 kind: FunctionKind::Native(NativeFunc { 3316 callback: date_constructor, 3317 }), 3318 prototype_obj: Some(date_proto), 3319 properties: HashMap::new(), 3320 upvalues: Vec::new(), 3321 }))); 3322 3323 // Static methods. 3324 let now_fn = make_native(&mut vm.gc, "now", date_now); 3325 set_func_prop(&mut vm.gc, ctor, "now", Value::Function(now_fn)); 3326 3327 let parse_fn = make_native(&mut vm.gc, "parse", date_parse); 3328 set_func_prop(&mut vm.gc, ctor, "parse", Value::Function(parse_fn)); 3329 3330 let utc_fn = make_native(&mut vm.gc, "UTC", date_utc); 3331 set_func_prop(&mut vm.gc, ctor, "UTC", Value::Function(utc_fn)); 3332 3333 vm.set_global("Date", Value::Function(ctor)); 3334} 3335 3336/// Internal: create a Date object (plain object with __date_ms__ property). 3337fn make_date_obj(gc: &mut Gc<HeapObject>, ms: f64, proto: Option<GcRef>) -> Value { 3338 let mut data = ObjectData::new(); 3339 if let Some(p) = proto { 3340 data.prototype = Some(p); 3341 } 3342 data.properties.insert( 3343 "__date_ms__".to_string(), 3344 Property::builtin(Value::Number(ms)), 3345 ); 3346 Value::Object(gc.alloc(HeapObject::Object(data))) 3347} 3348 3349/// Extract timestamp from a Date object. 3350fn date_get_ms(gc: &Gc<HeapObject>, this: &Value) -> f64 { 3351 match this { 3352 Value::Object(r) => match gc.get(*r) { 3353 Some(HeapObject::Object(data)) => data 3354 .properties 3355 .get("__date_ms__") 3356 .map(|p| p.value.to_number()) 3357 .unwrap_or(f64::NAN), 3358 _ => f64::NAN, 3359 }, 3360 _ => f64::NAN, 3361 } 3362} 3363 3364/// Set timestamp on a Date object and return the new value. 3365fn date_set_ms(gc: &mut Gc<HeapObject>, this: &Value, ms: f64) -> f64 { 3366 if let Value::Object(r) = this { 3367 if let Some(HeapObject::Object(data)) = gc.get_mut(*r) { 3368 if let Some(prop) = data.properties.get_mut("__date_ms__") { 3369 prop.value = Value::Number(ms); 3370 } 3371 } 3372 } 3373 ms 3374} 3375 3376fn date_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3377 let proto = DATE_PROTO.with(|cell| cell.get()); 3378 let ms = match args.len() { 3379 0 => now_ms(), 3380 1 => match &args[0] { 3381 Value::String(s) => parse_date_string(s).unwrap_or(f64::NAN), 3382 Value::Number(n) => *n, 3383 v => v.to_number(), 3384 }, 3385 _ => { 3386 // new Date(year, month, day?, hours?, min?, sec?, ms?) 3387 let year = args[0].to_number() as i64; 3388 let month = args[1].to_number() as i64; 3389 let day = args.get(2).map(|v| v.to_number() as i64).unwrap_or(1); 3390 let h = args.get(3).map(|v| v.to_number() as i64).unwrap_or(0); 3391 let min = args.get(4).map(|v| v.to_number() as i64).unwrap_or(0); 3392 let sec = args.get(5).map(|v| v.to_number() as i64).unwrap_or(0); 3393 let ms = args.get(6).map(|v| v.to_number() as i64).unwrap_or(0); 3394 // Years 0-99 map to 1900-1999 per spec. 3395 let year = if (0..100).contains(&year) { 3396 year + 1900 3397 } else { 3398 year 3399 }; 3400 utc_components_to_ms(year, month, day, h, min, sec, ms) 3401 } 3402 }; 3403 Ok(make_date_obj(ctx.gc, ms, proto)) 3404} 3405 3406fn date_now(_args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3407 Ok(Value::Number(now_ms())) 3408} 3409 3410fn date_parse(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3411 let s = args 3412 .first() 3413 .map(|v| v.to_js_string(ctx.gc)) 3414 .unwrap_or_default(); 3415 Ok(Value::Number(parse_date_string(&s).unwrap_or(f64::NAN))) 3416} 3417 3418fn date_utc(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3419 let year = args.first().map(|v| v.to_number() as i64).unwrap_or(1970); 3420 let month = args.get(1).map(|v| v.to_number() as i64).unwrap_or(0); 3421 let day = args.get(2).map(|v| v.to_number() as i64).unwrap_or(1); 3422 let h = args.get(3).map(|v| v.to_number() as i64).unwrap_or(0); 3423 let min = args.get(4).map(|v| v.to_number() as i64).unwrap_or(0); 3424 let sec = args.get(5).map(|v| v.to_number() as i64).unwrap_or(0); 3425 let ms = args.get(6).map(|v| v.to_number() as i64).unwrap_or(0); 3426 let year = if (0..100).contains(&year) { 3427 year + 1900 3428 } else { 3429 year 3430 }; 3431 Ok(Value::Number(utc_components_to_ms( 3432 year, month, day, h, min, sec, ms, 3433 ))) 3434} 3435 3436fn init_date_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 3437 let methods: &[NativeMethod] = &[ 3438 ("getTime", date_get_time), 3439 ("valueOf", date_get_time), // valueOf === getTime 3440 ("getFullYear", date_get_full_year), 3441 ("getMonth", date_get_month), 3442 ("getDate", date_get_date), 3443 ("getDay", date_get_day), 3444 ("getHours", date_get_hours), 3445 ("getMinutes", date_get_minutes), 3446 ("getSeconds", date_get_seconds), 3447 ("getMilliseconds", date_get_milliseconds), 3448 ("getTimezoneOffset", date_get_timezone_offset), 3449 ("getUTCFullYear", date_get_full_year), 3450 ("getUTCMonth", date_get_month), 3451 ("getUTCDate", date_get_date), 3452 ("getUTCDay", date_get_day), 3453 ("getUTCHours", date_get_hours), 3454 ("getUTCMinutes", date_get_minutes), 3455 ("getUTCSeconds", date_get_seconds), 3456 ("getUTCMilliseconds", date_get_milliseconds), 3457 ("setTime", date_set_time), 3458 ("setFullYear", date_set_full_year), 3459 ("setMonth", date_set_month), 3460 ("setDate", date_set_date), 3461 ("setHours", date_set_hours), 3462 ("setMinutes", date_set_minutes), 3463 ("setSeconds", date_set_seconds), 3464 ("setMilliseconds", date_set_milliseconds), 3465 ("toString", date_to_string), 3466 ("toISOString", date_to_iso_string), 3467 ("toUTCString", date_to_utc_string), 3468 ("toLocaleDateString", date_to_locale_date_string), 3469 ("toJSON", date_to_json), 3470 ]; 3471 for &(name, cb) in methods { 3472 let f = make_native(gc, name, cb); 3473 set_builtin_prop(gc, proto, name, Value::Function(f)); 3474 } 3475} 3476 3477fn date_get_time(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3478 let _ = args; 3479 Ok(Value::Number(date_get_ms(ctx.gc, &ctx.this))) 3480} 3481 3482fn date_get_full_year(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3483 let ms = date_get_ms(ctx.gc, &ctx.this); 3484 if ms.is_nan() { 3485 return Ok(Value::Number(f64::NAN)); 3486 } 3487 let (y, ..) = ms_to_utc_components(ms); 3488 Ok(Value::Number(y as f64)) 3489} 3490 3491fn date_get_month(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3492 let ms = date_get_ms(ctx.gc, &ctx.this); 3493 if ms.is_nan() { 3494 return Ok(Value::Number(f64::NAN)); 3495 } 3496 let (_, m, ..) = ms_to_utc_components(ms); 3497 Ok(Value::Number(m as f64)) 3498} 3499 3500fn date_get_date(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3501 let ms = date_get_ms(ctx.gc, &ctx.this); 3502 if ms.is_nan() { 3503 return Ok(Value::Number(f64::NAN)); 3504 } 3505 let (_, _, d, ..) = ms_to_utc_components(ms); 3506 Ok(Value::Number(d as f64)) 3507} 3508 3509fn date_get_day(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3510 let ms = date_get_ms(ctx.gc, &ctx.this); 3511 if ms.is_nan() { 3512 return Ok(Value::Number(f64::NAN)); 3513 } 3514 let (.., wd) = ms_to_utc_components(ms); 3515 Ok(Value::Number(wd as f64)) 3516} 3517 3518fn date_get_hours(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3519 let ms = date_get_ms(ctx.gc, &ctx.this); 3520 if ms.is_nan() { 3521 return Ok(Value::Number(f64::NAN)); 3522 } 3523 let (_, _, _, h, ..) = ms_to_utc_components(ms); 3524 Ok(Value::Number(h as f64)) 3525} 3526 3527fn date_get_minutes(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3528 let ms = date_get_ms(ctx.gc, &ctx.this); 3529 if ms.is_nan() { 3530 return Ok(Value::Number(f64::NAN)); 3531 } 3532 let (_, _, _, _, min, ..) = ms_to_utc_components(ms); 3533 Ok(Value::Number(min as f64)) 3534} 3535 3536fn date_get_seconds(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3537 let ms = date_get_ms(ctx.gc, &ctx.this); 3538 if ms.is_nan() { 3539 return Ok(Value::Number(f64::NAN)); 3540 } 3541 let (_, _, _, _, _, s, ..) = ms_to_utc_components(ms); 3542 Ok(Value::Number(s as f64)) 3543} 3544 3545fn date_get_milliseconds(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3546 let ms = date_get_ms(ctx.gc, &ctx.this); 3547 if ms.is_nan() { 3548 return Ok(Value::Number(f64::NAN)); 3549 } 3550 let (_, _, _, _, _, _, ms_part, _) = ms_to_utc_components(ms); 3551 Ok(Value::Number(ms_part as f64)) 3552} 3553 3554fn date_get_timezone_offset( 3555 _args: &[Value], 3556 _ctx: &mut NativeContext, 3557) -> Result<Value, RuntimeError> { 3558 // We operate in UTC; return 0. 3559 Ok(Value::Number(0.0)) 3560} 3561 3562fn date_set_time(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3563 let ms = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 3564 date_set_ms(ctx.gc, &ctx.this, ms); 3565 Ok(Value::Number(ms)) 3566} 3567 3568fn date_set_full_year(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3569 let old = date_get_ms(ctx.gc, &ctx.this); 3570 let (_, m, d, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3571 let year = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3572 let month = args.get(1).map(|v| v.to_number() as i64).unwrap_or(m); 3573 let day = args.get(2).map(|v| v.to_number() as i64).unwrap_or(d); 3574 let new_ms = utc_components_to_ms(year, month, day, h, min, s, ms); 3575 date_set_ms(ctx.gc, &ctx.this, new_ms); 3576 Ok(Value::Number(new_ms)) 3577} 3578 3579fn date_set_month(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3580 let old = date_get_ms(ctx.gc, &ctx.this); 3581 let (y, _, d, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3582 let month = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3583 let day = args.get(1).map(|v| v.to_number() as i64).unwrap_or(d); 3584 let new_ms = utc_components_to_ms(y, month, day, h, min, s, ms); 3585 date_set_ms(ctx.gc, &ctx.this, new_ms); 3586 Ok(Value::Number(new_ms)) 3587} 3588 3589fn date_set_date(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3590 let old = date_get_ms(ctx.gc, &ctx.this); 3591 let (y, m, _, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3592 let day = args.first().map(|v| v.to_number() as i64).unwrap_or(1); 3593 let new_ms = utc_components_to_ms(y, m, day, h, min, s, ms); 3594 date_set_ms(ctx.gc, &ctx.this, new_ms); 3595 Ok(Value::Number(new_ms)) 3596} 3597 3598fn date_set_hours(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3599 let old = date_get_ms(ctx.gc, &ctx.this); 3600 let (y, m, d, _, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3601 let h = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3602 let min_v = args.get(1).map(|v| v.to_number() as i64).unwrap_or(min); 3603 let sec = args.get(2).map(|v| v.to_number() as i64).unwrap_or(s); 3604 let ms_v = args.get(3).map(|v| v.to_number() as i64).unwrap_or(ms); 3605 let new_ms = utc_components_to_ms(y, m, d, h, min_v, sec, ms_v); 3606 date_set_ms(ctx.gc, &ctx.this, new_ms); 3607 Ok(Value::Number(new_ms)) 3608} 3609 3610fn date_set_minutes(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3611 let old = date_get_ms(ctx.gc, &ctx.this); 3612 let (y, m, d, h, _, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3613 let min = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3614 let sec = args.get(1).map(|v| v.to_number() as i64).unwrap_or(s); 3615 let ms_v = args.get(2).map(|v| v.to_number() as i64).unwrap_or(ms); 3616 let new_ms = utc_components_to_ms(y, m, d, h, min, sec, ms_v); 3617 date_set_ms(ctx.gc, &ctx.this, new_ms); 3618 Ok(Value::Number(new_ms)) 3619} 3620 3621fn date_set_seconds(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3622 let old = date_get_ms(ctx.gc, &ctx.this); 3623 let (y, m, d, h, min, _, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3624 let sec = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3625 let ms_v = args.get(1).map(|v| v.to_number() as i64).unwrap_or(ms); 3626 let new_ms = utc_components_to_ms(y, m, d, h, min, sec, ms_v); 3627 date_set_ms(ctx.gc, &ctx.this, new_ms); 3628 Ok(Value::Number(new_ms)) 3629} 3630 3631fn date_set_milliseconds(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3632 let old = date_get_ms(ctx.gc, &ctx.this); 3633 let (y, m, d, h, min, s, _, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3634 let ms_v = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3635 let new_ms = utc_components_to_ms(y, m, d, h, min, s, ms_v); 3636 date_set_ms(ctx.gc, &ctx.this, new_ms); 3637 Ok(Value::Number(new_ms)) 3638} 3639 3640fn date_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3641 let ms = date_get_ms(ctx.gc, &ctx.this); 3642 if ms.is_nan() { 3643 return Ok(Value::String("Invalid Date".to_string())); 3644 } 3645 let (y, m, d, h, min, s, _, wd) = ms_to_utc_components(ms); 3646 Ok(Value::String(format!( 3647 "{} {} {:02} {} {:02}:{:02}:{:02} GMT", 3648 DAY_NAMES[wd as usize], MONTH_NAMES[m as usize], d, y, h, min, s 3649 ))) 3650} 3651 3652fn date_to_iso_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3653 let ms = date_get_ms(ctx.gc, &ctx.this); 3654 if ms.is_nan() { 3655 return Err(RuntimeError::range_error("Invalid time value")); 3656 } 3657 let (y, m, d, h, min, s, ms_part, _) = ms_to_utc_components(ms); 3658 Ok(Value::String(format!( 3659 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z", 3660 y, 3661 m + 1, 3662 d, 3663 h, 3664 min, 3665 s, 3666 ms_part 3667 ))) 3668} 3669 3670fn date_to_utc_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3671 let ms = date_get_ms(ctx.gc, &ctx.this); 3672 if ms.is_nan() { 3673 return Ok(Value::String("Invalid Date".to_string())); 3674 } 3675 let (y, m, d, h, min, s, _, wd) = ms_to_utc_components(ms); 3676 Ok(Value::String(format!( 3677 "{}, {:02} {} {} {:02}:{:02}:{:02} GMT", 3678 DAY_NAMES[wd as usize], d, MONTH_NAMES[m as usize], y, h, min, s 3679 ))) 3680} 3681 3682fn date_to_locale_date_string( 3683 _args: &[Value], 3684 ctx: &mut NativeContext, 3685) -> Result<Value, RuntimeError> { 3686 // Simplified: same as toISOString date portion. 3687 let ms = date_get_ms(ctx.gc, &ctx.this); 3688 if ms.is_nan() { 3689 return Ok(Value::String("Invalid Date".to_string())); 3690 } 3691 let (y, m, d, ..) = ms_to_utc_components(ms); 3692 Ok(Value::String(format!("{:04}-{:02}-{:02}", y, m + 1, d))) 3693} 3694 3695fn date_to_json(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3696 let ms = date_get_ms(ctx.gc, &ctx.this); 3697 if ms.is_nan() { 3698 return Ok(Value::Null); 3699 } 3700 date_to_iso_string(_args, ctx) 3701} 3702 3703// ── RegExp built-in ────────────────────────────────────────── 3704 3705thread_local! { 3706 static REGEXP_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 3707} 3708 3709fn init_regexp_builtins(vm: &mut Vm) { 3710 // RegExp.prototype. 3711 let mut regexp_proto_data = ObjectData::new(); 3712 if let Some(proto) = vm.object_prototype { 3713 regexp_proto_data.prototype = Some(proto); 3714 } 3715 let regexp_proto = vm.gc.alloc(HeapObject::Object(regexp_proto_data)); 3716 init_regexp_prototype(&mut vm.gc, regexp_proto); 3717 3718 vm.regexp_prototype = Some(regexp_proto); 3719 REGEXP_PROTO.with(|cell| cell.set(Some(regexp_proto))); 3720 3721 // RegExp constructor function. 3722 let ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 3723 name: "RegExp".to_string(), 3724 kind: FunctionKind::Native(NativeFunc { 3725 callback: regexp_constructor, 3726 }), 3727 prototype_obj: Some(regexp_proto), 3728 properties: HashMap::new(), 3729 upvalues: Vec::new(), 3730 }))); 3731 3732 vm.set_global("RegExp", Value::Function(ctor)); 3733} 3734 3735fn init_regexp_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 3736 let methods: &[NativeMethod] = &[ 3737 ("test", regexp_proto_test), 3738 ("exec", regexp_proto_exec), 3739 ("toString", regexp_proto_to_string), 3740 ]; 3741 for &(name, callback) in methods { 3742 let f = make_native(gc, name, callback); 3743 set_builtin_prop(gc, proto, name, Value::Function(f)); 3744 } 3745} 3746 3747/// Create a RegExp object storing compiled regex state in hidden properties. 3748pub fn make_regexp_obj( 3749 gc: &mut Gc<HeapObject>, 3750 pattern: &str, 3751 flags_str: &str, 3752 proto: Option<GcRef>, 3753) -> Result<Value, String> { 3754 use crate::regex::CompiledRegex; 3755 3756 let compiled = CompiledRegex::new(pattern, flags_str)?; 3757 let flags = compiled.flags; 3758 3759 let mut data = ObjectData::new(); 3760 if let Some(p) = proto { 3761 data.prototype = Some(p); 3762 } 3763 // Store pattern and flags as properties. 3764 data.properties.insert( 3765 "source".to_string(), 3766 Property::builtin(Value::String(pattern.to_string())), 3767 ); 3768 let flags_string = flags.as_flag_string(); 3769 data.properties.insert( 3770 "flags".to_string(), 3771 Property::builtin(Value::String(flags_string)), 3772 ); 3773 data.properties.insert( 3774 "global".to_string(), 3775 Property::builtin(Value::Boolean(flags.global)), 3776 ); 3777 data.properties.insert( 3778 "ignoreCase".to_string(), 3779 Property::builtin(Value::Boolean(flags.ignore_case)), 3780 ); 3781 data.properties.insert( 3782 "multiline".to_string(), 3783 Property::builtin(Value::Boolean(flags.multiline)), 3784 ); 3785 data.properties.insert( 3786 "dotAll".to_string(), 3787 Property::builtin(Value::Boolean(flags.dot_all)), 3788 ); 3789 data.properties.insert( 3790 "unicode".to_string(), 3791 Property::builtin(Value::Boolean(flags.unicode)), 3792 ); 3793 data.properties.insert( 3794 "sticky".to_string(), 3795 Property::builtin(Value::Boolean(flags.sticky)), 3796 ); 3797 data.properties 3798 .insert("lastIndex".to_string(), Property::data(Value::Number(0.0))); 3799 // Hidden: serialized pattern for re-compilation. 3800 data.properties.insert( 3801 "__regexp_pattern__".to_string(), 3802 Property::builtin(Value::String(pattern.to_string())), 3803 ); 3804 data.properties.insert( 3805 "__regexp_flags__".to_string(), 3806 Property::builtin(Value::String(flags.as_flag_string())), 3807 ); 3808 3809 Ok(Value::Object(gc.alloc(HeapObject::Object(data)))) 3810} 3811 3812/// Check if a Value is a RegExp object. 3813pub fn is_regexp(gc: &Gc<HeapObject>, val: &Value) -> bool { 3814 match val { 3815 Value::Object(r) => match gc.get(*r) { 3816 Some(HeapObject::Object(data)) => data.properties.contains_key("__regexp_pattern__"), 3817 _ => false, 3818 }, 3819 _ => false, 3820 } 3821} 3822 3823/// Extract the pattern from a RegExp object. 3824fn regexp_get_pattern(gc: &Gc<HeapObject>, val: &Value) -> Option<String> { 3825 match val { 3826 Value::Object(r) => match gc.get(*r) { 3827 Some(HeapObject::Object(data)) => { 3828 data.properties 3829 .get("__regexp_pattern__") 3830 .and_then(|p| match &p.value { 3831 Value::String(s) => Some(s.clone()), 3832 _ => None, 3833 }) 3834 } 3835 _ => None, 3836 }, 3837 _ => None, 3838 } 3839} 3840 3841/// Extract the flags string from a RegExp object. 3842fn regexp_get_flags(gc: &Gc<HeapObject>, val: &Value) -> Option<String> { 3843 match val { 3844 Value::Object(r) => match gc.get(*r) { 3845 Some(HeapObject::Object(data)) => { 3846 data.properties 3847 .get("__regexp_flags__") 3848 .and_then(|p| match &p.value { 3849 Value::String(s) => Some(s.clone()), 3850 _ => None, 3851 }) 3852 } 3853 _ => None, 3854 }, 3855 _ => None, 3856 } 3857} 3858 3859/// Get lastIndex from a RegExp object. 3860fn regexp_get_last_index(gc: &Gc<HeapObject>, val: &Value) -> f64 { 3861 match val { 3862 Value::Object(r) => match gc.get(*r) { 3863 Some(HeapObject::Object(data)) => data 3864 .properties 3865 .get("lastIndex") 3866 .map(|p| p.value.to_number()) 3867 .unwrap_or(0.0), 3868 _ => 0.0, 3869 }, 3870 _ => 0.0, 3871 } 3872} 3873 3874/// Set lastIndex on a RegExp object. 3875fn regexp_set_last_index(gc: &mut Gc<HeapObject>, val: &Value, idx: f64) { 3876 if let Value::Object(r) = val { 3877 if let Some(HeapObject::Object(data)) = gc.get_mut(*r) { 3878 if let Some(prop) = data.properties.get_mut("lastIndex") { 3879 prop.value = Value::Number(idx); 3880 } 3881 } 3882 } 3883} 3884 3885/// Execute the regex on a string and return a match result array or null. 3886fn regexp_exec_internal( 3887 gc: &mut Gc<HeapObject>, 3888 this: &Value, 3889 input: &str, 3890) -> Result<Value, RuntimeError> { 3891 use crate::regex::{exec, CompiledRegex}; 3892 3893 let pattern = regexp_get_pattern(gc, this) 3894 .ok_or_else(|| RuntimeError::type_error("not a RegExp".to_string()))?; 3895 let flags_str = regexp_get_flags(gc, this).unwrap_or_default(); 3896 let compiled = CompiledRegex::new(&pattern, &flags_str).map_err(RuntimeError::syntax_error)?; 3897 let is_global = compiled.flags.global; 3898 let is_sticky = compiled.flags.sticky; 3899 3900 let start_index = if is_global || is_sticky { 3901 let li = regexp_get_last_index(gc, this); 3902 if li < 0.0 { 3903 0 3904 } else { 3905 li as usize 3906 } 3907 } else { 3908 0 3909 }; 3910 3911 let chars: Vec<char> = input.chars().collect(); 3912 let result = exec(&compiled, input, start_index); 3913 3914 match result { 3915 Some(m) => { 3916 if is_global || is_sticky { 3917 regexp_set_last_index(gc, this, m.end as f64); 3918 } 3919 3920 // Build result array: [fullMatch, ...groups] 3921 let mut items: Vec<Value> = Vec::new(); 3922 3923 // Full match (index 0). 3924 let full: String = chars[m.start..m.end].iter().collect(); 3925 items.push(Value::String(full)); 3926 3927 // Capture groups (index 1..n). 3928 for i in 1..m.captures.len() { 3929 match m.captures[i] { 3930 Some((s, e)) => { 3931 let cap: String = chars[s..e].iter().collect(); 3932 items.push(Value::String(cap)); 3933 } 3934 None => items.push(Value::Undefined), 3935 } 3936 } 3937 3938 // Build named groups object (if any) before creating the array. 3939 let groups_val = { 3940 let (node, _) = 3941 crate::regex::parse_pattern(&pattern).map_err(RuntimeError::syntax_error)?; 3942 let named = collect_named_groups(&node); 3943 if named.is_empty() { 3944 Value::Undefined 3945 } else { 3946 let mut groups_data = ObjectData::new(); 3947 for (name, idx) in &named { 3948 let cap_idx = *idx as usize; 3949 let val = if cap_idx < m.captures.len() { 3950 match m.captures[cap_idx] { 3951 Some((s, e)) => { 3952 let cap: String = chars[s..e].iter().collect(); 3953 Value::String(cap) 3954 } 3955 None => Value::Undefined, 3956 } 3957 } else { 3958 Value::Undefined 3959 }; 3960 groups_data 3961 .properties 3962 .insert(name.clone(), Property::data(val)); 3963 } 3964 Value::Object(gc.alloc(HeapObject::Object(groups_data))) 3965 } 3966 }; 3967 3968 let arr = make_value_array(gc, &items); 3969 // Set index, input, and groups properties on the result array. 3970 if let Value::Object(r) = arr { 3971 if let Some(HeapObject::Object(data)) = gc.get_mut(r) { 3972 data.properties.insert( 3973 "index".to_string(), 3974 Property::data(Value::Number(m.start as f64)), 3975 ); 3976 data.properties.insert( 3977 "input".to_string(), 3978 Property::data(Value::String(input.to_string())), 3979 ); 3980 data.properties 3981 .insert("groups".to_string(), Property::data(groups_val)); 3982 } 3983 Ok(Value::Object(r)) 3984 } else { 3985 Ok(arr) 3986 } 3987 } 3988 None => { 3989 if is_global || is_sticky { 3990 regexp_set_last_index(gc, this, 0.0); 3991 } 3992 Ok(Value::Null) 3993 } 3994 } 3995} 3996 3997/// Collect named groups from a regex AST node. 3998fn collect_named_groups(node: &crate::regex::Node) -> Vec<(String, u32)> { 3999 use crate::regex::Node; 4000 let mut result = Vec::new(); 4001 match node { 4002 Node::Group { 4003 index, 4004 name: Some(name), 4005 node: inner, 4006 } => { 4007 result.push((name.clone(), *index)); 4008 result.extend(collect_named_groups(inner)); 4009 } 4010 Node::Group { node: inner, .. } 4011 | Node::NonCapturingGroup(inner) 4012 | Node::Lookahead(inner) 4013 | Node::NegativeLookahead(inner) => { 4014 result.extend(collect_named_groups(inner)); 4015 } 4016 Node::Quantifier { node: inner, .. } => { 4017 result.extend(collect_named_groups(inner)); 4018 } 4019 Node::Sequence(nodes) | Node::Alternation(nodes) => { 4020 for n in nodes { 4021 result.extend(collect_named_groups(n)); 4022 } 4023 } 4024 _ => {} 4025 } 4026 result 4027} 4028 4029fn regexp_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4030 let proto = REGEXP_PROTO.with(|cell| cell.get()); 4031 let pattern = args 4032 .first() 4033 .map(|v| v.to_js_string(ctx.gc)) 4034 .unwrap_or_default(); 4035 let flags = args 4036 .get(1) 4037 .map(|v| { 4038 if matches!(v, Value::Undefined) { 4039 String::new() 4040 } else { 4041 v.to_js_string(ctx.gc) 4042 } 4043 }) 4044 .unwrap_or_default(); 4045 4046 make_regexp_obj(ctx.gc, &pattern, &flags, proto).map_err(RuntimeError::syntax_error) 4047} 4048 4049fn regexp_proto_test(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4050 let input = args 4051 .first() 4052 .map(|v| v.to_js_string(ctx.gc)) 4053 .unwrap_or_default(); 4054 let result = regexp_exec_internal(ctx.gc, &ctx.this, &input)?; 4055 Ok(Value::Boolean(!matches!(result, Value::Null))) 4056} 4057 4058fn regexp_proto_exec(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4059 let input = args 4060 .first() 4061 .map(|v| v.to_js_string(ctx.gc)) 4062 .unwrap_or_default(); 4063 regexp_exec_internal(ctx.gc, &ctx.this, &input) 4064} 4065 4066fn regexp_proto_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4067 let pattern = regexp_get_pattern(ctx.gc, &ctx.this).unwrap_or_default(); 4068 let flags = regexp_get_flags(ctx.gc, &ctx.this).unwrap_or_default(); 4069 Ok(Value::String(format!("/{}/{}", pattern, flags))) 4070} 4071 4072// ── Map built-in ───────────────────────────────────────────── 4073 4074thread_local! { 4075 static MAP_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 4076 static SET_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 4077 static WEAKMAP_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 4078 static WEAKSET_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 4079} 4080 4081fn init_map_set_builtins(vm: &mut Vm) { 4082 // ── Map ── 4083 let mut map_proto_data = ObjectData::new(); 4084 if let Some(proto) = vm.object_prototype { 4085 map_proto_data.prototype = Some(proto); 4086 } 4087 let map_proto = vm.gc.alloc(HeapObject::Object(map_proto_data)); 4088 init_map_prototype(&mut vm.gc, map_proto); 4089 MAP_PROTO.with(|cell| cell.set(Some(map_proto))); 4090 4091 let map_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 4092 name: "Map".to_string(), 4093 kind: FunctionKind::Native(NativeFunc { 4094 callback: map_constructor, 4095 }), 4096 prototype_obj: Some(map_proto), 4097 properties: HashMap::new(), 4098 upvalues: Vec::new(), 4099 }))); 4100 vm.set_global("Map", Value::Function(map_ctor)); 4101 4102 // ── Set ── 4103 let mut set_proto_data = ObjectData::new(); 4104 if let Some(proto) = vm.object_prototype { 4105 set_proto_data.prototype = Some(proto); 4106 } 4107 let set_proto = vm.gc.alloc(HeapObject::Object(set_proto_data)); 4108 init_set_prototype(&mut vm.gc, set_proto); 4109 SET_PROTO.with(|cell| cell.set(Some(set_proto))); 4110 4111 let set_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 4112 name: "Set".to_string(), 4113 kind: FunctionKind::Native(NativeFunc { 4114 callback: set_constructor, 4115 }), 4116 prototype_obj: Some(set_proto), 4117 properties: HashMap::new(), 4118 upvalues: Vec::new(), 4119 }))); 4120 vm.set_global("Set", Value::Function(set_ctor)); 4121 4122 // ── WeakMap ── 4123 let mut wm_proto_data = ObjectData::new(); 4124 if let Some(proto) = vm.object_prototype { 4125 wm_proto_data.prototype = Some(proto); 4126 } 4127 let wm_proto = vm.gc.alloc(HeapObject::Object(wm_proto_data)); 4128 init_weakmap_prototype(&mut vm.gc, wm_proto); 4129 WEAKMAP_PROTO.with(|cell| cell.set(Some(wm_proto))); 4130 4131 let wm_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 4132 name: "WeakMap".to_string(), 4133 kind: FunctionKind::Native(NativeFunc { 4134 callback: weakmap_constructor, 4135 }), 4136 prototype_obj: Some(wm_proto), 4137 properties: HashMap::new(), 4138 upvalues: Vec::new(), 4139 }))); 4140 vm.set_global("WeakMap", Value::Function(wm_ctor)); 4141 4142 // ── WeakSet ── 4143 let mut ws_proto_data = ObjectData::new(); 4144 if let Some(proto) = vm.object_prototype { 4145 ws_proto_data.prototype = Some(proto); 4146 } 4147 let ws_proto = vm.gc.alloc(HeapObject::Object(ws_proto_data)); 4148 init_weakset_prototype(&mut vm.gc, ws_proto); 4149 WEAKSET_PROTO.with(|cell| cell.set(Some(ws_proto))); 4150 4151 let ws_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 4152 name: "WeakSet".to_string(), 4153 kind: FunctionKind::Native(NativeFunc { 4154 callback: weakset_constructor, 4155 }), 4156 prototype_obj: Some(ws_proto), 4157 properties: HashMap::new(), 4158 upvalues: Vec::new(), 4159 }))); 4160 vm.set_global("WeakSet", Value::Function(ws_ctor)); 4161} 4162 4163// ── Map / Set internal helpers ─────────────────────────────── 4164 4165/// Internal storage key for Map/Set entries. 4166/// We store entries as a hidden object with indexed key/value pairs: 4167/// __entries__: GcRef to an object with "0_k", "0_v", "1_k", "1_v", ... 4168/// __entry_count__: total slots allocated (some may be deleted) 4169/// __live_count__: number of non-deleted entries 4170/// Deleted entries have their key set to a special "__deleted__" marker. 4171const ENTRIES_KEY: &str = "__entries__"; 4172const ENTRY_COUNT_KEY: &str = "__entry_count__"; 4173const LIVE_COUNT_KEY: &str = "__live_count__"; 4174const DELETED_MARKER: &str = "__deleted__"; 4175 4176/// Create a new empty Map/Set internal storage object. 4177fn make_collection_obj(gc: &mut Gc<HeapObject>, proto: Option<GcRef>) -> GcRef { 4178 let entries_obj = gc.alloc(HeapObject::Object(ObjectData::new())); 4179 let mut data = ObjectData::new(); 4180 if let Some(p) = proto { 4181 data.prototype = Some(p); 4182 } 4183 data.properties.insert( 4184 ENTRIES_KEY.to_string(), 4185 Property::builtin(Value::Object(entries_obj)), 4186 ); 4187 data.properties.insert( 4188 ENTRY_COUNT_KEY.to_string(), 4189 Property::builtin(Value::Number(0.0)), 4190 ); 4191 data.properties.insert( 4192 LIVE_COUNT_KEY.to_string(), 4193 Property::builtin(Value::Number(0.0)), 4194 ); 4195 // size is a read-only, non-enumerable property. 4196 data.properties 4197 .insert("size".to_string(), Property::builtin(Value::Number(0.0))); 4198 gc.alloc(HeapObject::Object(data)) 4199} 4200 4201/// Get the entries object GcRef from a Map/Set object. 4202fn collection_entries(gc: &Gc<HeapObject>, obj: &Value) -> Option<GcRef> { 4203 let gc_ref = obj.gc_ref()?; 4204 let heap = gc.get(gc_ref)?; 4205 if let HeapObject::Object(data) = heap { 4206 if let Some(prop) = data.properties.get(ENTRIES_KEY) { 4207 return prop.value.gc_ref(); 4208 } 4209 } 4210 None 4211} 4212 4213/// Get the entry count from a Map/Set object. 4214fn collection_entry_count(gc: &Gc<HeapObject>, obj: &Value) -> usize { 4215 let gc_ref = match obj.gc_ref() { 4216 Some(r) => r, 4217 None => return 0, 4218 }; 4219 match gc.get(gc_ref) { 4220 Some(HeapObject::Object(data)) => data 4221 .properties 4222 .get(ENTRY_COUNT_KEY) 4223 .map(|p| p.value.to_number() as usize) 4224 .unwrap_or(0), 4225 _ => 0, 4226 } 4227} 4228 4229/// Get the live count from a Map/Set object. 4230fn collection_live_count(gc: &Gc<HeapObject>, obj: &Value) -> usize { 4231 let gc_ref = match obj.gc_ref() { 4232 Some(r) => r, 4233 None => return 0, 4234 }; 4235 match gc.get(gc_ref) { 4236 Some(HeapObject::Object(data)) => data 4237 .properties 4238 .get(LIVE_COUNT_KEY) 4239 .map(|p| p.value.to_number() as usize) 4240 .unwrap_or(0), 4241 _ => 0, 4242 } 4243} 4244 4245/// Set the entry count on a Map/Set object and update the `size` property. 4246fn set_collection_count(gc: &mut Gc<HeapObject>, obj: &Value, entry_count: usize, live: usize) { 4247 let gc_ref = match obj.gc_ref() { 4248 Some(r) => r, 4249 None => return, 4250 }; 4251 if let Some(HeapObject::Object(data)) = gc.get_mut(gc_ref) { 4252 data.properties.insert( 4253 ENTRY_COUNT_KEY.to_string(), 4254 Property::builtin(Value::Number(entry_count as f64)), 4255 ); 4256 data.properties.insert( 4257 LIVE_COUNT_KEY.to_string(), 4258 Property::builtin(Value::Number(live as f64)), 4259 ); 4260 data.properties.insert( 4261 "size".to_string(), 4262 Property::builtin(Value::Number(live as f64)), 4263 ); 4264 } 4265} 4266 4267/// Get the key at index `i` in the entries object (returns None if deleted or missing). 4268fn entry_key_at(gc: &Gc<HeapObject>, entries: GcRef, i: usize) -> Option<Value> { 4269 match gc.get(entries) { 4270 Some(HeapObject::Object(data)) => { 4271 let k = format!("{i}_k"); 4272 let prop = data.properties.get(&k)?; 4273 if let Value::String(s) = &prop.value { 4274 if s == DELETED_MARKER { 4275 return None; 4276 } 4277 } 4278 Some(prop.value.clone()) 4279 } 4280 _ => None, 4281 } 4282} 4283 4284/// Get the value at index `i` in the entries object. 4285fn entry_value_at(gc: &Gc<HeapObject>, entries: GcRef, i: usize) -> Value { 4286 match gc.get(entries) { 4287 Some(HeapObject::Object(data)) => { 4288 let v = format!("{i}_v"); 4289 data.properties 4290 .get(&v) 4291 .map(|p| p.value.clone()) 4292 .unwrap_or(Value::Undefined) 4293 } 4294 _ => Value::Undefined, 4295 } 4296} 4297 4298/// Set an entry at index `i`. 4299fn set_entry_at(gc: &mut Gc<HeapObject>, entries: GcRef, i: usize, key: Value, value: Value) { 4300 if let Some(HeapObject::Object(data)) = gc.get_mut(entries) { 4301 data.properties 4302 .insert(format!("{i}_k"), Property::builtin(key)); 4303 data.properties 4304 .insert(format!("{i}_v"), Property::builtin(value)); 4305 } 4306} 4307 4308/// Mark entry at index `i` as deleted. 4309fn delete_entry_at(gc: &mut Gc<HeapObject>, entries: GcRef, i: usize) { 4310 if let Some(HeapObject::Object(data)) = gc.get_mut(entries) { 4311 data.properties.insert( 4312 format!("{i}_k"), 4313 Property::builtin(Value::String(DELETED_MARKER.to_string())), 4314 ); 4315 data.properties.remove(&format!("{i}_v")); 4316 } 4317} 4318 4319/// Find the index of a key in the entries, using SameValueZero. 4320fn find_key_index(gc: &Gc<HeapObject>, entries: GcRef, count: usize, key: &Value) -> Option<usize> { 4321 for i in 0..count { 4322 if let Some(existing) = entry_key_at(gc, entries, i) { 4323 if same_value_zero(&existing, key) { 4324 return Some(i); 4325 } 4326 } 4327 } 4328 None 4329} 4330 4331// ── Map constructor & prototype ────────────────────────────── 4332 4333fn map_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4334 let proto = MAP_PROTO.with(|cell| cell.get()); 4335 let obj_ref = make_collection_obj(ctx.gc, proto); 4336 let obj = Value::Object(obj_ref); 4337 4338 // If an iterable argument is provided, add entries from it. 4339 // We support arrays of [key, value] pairs. 4340 if let Some(arg) = args.first() { 4341 if !matches!(arg, Value::Undefined | Value::Null) { 4342 if let Some(arr_ref) = arg.gc_ref() { 4343 let len = array_length(ctx.gc, arr_ref); 4344 for i in 0..len { 4345 let pair = get_property(ctx.gc, &Value::Object(arr_ref), &i.to_string()); 4346 if let Some(pair_ref) = pair.gc_ref() { 4347 let k = get_property(ctx.gc, &Value::Object(pair_ref), "0"); 4348 let v = get_property(ctx.gc, &Value::Object(pair_ref), "1"); 4349 map_set_internal(ctx.gc, &obj, k, v); 4350 } 4351 } 4352 } 4353 } 4354 } 4355 4356 Ok(obj) 4357} 4358 4359fn map_set_internal(gc: &mut Gc<HeapObject>, map: &Value, key: Value, value: Value) { 4360 let entries = match collection_entries(gc, map) { 4361 Some(e) => e, 4362 None => return, 4363 }; 4364 let count = collection_entry_count(gc, map); 4365 let live = collection_live_count(gc, map); 4366 4367 // Check if key already exists. 4368 if let Some(idx) = find_key_index(gc, entries, count, &key) { 4369 set_entry_at(gc, entries, idx, key, value); 4370 return; 4371 } 4372 4373 // Add new entry. 4374 set_entry_at(gc, entries, count, key, value); 4375 set_collection_count(gc, map, count + 1, live + 1); 4376} 4377 4378fn init_map_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 4379 let methods: &[NativeMethod] = &[ 4380 ("set", map_proto_set), 4381 ("get", map_proto_get), 4382 ("has", map_proto_has), 4383 ("delete", map_proto_delete), 4384 ("clear", map_proto_clear), 4385 ("forEach", map_proto_for_each), 4386 ("keys", map_proto_keys), 4387 ("values", map_proto_values), 4388 ("entries", map_proto_entries), 4389 ]; 4390 for &(name, callback) in methods { 4391 let f = make_native(gc, name, callback); 4392 set_builtin_prop(gc, proto, name, Value::Function(f)); 4393 } 4394 // Map.prototype[@@iterator] — returns an iterator of [key, value] pairs. 4395 let iter_fn = make_native(gc, "[Symbol.iterator]", map_symbol_iterator); 4396 set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter_fn)); 4397} 4398 4399fn map_proto_set(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4400 let key = args.first().cloned().unwrap_or(Value::Undefined); 4401 let value = args.get(1).cloned().unwrap_or(Value::Undefined); 4402 // Normalize -0 to +0 for key. 4403 let key = normalize_zero(key); 4404 map_set_internal(ctx.gc, &ctx.this, key, value); 4405 Ok(ctx.this.clone()) 4406} 4407 4408fn map_proto_get(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4409 let key = args.first().cloned().unwrap_or(Value::Undefined); 4410 let key = normalize_zero(key); 4411 let entries = match collection_entries(ctx.gc, &ctx.this) { 4412 Some(e) => e, 4413 None => return Ok(Value::Undefined), 4414 }; 4415 let count = collection_entry_count(ctx.gc, &ctx.this); 4416 if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4417 return Ok(entry_value_at(ctx.gc, entries, idx)); 4418 } 4419 Ok(Value::Undefined) 4420} 4421 4422fn map_proto_has(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4423 let key = args.first().cloned().unwrap_or(Value::Undefined); 4424 let key = normalize_zero(key); 4425 let entries = match collection_entries(ctx.gc, &ctx.this) { 4426 Some(e) => e, 4427 None => return Ok(Value::Boolean(false)), 4428 }; 4429 let count = collection_entry_count(ctx.gc, &ctx.this); 4430 Ok(Value::Boolean( 4431 find_key_index(ctx.gc, entries, count, &key).is_some(), 4432 )) 4433} 4434 4435fn map_proto_delete(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4436 let key = args.first().cloned().unwrap_or(Value::Undefined); 4437 let key = normalize_zero(key); 4438 let entries = match collection_entries(ctx.gc, &ctx.this) { 4439 Some(e) => e, 4440 None => return Ok(Value::Boolean(false)), 4441 }; 4442 let count = collection_entry_count(ctx.gc, &ctx.this); 4443 let live = collection_live_count(ctx.gc, &ctx.this); 4444 if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4445 delete_entry_at(ctx.gc, entries, idx); 4446 set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 4447 return Ok(Value::Boolean(true)); 4448 } 4449 Ok(Value::Boolean(false)) 4450} 4451 4452fn map_proto_clear(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4453 clear_collection(ctx.gc, &ctx.this); 4454 Ok(Value::Undefined) 4455} 4456 4457/// Clear all entries from a Map/Set collection. 4458fn clear_collection(gc: &mut Gc<HeapObject>, obj: &Value) { 4459 let new_entries = gc.alloc(HeapObject::Object(ObjectData::new())); 4460 if let Some(gc_ref) = obj.gc_ref() { 4461 if let Some(HeapObject::Object(data)) = gc.get_mut(gc_ref) { 4462 data.properties.insert( 4463 ENTRIES_KEY.to_string(), 4464 Property::builtin(Value::Object(new_entries)), 4465 ); 4466 data.properties.insert( 4467 ENTRY_COUNT_KEY.to_string(), 4468 Property::builtin(Value::Number(0.0)), 4469 ); 4470 data.properties.insert( 4471 LIVE_COUNT_KEY.to_string(), 4472 Property::builtin(Value::Number(0.0)), 4473 ); 4474 data.properties 4475 .insert("size".to_string(), Property::builtin(Value::Number(0.0))); 4476 } 4477 } 4478} 4479 4480fn map_proto_for_each(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4481 let callback = match args.first() { 4482 Some(Value::Function(r)) => *r, 4483 _ => { 4484 return Err(RuntimeError::type_error( 4485 "Map.prototype.forEach requires a function argument", 4486 )) 4487 } 4488 }; 4489 let entries = match collection_entries(ctx.gc, &ctx.this) { 4490 Some(e) => e, 4491 None => return Ok(Value::Undefined), 4492 }; 4493 let count = collection_entry_count(ctx.gc, &ctx.this); 4494 // Collect entries first to avoid borrow issues. 4495 let mut pairs = Vec::new(); 4496 for i in 0..count { 4497 if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4498 let v = entry_value_at(ctx.gc, entries, i); 4499 pairs.push((k, v)); 4500 } 4501 } 4502 let this_val = ctx.this.clone(); 4503 for (k, v) in pairs { 4504 call_native_callback( 4505 ctx.gc, 4506 callback, 4507 &[v, k, this_val.clone()], 4508 ctx.console_output, 4509 ctx.dom_bridge, 4510 )?; 4511 } 4512 Ok(Value::Undefined) 4513} 4514 4515fn map_proto_keys(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4516 map_proto_iter(args, ctx, IterKind::Keys) 4517} 4518 4519fn map_proto_values(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4520 map_proto_iter(args, ctx, IterKind::Values) 4521} 4522 4523fn map_proto_entries(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4524 map_proto_iter(args, ctx, IterKind::Entries) 4525} 4526 4527enum IterKind { 4528 Keys, 4529 Values, 4530 Entries, 4531} 4532 4533fn map_proto_iter( 4534 _args: &[Value], 4535 ctx: &mut NativeContext, 4536 kind: IterKind, 4537) -> Result<Value, RuntimeError> { 4538 let entries = match collection_entries(ctx.gc, &ctx.this) { 4539 Some(e) => e, 4540 None => return Ok(make_value_array(ctx.gc, &[])), 4541 }; 4542 let count = collection_entry_count(ctx.gc, &ctx.this); 4543 let mut items = Vec::new(); 4544 for i in 0..count { 4545 if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4546 match kind { 4547 IterKind::Keys => items.push(k), 4548 IterKind::Values => { 4549 let v = entry_value_at(ctx.gc, entries, i); 4550 items.push(v); 4551 } 4552 IterKind::Entries => { 4553 let v = entry_value_at(ctx.gc, entries, i); 4554 let pair = make_value_array(ctx.gc, &[k, v]); 4555 items.push(pair); 4556 } 4557 } 4558 } 4559 } 4560 Ok(make_value_array(ctx.gc, &items)) 4561} 4562 4563/// Map[@@iterator]() — wraps entries array into an iterator. 4564fn map_symbol_iterator(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4565 let arr_val = map_proto_iter(_args, ctx, IterKind::Entries)?; 4566 let arr_ref = match arr_val.gc_ref() { 4567 Some(r) => r, 4568 None => return Ok(Value::Undefined), 4569 }; 4570 Ok(make_simple_iterator(ctx.gc, arr_ref, array_values_next)) 4571} 4572 4573/// Set[@@iterator]() — wraps values array into an iterator. 4574fn set_symbol_iterator(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4575 let arr_val = set_proto_values(_args, ctx)?; 4576 let arr_ref = match arr_val.gc_ref() { 4577 Some(r) => r, 4578 None => return Ok(Value::Undefined), 4579 }; 4580 Ok(make_simple_iterator(ctx.gc, arr_ref, array_values_next)) 4581} 4582 4583/// Normalize -0 to +0 for Map/Set key equality. 4584fn normalize_zero(val: Value) -> Value { 4585 if let Value::Number(n) = &val { 4586 if *n == 0.0 { 4587 return Value::Number(0.0); 4588 } 4589 } 4590 val 4591} 4592 4593/// Helper to get a property from an object value by key (used for reading iterable pairs). 4594fn get_property(gc: &Gc<HeapObject>, obj: &Value, key: &str) -> Value { 4595 let gc_ref = match obj.gc_ref() { 4596 Some(r) => r, 4597 None => return Value::Undefined, 4598 }; 4599 match gc.get(gc_ref) { 4600 Some(HeapObject::Object(data)) => data 4601 .properties 4602 .get(key) 4603 .map(|p| p.value.clone()) 4604 .unwrap_or(Value::Undefined), 4605 _ => Value::Undefined, 4606 } 4607} 4608 4609/// Call a native callback function (for forEach). 4610fn call_native_callback( 4611 gc: &mut Gc<HeapObject>, 4612 func_ref: GcRef, 4613 args: &[Value], 4614 console_output: &dyn ConsoleOutput, 4615 dom_bridge: Option<&DomBridge>, 4616) -> Result<Value, RuntimeError> { 4617 match gc.get(func_ref) { 4618 Some(HeapObject::Function(fdata)) => match &fdata.kind { 4619 FunctionKind::Native(native) => { 4620 let cb = native.callback; 4621 let mut ctx = NativeContext { 4622 gc, 4623 this: Value::Undefined, 4624 console_output, 4625 dom_bridge, 4626 }; 4627 cb(args, &mut ctx) 4628 } 4629 FunctionKind::Bytecode(_) => Err(RuntimeError::type_error( 4630 "bytecode callbacks in forEach not yet supported", 4631 )), 4632 }, 4633 _ => Err(RuntimeError::type_error("not a function")), 4634 } 4635} 4636 4637// ── Set constructor & prototype ────────────────────────────── 4638 4639fn set_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4640 let proto = SET_PROTO.with(|cell| cell.get()); 4641 let obj_ref = make_collection_obj(ctx.gc, proto); 4642 let obj = Value::Object(obj_ref); 4643 4644 // If an iterable argument is provided, add values from it. 4645 if let Some(arg) = args.first() { 4646 if !matches!(arg, Value::Undefined | Value::Null) { 4647 if let Some(arr_ref) = arg.gc_ref() { 4648 let len = array_length(ctx.gc, arr_ref); 4649 for i in 0..len { 4650 let v = get_property(ctx.gc, &Value::Object(arr_ref), &i.to_string()); 4651 set_add_internal(ctx.gc, &obj, v); 4652 } 4653 } 4654 } 4655 } 4656 4657 Ok(obj) 4658} 4659 4660fn set_add_internal(gc: &mut Gc<HeapObject>, set: &Value, value: Value) { 4661 let entries = match collection_entries(gc, set) { 4662 Some(e) => e, 4663 None => return, 4664 }; 4665 let count = collection_entry_count(gc, set); 4666 let live = collection_live_count(gc, set); 4667 4668 // Check if value already exists. 4669 if find_key_index(gc, entries, count, &value).is_some() { 4670 return; 4671 } 4672 4673 // Add new entry (value stored as key, value slot unused). 4674 set_entry_at(gc, entries, count, value, Value::Undefined); 4675 set_collection_count(gc, set, count + 1, live + 1); 4676} 4677 4678fn init_set_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 4679 let methods: &[NativeMethod] = &[ 4680 ("add", set_proto_add), 4681 ("has", set_proto_has), 4682 ("delete", set_proto_delete), 4683 ("clear", set_proto_clear), 4684 ("forEach", set_proto_for_each), 4685 ("keys", set_proto_keys), 4686 ("values", set_proto_values), 4687 ("entries", set_proto_entries), 4688 ]; 4689 for &(name, callback) in methods { 4690 let f = make_native(gc, name, callback); 4691 set_builtin_prop(gc, proto, name, Value::Function(f)); 4692 } 4693 // Set.prototype[@@iterator] — returns an iterator of values. 4694 let iter_fn = make_native(gc, "[Symbol.iterator]", set_symbol_iterator); 4695 set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter_fn)); 4696} 4697 4698fn set_proto_add(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4699 let value = args.first().cloned().unwrap_or(Value::Undefined); 4700 let value = normalize_zero(value); 4701 set_add_internal(ctx.gc, &ctx.this, value); 4702 Ok(ctx.this.clone()) 4703} 4704 4705fn set_proto_has(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4706 let value = args.first().cloned().unwrap_or(Value::Undefined); 4707 let value = normalize_zero(value); 4708 let entries = match collection_entries(ctx.gc, &ctx.this) { 4709 Some(e) => e, 4710 None => return Ok(Value::Boolean(false)), 4711 }; 4712 let count = collection_entry_count(ctx.gc, &ctx.this); 4713 Ok(Value::Boolean( 4714 find_key_index(ctx.gc, entries, count, &value).is_some(), 4715 )) 4716} 4717 4718fn set_proto_delete(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4719 let value = args.first().cloned().unwrap_or(Value::Undefined); 4720 let value = normalize_zero(value); 4721 let entries = match collection_entries(ctx.gc, &ctx.this) { 4722 Some(e) => e, 4723 None => return Ok(Value::Boolean(false)), 4724 }; 4725 let count = collection_entry_count(ctx.gc, &ctx.this); 4726 let live = collection_live_count(ctx.gc, &ctx.this); 4727 if let Some(idx) = find_key_index(ctx.gc, entries, count, &value) { 4728 delete_entry_at(ctx.gc, entries, idx); 4729 set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 4730 return Ok(Value::Boolean(true)); 4731 } 4732 Ok(Value::Boolean(false)) 4733} 4734 4735fn set_proto_clear(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4736 clear_collection(ctx.gc, &ctx.this); 4737 Ok(Value::Undefined) 4738} 4739 4740fn set_proto_for_each(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4741 let callback = match args.first() { 4742 Some(Value::Function(r)) => *r, 4743 _ => { 4744 return Err(RuntimeError::type_error( 4745 "Set.prototype.forEach requires a function argument", 4746 )) 4747 } 4748 }; 4749 let entries = match collection_entries(ctx.gc, &ctx.this) { 4750 Some(e) => e, 4751 None => return Ok(Value::Undefined), 4752 }; 4753 let count = collection_entry_count(ctx.gc, &ctx.this); 4754 let mut values = Vec::new(); 4755 for i in 0..count { 4756 if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4757 values.push(k); 4758 } 4759 } 4760 let this_val = ctx.this.clone(); 4761 for v in values { 4762 call_native_callback( 4763 ctx.gc, 4764 callback, 4765 &[v.clone(), v, this_val.clone()], 4766 ctx.console_output, 4767 ctx.dom_bridge, 4768 )?; 4769 } 4770 Ok(Value::Undefined) 4771} 4772 4773fn set_proto_keys(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4774 set_proto_values(_args, ctx) 4775} 4776 4777fn set_proto_values(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4778 let entries = match collection_entries(ctx.gc, &ctx.this) { 4779 Some(e) => e, 4780 None => return Ok(make_value_array(ctx.gc, &[])), 4781 }; 4782 let count = collection_entry_count(ctx.gc, &ctx.this); 4783 let mut items = Vec::new(); 4784 for i in 0..count { 4785 if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4786 items.push(k); 4787 } 4788 } 4789 Ok(make_value_array(ctx.gc, &items)) 4790} 4791 4792fn set_proto_entries(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4793 let entries = match collection_entries(ctx.gc, &ctx.this) { 4794 Some(e) => e, 4795 None => return Ok(make_value_array(ctx.gc, &[])), 4796 }; 4797 let count = collection_entry_count(ctx.gc, &ctx.this); 4798 let mut items = Vec::new(); 4799 for i in 0..count { 4800 if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4801 let pair = make_value_array(ctx.gc, &[k.clone(), k]); 4802 items.push(pair); 4803 } 4804 } 4805 Ok(make_value_array(ctx.gc, &items)) 4806} 4807 4808// ── WeakMap constructor & prototype ────────────────────────── 4809 4810fn weakmap_constructor(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4811 let proto = WEAKMAP_PROTO.with(|cell| cell.get()); 4812 let obj_ref = make_collection_obj(ctx.gc, proto); 4813 Ok(Value::Object(obj_ref)) 4814} 4815 4816fn is_object_value(val: &Value) -> bool { 4817 matches!(val, Value::Object(_) | Value::Function(_)) 4818} 4819 4820fn init_weakmap_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 4821 let methods: &[NativeMethod] = &[ 4822 ("set", weakmap_proto_set), 4823 ("get", weakmap_proto_get), 4824 ("has", weakmap_proto_has), 4825 ("delete", weakmap_proto_delete), 4826 ]; 4827 for &(name, callback) in methods { 4828 let f = make_native(gc, name, callback); 4829 set_builtin_prop(gc, proto, name, Value::Function(f)); 4830 } 4831} 4832 4833fn weakmap_proto_set(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4834 let key = args.first().cloned().unwrap_or(Value::Undefined); 4835 if !is_object_value(&key) { 4836 return Err(RuntimeError::type_error("WeakMap key must be an object")); 4837 } 4838 let value = args.get(1).cloned().unwrap_or(Value::Undefined); 4839 map_set_internal(ctx.gc, &ctx.this, key, value); 4840 Ok(ctx.this.clone()) 4841} 4842 4843fn weakmap_proto_get(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4844 let key = args.first().cloned().unwrap_or(Value::Undefined); 4845 if !is_object_value(&key) { 4846 return Ok(Value::Undefined); 4847 } 4848 let entries = match collection_entries(ctx.gc, &ctx.this) { 4849 Some(e) => e, 4850 None => return Ok(Value::Undefined), 4851 }; 4852 let count = collection_entry_count(ctx.gc, &ctx.this); 4853 if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4854 return Ok(entry_value_at(ctx.gc, entries, idx)); 4855 } 4856 Ok(Value::Undefined) 4857} 4858 4859fn weakmap_proto_has(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4860 let key = args.first().cloned().unwrap_or(Value::Undefined); 4861 if !is_object_value(&key) { 4862 return Ok(Value::Boolean(false)); 4863 } 4864 let entries = match collection_entries(ctx.gc, &ctx.this) { 4865 Some(e) => e, 4866 None => return Ok(Value::Boolean(false)), 4867 }; 4868 let count = collection_entry_count(ctx.gc, &ctx.this); 4869 Ok(Value::Boolean( 4870 find_key_index(ctx.gc, entries, count, &key).is_some(), 4871 )) 4872} 4873 4874fn weakmap_proto_delete(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4875 let key = args.first().cloned().unwrap_or(Value::Undefined); 4876 if !is_object_value(&key) { 4877 return Ok(Value::Boolean(false)); 4878 } 4879 let entries = match collection_entries(ctx.gc, &ctx.this) { 4880 Some(e) => e, 4881 None => return Ok(Value::Boolean(false)), 4882 }; 4883 let count = collection_entry_count(ctx.gc, &ctx.this); 4884 let live = collection_live_count(ctx.gc, &ctx.this); 4885 if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4886 delete_entry_at(ctx.gc, entries, idx); 4887 set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 4888 return Ok(Value::Boolean(true)); 4889 } 4890 Ok(Value::Boolean(false)) 4891} 4892 4893// ── WeakSet constructor & prototype ────────────────────────── 4894 4895fn weakset_constructor(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4896 let proto = WEAKSET_PROTO.with(|cell| cell.get()); 4897 let obj_ref = make_collection_obj(ctx.gc, proto); 4898 Ok(Value::Object(obj_ref)) 4899} 4900 4901fn init_weakset_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 4902 let methods: &[NativeMethod] = &[ 4903 ("add", weakset_proto_add), 4904 ("has", weakset_proto_has), 4905 ("delete", weakset_proto_delete), 4906 ]; 4907 for &(name, callback) in methods { 4908 let f = make_native(gc, name, callback); 4909 set_builtin_prop(gc, proto, name, Value::Function(f)); 4910 } 4911} 4912 4913fn weakset_proto_add(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4914 let value = args.first().cloned().unwrap_or(Value::Undefined); 4915 if !is_object_value(&value) { 4916 return Err(RuntimeError::type_error("WeakSet value must be an object")); 4917 } 4918 set_add_internal(ctx.gc, &ctx.this, value); 4919 Ok(ctx.this.clone()) 4920} 4921 4922fn weakset_proto_has(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4923 let value = args.first().cloned().unwrap_or(Value::Undefined); 4924 if !is_object_value(&value) { 4925 return Ok(Value::Boolean(false)); 4926 } 4927 let entries = match collection_entries(ctx.gc, &ctx.this) { 4928 Some(e) => e, 4929 None => return Ok(Value::Boolean(false)), 4930 }; 4931 let count = collection_entry_count(ctx.gc, &ctx.this); 4932 Ok(Value::Boolean( 4933 find_key_index(ctx.gc, entries, count, &value).is_some(), 4934 )) 4935} 4936 4937fn weakset_proto_delete(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4938 let value = args.first().cloned().unwrap_or(Value::Undefined); 4939 if !is_object_value(&value) { 4940 return Ok(Value::Boolean(false)); 4941 } 4942 let entries = match collection_entries(ctx.gc, &ctx.this) { 4943 Some(e) => e, 4944 None => return Ok(Value::Boolean(false)), 4945 }; 4946 let count = collection_entry_count(ctx.gc, &ctx.this); 4947 let live = collection_live_count(ctx.gc, &ctx.this); 4948 if let Some(idx) = find_key_index(ctx.gc, entries, count, &value) { 4949 delete_entry_at(ctx.gc, entries, idx); 4950 set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 4951 return Ok(Value::Boolean(true)); 4952 } 4953 Ok(Value::Boolean(false)) 4954} 4955 4956// ── Promise built-in ───────────────────────────────────────── 4957 4958/// Promise states. 4959const PROMISE_PENDING: f64 = 0.0; 4960pub const PROMISE_FULFILLED: f64 = 1.0; 4961pub const PROMISE_REJECTED: f64 = 2.0; 4962 4963/// Hidden property keys for Promise objects. 4964const PROMISE_STATE_KEY: &str = "__promise_state__"; 4965pub const PROMISE_RESULT_KEY: &str = "__promise_result__"; 4966const PROMISE_REACTIONS_KEY: &str = "__promise_reactions__"; 4967const PROMISE_REACTION_COUNT_KEY: &str = "__promise_reaction_count__"; 4968 4969thread_local! { 4970 static PROMISE_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 4971 static MICROTASK_QUEUE: RefCell<Vec<Microtask>> = const { RefCell::new(Vec::new()) }; 4972} 4973 4974/// A microtask queued by a promise settlement. 4975pub struct Microtask { 4976 /// The handler to call (onFulfilled or onRejected). None means identity/thrower. 4977 pub handler: Option<GcRef>, 4978 /// The value to pass to the handler. 4979 pub value: Value, 4980 /// The chained promise to resolve/reject with the handler's result. 4981 pub chained_promise: Option<GcRef>, 4982 /// Whether this is a fulfillment reaction (true) or rejection (false). 4983 pub is_fulfillment: bool, 4984} 4985 4986/// Take all pending microtasks from the queue (called by the VM). 4987pub fn take_microtasks() -> Vec<Microtask> { 4988 MICROTASK_QUEUE.with(|q| std::mem::take(&mut *q.borrow_mut())) 4989} 4990 4991fn enqueue_microtask(task: Microtask) { 4992 MICROTASK_QUEUE.with(|q| q.borrow_mut().push(task)); 4993} 4994 4995/// Public wrappers for functions used by the VM's microtask drain. 4996pub fn promise_get_prop_pub(gc: &Gc<HeapObject>, promise: GcRef, key: &str) -> Value { 4997 promise_get_prop(gc, promise, key) 4998} 4999 5000pub fn promise_state_pub(gc: &Gc<HeapObject>, promise: GcRef) -> f64 { 5001 promise_state(gc, promise) 5002} 5003 5004pub fn is_promise_pub(gc: &Gc<HeapObject>, value: &Value) -> bool { 5005 is_promise(gc, value) 5006} 5007 5008pub fn chain_promise_pub(gc: &mut Gc<HeapObject>, source: GcRef, target: GcRef) { 5009 chain_promise(gc, source, target) 5010} 5011 5012pub fn create_promise_object_pub(gc: &mut Gc<HeapObject>) -> GcRef { 5013 create_promise_object(gc) 5014} 5015 5016pub fn enqueue_microtask_pub(task: Microtask) { 5017 enqueue_microtask(task); 5018} 5019 5020pub fn add_reaction_pub( 5021 gc: &mut Gc<HeapObject>, 5022 promise: GcRef, 5023 on_fulfilled: Value, 5024 on_rejected: Value, 5025) -> GcRef { 5026 add_reaction(gc, promise, on_fulfilled, on_rejected) 5027} 5028 5029/// Initialize Promise.prototype in a standalone GC (for unit tests that 5030/// create promise objects without a full VM). 5031pub fn init_promise_proto_for_test(gc: &mut Gc<HeapObject>) { 5032 let proto_data = ObjectData::new(); 5033 let promise_proto = gc.alloc(HeapObject::Object(proto_data)); 5034 init_promise_prototype(gc, promise_proto); 5035 PROMISE_PROTO.with(|cell| cell.set(Some(promise_proto))); 5036} 5037 5038fn init_promise_builtins(vm: &mut Vm) { 5039 // Create Promise.prototype (inherits from Object.prototype). 5040 let mut proto_data = ObjectData::new(); 5041 if let Some(proto) = vm.object_prototype { 5042 proto_data.prototype = Some(proto); 5043 } 5044 let promise_proto = vm.gc.alloc(HeapObject::Object(proto_data)); 5045 init_promise_prototype(&mut vm.gc, promise_proto); 5046 PROMISE_PROTO.with(|cell| cell.set(Some(promise_proto))); 5047 vm.promise_prototype = Some(promise_proto); 5048 5049 // Register native helper functions used by the JS preamble. 5050 let create_fn = make_native(&mut vm.gc, "__Promise_create", promise_native_create); 5051 vm.set_global("__Promise_create", Value::Function(create_fn)); 5052 5053 let resolve_fn = make_native(&mut vm.gc, "__Promise_resolve", promise_native_resolve); 5054 vm.set_global("__Promise_resolve", Value::Function(resolve_fn)); 5055 5056 let reject_fn = make_native(&mut vm.gc, "__Promise_reject", promise_native_reject); 5057 vm.set_global("__Promise_reject", Value::Function(reject_fn)); 5058 5059 // Register Promise.resolve and Promise.reject as standalone globals 5060 // that the JS preamble will attach to the Promise constructor. 5061 let static_resolve = make_native(&mut vm.gc, "resolve", promise_static_resolve); 5062 vm.set_global("__Promise_static_resolve", Value::Function(static_resolve)); 5063 5064 let static_reject = make_native(&mut vm.gc, "reject", promise_static_reject); 5065 vm.set_global("__Promise_static_reject", Value::Function(static_reject)); 5066 5067 let static_all = make_native(&mut vm.gc, "all", promise_static_all); 5068 vm.set_global("__Promise_static_all", Value::Function(static_all)); 5069 5070 let static_race = make_native(&mut vm.gc, "race", promise_static_race); 5071 vm.set_global("__Promise_static_race", Value::Function(static_race)); 5072 5073 let static_all_settled = make_native(&mut vm.gc, "allSettled", promise_static_all_settled); 5074 vm.set_global( 5075 "__Promise_static_allSettled", 5076 Value::Function(static_all_settled), 5077 ); 5078 5079 let static_any = make_native(&mut vm.gc, "any", promise_static_any); 5080 vm.set_global("__Promise_static_any", Value::Function(static_any)); 5081} 5082 5083fn init_promise_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 5084 let methods: &[NativeMethod] = &[ 5085 ("then", promise_proto_then), 5086 ("catch", promise_proto_catch), 5087 ("finally", promise_proto_finally), 5088 ]; 5089 for &(name, callback) in methods { 5090 let f = make_native(gc, name, callback); 5091 set_builtin_prop(gc, proto, name, Value::Function(f)); 5092 } 5093} 5094 5095/// Create a new pending promise object. 5096fn create_promise_object(gc: &mut Gc<HeapObject>) -> GcRef { 5097 let reactions = gc.alloc(HeapObject::Object(ObjectData::new())); 5098 let proto = PROMISE_PROTO.with(|cell| cell.get()); 5099 let mut data = ObjectData::new(); 5100 if let Some(p) = proto { 5101 data.prototype = Some(p); 5102 } 5103 data.properties.insert( 5104 PROMISE_STATE_KEY.to_string(), 5105 Property::builtin(Value::Number(PROMISE_PENDING)), 5106 ); 5107 data.properties.insert( 5108 PROMISE_RESULT_KEY.to_string(), 5109 Property::builtin(Value::Undefined), 5110 ); 5111 data.properties.insert( 5112 PROMISE_REACTIONS_KEY.to_string(), 5113 Property::builtin(Value::Object(reactions)), 5114 ); 5115 data.properties.insert( 5116 PROMISE_REACTION_COUNT_KEY.to_string(), 5117 Property::builtin(Value::Number(0.0)), 5118 ); 5119 gc.alloc(HeapObject::Object(data)) 5120} 5121 5122/// Get a hidden property from a promise object. 5123fn promise_get_prop(gc: &Gc<HeapObject>, promise: GcRef, key: &str) -> Value { 5124 match gc.get(promise) { 5125 Some(HeapObject::Object(data)) => data 5126 .properties 5127 .get(key) 5128 .map(|p| p.value.clone()) 5129 .unwrap_or(Value::Undefined), 5130 _ => Value::Undefined, 5131 } 5132} 5133 5134/// Set a hidden property on a promise object. 5135fn promise_set_prop(gc: &mut Gc<HeapObject>, promise: GcRef, key: &str, value: Value) { 5136 if let Some(HeapObject::Object(data)) = gc.get_mut(promise) { 5137 data.properties 5138 .insert(key.to_string(), Property::builtin(value)); 5139 } 5140} 5141 5142/// Get the state of a promise (PROMISE_PENDING/FULFILLED/REJECTED). 5143fn promise_state(gc: &Gc<HeapObject>, promise: GcRef) -> f64 { 5144 match promise_get_prop(gc, promise, PROMISE_STATE_KEY) { 5145 Value::Number(n) => n, 5146 _ => PROMISE_PENDING, 5147 } 5148} 5149 5150/// Resolve a pending promise with a value. 5151pub fn resolve_promise_internal(gc: &mut Gc<HeapObject>, promise: GcRef, value: Value) { 5152 if promise_state(gc, promise) != PROMISE_PENDING { 5153 return; // Already settled. 5154 } 5155 promise_set_prop( 5156 gc, 5157 promise, 5158 PROMISE_STATE_KEY, 5159 Value::Number(PROMISE_FULFILLED), 5160 ); 5161 promise_set_prop(gc, promise, PROMISE_RESULT_KEY, value.clone()); 5162 trigger_reactions(gc, promise, value, true); 5163} 5164 5165/// Reject a pending promise with a reason. 5166pub fn reject_promise_internal(gc: &mut Gc<HeapObject>, promise: GcRef, reason: Value) { 5167 if promise_state(gc, promise) != PROMISE_PENDING { 5168 return; // Already settled. 5169 } 5170 promise_set_prop( 5171 gc, 5172 promise, 5173 PROMISE_STATE_KEY, 5174 Value::Number(PROMISE_REJECTED), 5175 ); 5176 promise_set_prop(gc, promise, PROMISE_RESULT_KEY, reason.clone()); 5177 trigger_reactions(gc, promise, reason, false); 5178} 5179 5180/// Enqueue microtasks for all registered reactions on a promise. 5181fn trigger_reactions(gc: &mut Gc<HeapObject>, promise: GcRef, value: Value, fulfilled: bool) { 5182 let reactions_ref = match promise_get_prop(gc, promise, PROMISE_REACTIONS_KEY) { 5183 Value::Object(r) => r, 5184 _ => return, 5185 }; 5186 let count = match promise_get_prop(gc, promise, PROMISE_REACTION_COUNT_KEY) { 5187 Value::Number(n) => n as usize, 5188 _ => 0, 5189 }; 5190 5191 // Collect reactions before mutating. 5192 let mut reactions = Vec::new(); 5193 for i in 0..count { 5194 let fulfill_key = format!("{i}_fulfill"); 5195 let reject_key = format!("{i}_reject"); 5196 let promise_key = format!("{i}_promise"); 5197 5198 let on_fulfilled = match gc.get(reactions_ref) { 5199 Some(HeapObject::Object(data)) => data 5200 .properties 5201 .get(&fulfill_key) 5202 .map(|p| p.value.clone()) 5203 .unwrap_or(Value::Undefined), 5204 _ => Value::Undefined, 5205 }; 5206 let on_rejected = match gc.get(reactions_ref) { 5207 Some(HeapObject::Object(data)) => data 5208 .properties 5209 .get(&reject_key) 5210 .map(|p| p.value.clone()) 5211 .unwrap_or(Value::Undefined), 5212 _ => Value::Undefined, 5213 }; 5214 let chained = match gc.get(reactions_ref) { 5215 Some(HeapObject::Object(data)) => data 5216 .properties 5217 .get(&promise_key) 5218 .and_then(|p| p.value.gc_ref()), 5219 _ => None, 5220 }; 5221 5222 let handler = if fulfilled { on_fulfilled } else { on_rejected }; 5223 let handler_ref = match &handler { 5224 Value::Function(r) => Some(*r), 5225 _ => None, 5226 }; 5227 5228 reactions.push(Microtask { 5229 handler: handler_ref, 5230 value: value.clone(), 5231 chained_promise: chained, 5232 is_fulfillment: fulfilled, 5233 }); 5234 } 5235 5236 for task in reactions { 5237 enqueue_microtask(task); 5238 } 5239} 5240 5241/// Add a reaction to a promise. Returns the chained promise GcRef. 5242fn add_reaction( 5243 gc: &mut Gc<HeapObject>, 5244 promise: GcRef, 5245 on_fulfilled: Value, 5246 on_rejected: Value, 5247) -> GcRef { 5248 let chained = create_promise_object(gc); 5249 5250 let reactions_ref = match promise_get_prop(gc, promise, PROMISE_REACTIONS_KEY) { 5251 Value::Object(r) => r, 5252 _ => return chained, 5253 }; 5254 let count = match promise_get_prop(gc, promise, PROMISE_REACTION_COUNT_KEY) { 5255 Value::Number(n) => n as usize, 5256 _ => 0, 5257 }; 5258 5259 if let Some(HeapObject::Object(data)) = gc.get_mut(reactions_ref) { 5260 data.properties 5261 .insert(format!("{count}_fulfill"), Property::builtin(on_fulfilled)); 5262 data.properties 5263 .insert(format!("{count}_reject"), Property::builtin(on_rejected)); 5264 data.properties.insert( 5265 format!("{count}_promise"), 5266 Property::builtin(Value::Object(chained)), 5267 ); 5268 } 5269 5270 promise_set_prop( 5271 gc, 5272 promise, 5273 PROMISE_REACTION_COUNT_KEY, 5274 Value::Number((count + 1) as f64), 5275 ); 5276 5277 chained 5278} 5279 5280// ── Promise native helpers (called from JS preamble) ───────── 5281 5282/// `__Promise_create()` — create a new pending promise object. 5283fn promise_native_create(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5284 let promise = create_promise_object(ctx.gc); 5285 Ok(Value::Object(promise)) 5286} 5287 5288/// `__Promise_resolve(promise, value)` — resolve a pending promise. 5289fn promise_native_resolve(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5290 let promise_ref = args.first().and_then(|v| v.gc_ref()).ok_or_else(|| { 5291 RuntimeError::type_error("__Promise_resolve: first arg must be a promise") 5292 })?; 5293 let value = args.get(1).cloned().unwrap_or(Value::Undefined); 5294 5295 // If value is a thenable (promise), chain it. 5296 if is_promise(ctx.gc, &value) { 5297 let value_ref = value.gc_ref().unwrap(); 5298 let state = promise_state(ctx.gc, value_ref); 5299 if state == PROMISE_FULFILLED { 5300 let result = promise_get_prop(ctx.gc, value_ref, PROMISE_RESULT_KEY); 5301 resolve_promise_internal(ctx.gc, promise_ref, result); 5302 } else if state == PROMISE_REJECTED { 5303 let reason = promise_get_prop(ctx.gc, value_ref, PROMISE_RESULT_KEY); 5304 reject_promise_internal(ctx.gc, promise_ref, reason); 5305 } else { 5306 // Pending thenable: chain so that when value_ref settles, 5307 // promise_ref settles the same way. 5308 chain_promise(ctx.gc, value_ref, promise_ref); 5309 } 5310 } else { 5311 resolve_promise_internal(ctx.gc, promise_ref, value); 5312 } 5313 5314 Ok(Value::Undefined) 5315} 5316 5317/// Chain a source promise to a target: when source settles, settle target the same way. 5318fn chain_promise(gc: &mut Gc<HeapObject>, source: GcRef, target: GcRef) { 5319 let reactions_ref = match promise_get_prop(gc, source, PROMISE_REACTIONS_KEY) { 5320 Value::Object(r) => r, 5321 _ => return, 5322 }; 5323 let count = match promise_get_prop(gc, source, PROMISE_REACTION_COUNT_KEY) { 5324 Value::Number(n) => n as usize, 5325 _ => 0, 5326 }; 5327 5328 // Store the target promise directly — the microtask drain handles identity propagation. 5329 if let Some(HeapObject::Object(data)) = gc.get_mut(reactions_ref) { 5330 data.properties.insert( 5331 format!("{count}_fulfill"), 5332 Property::builtin(Value::Undefined), 5333 ); 5334 data.properties.insert( 5335 format!("{count}_reject"), 5336 Property::builtin(Value::Undefined), 5337 ); 5338 data.properties.insert( 5339 format!("{count}_promise"), 5340 Property::builtin(Value::Object(target)), 5341 ); 5342 } 5343 5344 promise_set_prop( 5345 gc, 5346 source, 5347 PROMISE_REACTION_COUNT_KEY, 5348 Value::Number((count + 1) as f64), 5349 ); 5350} 5351 5352/// Check if a value is a promise (has __promise_state__ property). 5353fn is_promise(gc: &Gc<HeapObject>, value: &Value) -> bool { 5354 let gc_ref = match value.gc_ref() { 5355 Some(r) => r, 5356 None => return false, 5357 }; 5358 match gc.get(gc_ref) { 5359 Some(HeapObject::Object(data)) => data.properties.contains_key(PROMISE_STATE_KEY), 5360 _ => false, 5361 } 5362} 5363 5364/// `__Promise_reject(promise, reason)` — reject a pending promise. 5365fn promise_native_reject(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5366 let promise_ref = args 5367 .first() 5368 .and_then(|v| v.gc_ref()) 5369 .ok_or_else(|| RuntimeError::type_error("__Promise_reject: first arg must be a promise"))?; 5370 let reason = args.get(1).cloned().unwrap_or(Value::Undefined); 5371 reject_promise_internal(ctx.gc, promise_ref, reason); 5372 Ok(Value::Undefined) 5373} 5374 5375// ── Promise.prototype.then / catch / finally ───────────────── 5376 5377fn promise_proto_then(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5378 let promise_ref = match ctx.this.gc_ref() { 5379 Some(r) => r, 5380 None => return Err(RuntimeError::type_error("then called on non-object")), 5381 }; 5382 5383 let on_fulfilled = args.first().cloned().unwrap_or(Value::Undefined); 5384 let on_rejected = args.get(1).cloned().unwrap_or(Value::Undefined); 5385 5386 let state = promise_state(ctx.gc, promise_ref); 5387 5388 if state == PROMISE_PENDING { 5389 // Register reaction for later. 5390 let chained = add_reaction(ctx.gc, promise_ref, on_fulfilled, on_rejected); 5391 return Ok(Value::Object(chained)); 5392 } 5393 5394 // Already settled — enqueue microtask immediately. 5395 let result = promise_get_prop(ctx.gc, promise_ref, PROMISE_RESULT_KEY); 5396 let chained = create_promise_object(ctx.gc); 5397 let fulfilled = state == PROMISE_FULFILLED; 5398 let handler = if fulfilled { 5399 &on_fulfilled 5400 } else { 5401 &on_rejected 5402 }; 5403 let handler_ref = match handler { 5404 Value::Function(r) => Some(*r), 5405 _ => None, 5406 }; 5407 5408 enqueue_microtask(Microtask { 5409 handler: handler_ref, 5410 value: result, 5411 chained_promise: Some(chained), 5412 is_fulfillment: fulfilled, 5413 }); 5414 5415 Ok(Value::Object(chained)) 5416} 5417 5418fn promise_proto_catch(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5419 let on_rejected = args.first().cloned().unwrap_or(Value::Undefined); 5420 promise_proto_then(&[Value::Undefined, on_rejected], ctx) 5421} 5422 5423fn promise_proto_finally(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5424 let promise_ref = match ctx.this.gc_ref() { 5425 Some(r) => r, 5426 None => return Err(RuntimeError::type_error("finally called on non-object")), 5427 }; 5428 5429 let on_finally = args.first().cloned().unwrap_or(Value::Undefined); 5430 let state = promise_state(ctx.gc, promise_ref); 5431 5432 if state == PROMISE_PENDING { 5433 // Register reaction: finally handler doesn't receive value, just runs. 5434 let chained = add_reaction(ctx.gc, promise_ref, on_finally.clone(), on_finally); 5435 // Mark the chained promise reactions as "finally" so drain can handle them. 5436 promise_set_prop(ctx.gc, chained, "__finally__", Value::Boolean(true)); 5437 // Store parent result for propagation. 5438 promise_set_prop( 5439 ctx.gc, 5440 chained, 5441 "__finally_parent__", 5442 Value::Object(promise_ref), 5443 ); 5444 return Ok(Value::Object(chained)); 5445 } 5446 5447 // Already settled. 5448 let _result = promise_get_prop(ctx.gc, promise_ref, PROMISE_RESULT_KEY); 5449 let chained = create_promise_object(ctx.gc); 5450 let handler_ref = match &on_finally { 5451 Value::Function(r) => Some(*r), 5452 _ => None, 5453 }; 5454 5455 // For finally, we enqueue the callback but propagate the original result. 5456 promise_set_prop(ctx.gc, chained, "__finally__", Value::Boolean(true)); 5457 promise_set_prop( 5458 ctx.gc, 5459 chained, 5460 "__finally_parent__", 5461 Value::Object(promise_ref), 5462 ); 5463 5464 enqueue_microtask(Microtask { 5465 handler: handler_ref, 5466 value: Value::Undefined, // finally callback gets no arguments 5467 chained_promise: Some(chained), 5468 is_fulfillment: state == PROMISE_FULFILLED, 5469 }); 5470 5471 Ok(Value::Object(chained)) 5472} 5473 5474// ── Promise static methods ─────────────────────────────────── 5475 5476fn promise_static_resolve(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5477 let value = args.first().cloned().unwrap_or(Value::Undefined); 5478 5479 // If already a promise, return it. 5480 if is_promise(ctx.gc, &value) { 5481 return Ok(value); 5482 } 5483 5484 let promise = create_promise_object(ctx.gc); 5485 resolve_promise_internal(ctx.gc, promise, value); 5486 Ok(Value::Object(promise)) 5487} 5488 5489fn promise_static_reject(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5490 let reason = args.first().cloned().unwrap_or(Value::Undefined); 5491 let promise = create_promise_object(ctx.gc); 5492 reject_promise_internal(ctx.gc, promise, reason); 5493 Ok(Value::Object(promise)) 5494} 5495 5496fn promise_static_all(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5497 let iterable = args.first().cloned().unwrap_or(Value::Undefined); 5498 let arr_ref = match iterable.gc_ref() { 5499 Some(r) => r, 5500 None => { 5501 let p = create_promise_object(ctx.gc); 5502 reject_promise_internal( 5503 ctx.gc, 5504 p, 5505 Value::String("Promise.all requires an iterable".to_string()), 5506 ); 5507 return Ok(Value::Object(p)); 5508 } 5509 }; 5510 5511 let len = array_length(ctx.gc, arr_ref); 5512 let result_promise = create_promise_object(ctx.gc); 5513 5514 if len == 0 { 5515 let empty = make_value_array(ctx.gc, &[]); 5516 resolve_promise_internal(ctx.gc, result_promise, empty); 5517 return Ok(Value::Object(result_promise)); 5518 } 5519 5520 // Create a results array and a counter object. 5521 let results = make_value_array(ctx.gc, &vec![Value::Undefined; len]); 5522 let results_ref = results.gc_ref().unwrap(); 5523 5524 // We track remaining count and results in hidden props on result_promise. 5525 promise_set_prop( 5526 ctx.gc, 5527 result_promise, 5528 "__all_remaining__", 5529 Value::Number(len as f64), 5530 ); 5531 promise_set_prop(ctx.gc, result_promise, "__all_results__", results.clone()); 5532 5533 for i in 0..len { 5534 let item = array_get(ctx.gc, arr_ref, i); 5535 if is_promise(ctx.gc, &item) { 5536 let item_ref = item.gc_ref().unwrap(); 5537 let state = promise_state(ctx.gc, item_ref); 5538 if state == PROMISE_FULFILLED { 5539 let val = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5540 array_set(ctx.gc, results_ref, i, val); 5541 promise_all_decrement(ctx.gc, result_promise); 5542 } else if state == PROMISE_REJECTED { 5543 let reason = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5544 reject_promise_internal(ctx.gc, result_promise, reason); 5545 return Ok(Value::Object(result_promise)); 5546 } else { 5547 // Pending: we need to register a reaction. Store index info. 5548 promise_set_prop( 5549 ctx.gc, 5550 item_ref, 5551 &format!("__all_target_{i}__"), 5552 Value::Object(result_promise), 5553 ); 5554 // Add a reaction — the microtask drain will handle all-tracking. 5555 let chained = add_reaction(ctx.gc, item_ref, Value::Undefined, Value::Undefined); 5556 promise_set_prop(ctx.gc, chained, "__all_index__", Value::Number(i as f64)); 5557 promise_set_prop( 5558 ctx.gc, 5559 chained, 5560 "__all_target__", 5561 Value::Object(result_promise), 5562 ); 5563 } 5564 } else { 5565 // Non-promise value: treat as immediately resolved. 5566 array_set(ctx.gc, results_ref, i, item); 5567 promise_all_decrement(ctx.gc, result_promise); 5568 } 5569 } 5570 5571 Ok(Value::Object(result_promise)) 5572} 5573 5574fn promise_all_decrement(gc: &mut Gc<HeapObject>, result_promise: GcRef) { 5575 let remaining = match promise_get_prop(gc, result_promise, "__all_remaining__") { 5576 Value::Number(n) => n as usize, 5577 _ => return, 5578 }; 5579 let new_remaining = remaining.saturating_sub(1); 5580 promise_set_prop( 5581 gc, 5582 result_promise, 5583 "__all_remaining__", 5584 Value::Number(new_remaining as f64), 5585 ); 5586 if new_remaining == 0 { 5587 let results = promise_get_prop(gc, result_promise, "__all_results__"); 5588 resolve_promise_internal(gc, result_promise, results); 5589 } 5590} 5591 5592fn promise_static_race(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5593 let iterable = args.first().cloned().unwrap_or(Value::Undefined); 5594 let arr_ref = match iterable.gc_ref() { 5595 Some(r) => r, 5596 None => { 5597 let p = create_promise_object(ctx.gc); 5598 reject_promise_internal( 5599 ctx.gc, 5600 p, 5601 Value::String("Promise.race requires an iterable".to_string()), 5602 ); 5603 return Ok(Value::Object(p)); 5604 } 5605 }; 5606 5607 let len = array_length(ctx.gc, arr_ref); 5608 let result_promise = create_promise_object(ctx.gc); 5609 5610 for i in 0..len { 5611 let item = array_get(ctx.gc, arr_ref, i); 5612 if is_promise(ctx.gc, &item) { 5613 let item_ref = item.gc_ref().unwrap(); 5614 let state = promise_state(ctx.gc, item_ref); 5615 if state == PROMISE_FULFILLED { 5616 let val = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5617 resolve_promise_internal(ctx.gc, result_promise, val); 5618 return Ok(Value::Object(result_promise)); 5619 } else if state == PROMISE_REJECTED { 5620 let reason = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5621 reject_promise_internal(ctx.gc, result_promise, reason); 5622 return Ok(Value::Object(result_promise)); 5623 } else { 5624 chain_promise(ctx.gc, item_ref, result_promise); 5625 } 5626 } else { 5627 resolve_promise_internal(ctx.gc, result_promise, item); 5628 return Ok(Value::Object(result_promise)); 5629 } 5630 } 5631 5632 Ok(Value::Object(result_promise)) 5633} 5634 5635fn promise_static_all_settled( 5636 args: &[Value], 5637 ctx: &mut NativeContext, 5638) -> Result<Value, RuntimeError> { 5639 let iterable = args.first().cloned().unwrap_or(Value::Undefined); 5640 let arr_ref = match iterable.gc_ref() { 5641 Some(r) => r, 5642 None => { 5643 let p = create_promise_object(ctx.gc); 5644 reject_promise_internal( 5645 ctx.gc, 5646 p, 5647 Value::String("Promise.allSettled requires an iterable".to_string()), 5648 ); 5649 return Ok(Value::Object(p)); 5650 } 5651 }; 5652 5653 let len = array_length(ctx.gc, arr_ref); 5654 let result_promise = create_promise_object(ctx.gc); 5655 5656 if len == 0 { 5657 let empty = make_value_array(ctx.gc, &[]); 5658 resolve_promise_internal(ctx.gc, result_promise, empty); 5659 return Ok(Value::Object(result_promise)); 5660 } 5661 5662 let results = make_value_array(ctx.gc, &vec![Value::Undefined; len]); 5663 let results_ref = results.gc_ref().unwrap(); 5664 5665 promise_set_prop( 5666 ctx.gc, 5667 result_promise, 5668 "__all_remaining__", 5669 Value::Number(len as f64), 5670 ); 5671 promise_set_prop(ctx.gc, result_promise, "__all_results__", results); 5672 5673 for i in 0..len { 5674 let item = array_get(ctx.gc, arr_ref, i); 5675 if is_promise(ctx.gc, &item) { 5676 let item_ref = item.gc_ref().unwrap(); 5677 let state = promise_state(ctx.gc, item_ref); 5678 if state != PROMISE_PENDING { 5679 let val = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5680 let status_str = if state == PROMISE_FULFILLED { 5681 "fulfilled" 5682 } else { 5683 "rejected" 5684 }; 5685 let entry = 5686 make_settled_entry(ctx.gc, status_str, &val, state == PROMISE_FULFILLED); 5687 array_set(ctx.gc, results_ref, i, entry); 5688 promise_all_decrement(ctx.gc, result_promise); 5689 } else { 5690 chain_promise(ctx.gc, item_ref, result_promise); 5691 } 5692 } else { 5693 let entry = make_settled_entry(ctx.gc, "fulfilled", &item, true); 5694 array_set(ctx.gc, results_ref, i, entry); 5695 promise_all_decrement(ctx.gc, result_promise); 5696 } 5697 } 5698 5699 Ok(Value::Object(result_promise)) 5700} 5701 5702fn make_settled_entry( 5703 gc: &mut Gc<HeapObject>, 5704 status: &str, 5705 value: &Value, 5706 is_fulfilled: bool, 5707) -> Value { 5708 let mut data = ObjectData::new(); 5709 data.properties.insert( 5710 "status".to_string(), 5711 Property::data(Value::String(status.to_string())), 5712 ); 5713 if is_fulfilled { 5714 data.properties 5715 .insert("value".to_string(), Property::data(value.clone())); 5716 } else { 5717 data.properties 5718 .insert("reason".to_string(), Property::data(value.clone())); 5719 } 5720 Value::Object(gc.alloc(HeapObject::Object(data))) 5721} 5722 5723fn promise_static_any(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 5724 let iterable = args.first().cloned().unwrap_or(Value::Undefined); 5725 let arr_ref = match iterable.gc_ref() { 5726 Some(r) => r, 5727 None => { 5728 let p = create_promise_object(ctx.gc); 5729 reject_promise_internal( 5730 ctx.gc, 5731 p, 5732 Value::String("Promise.any requires an iterable".to_string()), 5733 ); 5734 return Ok(Value::Object(p)); 5735 } 5736 }; 5737 5738 let len = array_length(ctx.gc, arr_ref); 5739 let result_promise = create_promise_object(ctx.gc); 5740 5741 if len == 0 { 5742 // Reject with AggregateError. 5743 let err = Value::String("All promises were rejected".to_string()); 5744 reject_promise_internal(ctx.gc, result_promise, err); 5745 return Ok(Value::Object(result_promise)); 5746 } 5747 5748 let errors = make_value_array(ctx.gc, &vec![Value::Undefined; len]); 5749 let errors_ref = errors.gc_ref().unwrap(); 5750 5751 promise_set_prop( 5752 ctx.gc, 5753 result_promise, 5754 "__any_remaining__", 5755 Value::Number(len as f64), 5756 ); 5757 promise_set_prop(ctx.gc, result_promise, "__any_errors__", errors); 5758 5759 for i in 0..len { 5760 let item = array_get(ctx.gc, arr_ref, i); 5761 if is_promise(ctx.gc, &item) { 5762 let item_ref = item.gc_ref().unwrap(); 5763 let state = promise_state(ctx.gc, item_ref); 5764 if state == PROMISE_FULFILLED { 5765 let val = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5766 resolve_promise_internal(ctx.gc, result_promise, val); 5767 return Ok(Value::Object(result_promise)); 5768 } else if state == PROMISE_REJECTED { 5769 let reason = promise_get_prop(ctx.gc, item_ref, PROMISE_RESULT_KEY); 5770 array_set(ctx.gc, errors_ref, i, reason); 5771 promise_any_decrement(ctx.gc, result_promise); 5772 } else { 5773 chain_promise(ctx.gc, item_ref, result_promise); 5774 } 5775 } else { 5776 resolve_promise_internal(ctx.gc, result_promise, item); 5777 return Ok(Value::Object(result_promise)); 5778 } 5779 } 5780 5781 Ok(Value::Object(result_promise)) 5782} 5783 5784fn promise_any_decrement(gc: &mut Gc<HeapObject>, result_promise: GcRef) { 5785 let remaining = match promise_get_prop(gc, result_promise, "__any_remaining__") { 5786 Value::Number(n) => n as usize, 5787 _ => return, 5788 }; 5789 let new_remaining = remaining.saturating_sub(1); 5790 promise_set_prop( 5791 gc, 5792 result_promise, 5793 "__any_remaining__", 5794 Value::Number(new_remaining as f64), 5795 ); 5796 if new_remaining == 0 { 5797 let err = Value::String("All promises were rejected".to_string()); 5798 reject_promise_internal(gc, result_promise, err); 5799 } 5800} 5801 5802// ── JSON object ────────────────────────────────────────────── 5803 5804fn init_json_object(vm: &mut Vm) { 5805 let mut data = ObjectData::new(); 5806 if let Some(proto) = vm.object_prototype { 5807 data.prototype = Some(proto); 5808 } 5809 let json_ref = vm.gc.alloc(HeapObject::Object(data)); 5810 5811 let parse_fn = make_native(&mut vm.gc, "parse", json_parse); 5812 set_builtin_prop(&mut vm.gc, json_ref, "parse", Value::Function(parse_fn)); 5813 5814 let stringify_fn = make_native(&mut vm.gc, "stringify", json_stringify); 5815 set_builtin_prop( 5816 &mut vm.gc, 5817 json_ref, 5818 "stringify", 5819 Value::Function(stringify_fn), 5820 ); 5821 5822 vm.set_global("JSON", Value::Object(json_ref)); 5823} 5824 5825// ── JSON.parse ─────────────────────────────────────────────── 5826 5827/// Minimal JSON tokenizer. 5828#[derive(Debug, Clone, PartialEq)] 5829enum JsonToken { 5830 LBrace, 5831 RBrace, 5832 LBracket, 5833 RBracket, 5834 Colon, 5835 Comma, 5836 String(String), 5837 Number(f64), 5838 True, 5839 False, 5840 Null, 5841} 5842 5843struct JsonLexer<'a> { 5844 chars: &'a [u8], 5845 pos: usize, 5846} 5847 5848impl<'a> JsonLexer<'a> { 5849 fn new(input: &'a str) -> Self { 5850 Self { 5851 chars: input.as_bytes(), 5852 pos: 0, 5853 } 5854 } 5855 5856 fn skip_ws(&mut self) { 5857 while self.pos < self.chars.len() { 5858 match self.chars[self.pos] { 5859 b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1, 5860 _ => break, 5861 } 5862 } 5863 } 5864 5865 fn next_token(&mut self) -> Result<Option<JsonToken>, RuntimeError> { 5866 self.skip_ws(); 5867 if self.pos >= self.chars.len() { 5868 return Ok(None); 5869 } 5870 let ch = self.chars[self.pos]; 5871 match ch { 5872 b'{' => { 5873 self.pos += 1; 5874 Ok(Some(JsonToken::LBrace)) 5875 } 5876 b'}' => { 5877 self.pos += 1; 5878 Ok(Some(JsonToken::RBrace)) 5879 } 5880 b'[' => { 5881 self.pos += 1; 5882 Ok(Some(JsonToken::LBracket)) 5883 } 5884 b']' => { 5885 self.pos += 1; 5886 Ok(Some(JsonToken::RBracket)) 5887 } 5888 b':' => { 5889 self.pos += 1; 5890 Ok(Some(JsonToken::Colon)) 5891 } 5892 b',' => { 5893 self.pos += 1; 5894 Ok(Some(JsonToken::Comma)) 5895 } 5896 b'"' => self.read_string(), 5897 b't' => self.read_keyword("true", JsonToken::True), 5898 b'f' => self.read_keyword("false", JsonToken::False), 5899 b'n' => self.read_keyword("null", JsonToken::Null), 5900 b'-' | b'0'..=b'9' => self.read_number(), 5901 _ => Err(RuntimeError::syntax_error(format!( 5902 "Unexpected character '{}' in JSON", 5903 ch as char 5904 ))), 5905 } 5906 } 5907 5908 fn read_string(&mut self) -> Result<Option<JsonToken>, RuntimeError> { 5909 self.pos += 1; // skip opening quote 5910 let mut s = String::new(); 5911 while self.pos < self.chars.len() { 5912 let ch = self.chars[self.pos]; 5913 self.pos += 1; 5914 if ch == b'"' { 5915 return Ok(Some(JsonToken::String(s))); 5916 } 5917 if ch == b'\\' { 5918 if self.pos >= self.chars.len() { 5919 return Err(RuntimeError::syntax_error("Unterminated string in JSON")); 5920 } 5921 let esc = self.chars[self.pos]; 5922 self.pos += 1; 5923 match esc { 5924 b'"' => s.push('"'), 5925 b'\\' => s.push('\\'), 5926 b'/' => s.push('/'), 5927 b'b' => s.push('\u{0008}'), 5928 b'f' => s.push('\u{000C}'), 5929 b'n' => s.push('\n'), 5930 b'r' => s.push('\r'), 5931 b't' => s.push('\t'), 5932 b'u' => { 5933 if self.pos + 4 > self.chars.len() { 5934 return Err(RuntimeError::syntax_error("Invalid unicode escape")); 5935 } 5936 let hex = std::str::from_utf8(&self.chars[self.pos..self.pos + 4]) 5937 .map_err(|_| RuntimeError::syntax_error("Invalid unicode escape"))?; 5938 self.pos += 4; 5939 let code = u32::from_str_radix(hex, 16) 5940 .map_err(|_| RuntimeError::syntax_error("Invalid unicode escape"))?; 5941 if let Some(c) = char::from_u32(code) { 5942 s.push(c); 5943 } else { 5944 return Err(RuntimeError::syntax_error("Invalid unicode code point")); 5945 } 5946 } 5947 _ => { 5948 return Err(RuntimeError::syntax_error(format!( 5949 "Invalid escape character '\\{}'", 5950 esc as char 5951 ))); 5952 } 5953 } 5954 } else { 5955 s.push(ch as char); 5956 } 5957 } 5958 Err(RuntimeError::syntax_error("Unterminated string in JSON")) 5959 } 5960 5961 fn read_number(&mut self) -> Result<Option<JsonToken>, RuntimeError> { 5962 let start = self.pos; 5963 if self.pos < self.chars.len() && self.chars[self.pos] == b'-' { 5964 self.pos += 1; 5965 } 5966 while self.pos < self.chars.len() && self.chars[self.pos].is_ascii_digit() { 5967 self.pos += 1; 5968 } 5969 if self.pos < self.chars.len() && self.chars[self.pos] == b'.' { 5970 self.pos += 1; 5971 while self.pos < self.chars.len() && self.chars[self.pos].is_ascii_digit() { 5972 self.pos += 1; 5973 } 5974 } 5975 if self.pos < self.chars.len() 5976 && (self.chars[self.pos] == b'e' || self.chars[self.pos] == b'E') 5977 { 5978 self.pos += 1; 5979 if self.pos < self.chars.len() 5980 && (self.chars[self.pos] == b'+' || self.chars[self.pos] == b'-') 5981 { 5982 self.pos += 1; 5983 } 5984 while self.pos < self.chars.len() && self.chars[self.pos].is_ascii_digit() { 5985 self.pos += 1; 5986 } 5987 } 5988 let num_str = std::str::from_utf8(&self.chars[start..self.pos]) 5989 .map_err(|_| RuntimeError::syntax_error("Invalid number in JSON"))?; 5990 let n: f64 = num_str 5991 .parse() 5992 .map_err(|_| RuntimeError::syntax_error("Invalid number in JSON"))?; 5993 Ok(Some(JsonToken::Number(n))) 5994 } 5995 5996 fn read_keyword( 5997 &mut self, 5998 keyword: &str, 5999 token: JsonToken, 6000 ) -> Result<Option<JsonToken>, RuntimeError> { 6001 let end = self.pos + keyword.len(); 6002 if end <= self.chars.len() && &self.chars[self.pos..end] == keyword.as_bytes() { 6003 self.pos = end; 6004 Ok(Some(token)) 6005 } else { 6006 Err(RuntimeError::syntax_error(format!( 6007 "Unexpected token in JSON at position {}", 6008 self.pos 6009 ))) 6010 } 6011 } 6012} 6013 6014struct JsonParser<'a> { 6015 lexer: JsonLexer<'a>, 6016 current: Option<JsonToken>, 6017} 6018 6019impl<'a> JsonParser<'a> { 6020 fn new(input: &'a str) -> Result<Self, RuntimeError> { 6021 let mut lexer = JsonLexer::new(input); 6022 let current = lexer.next_token()?; 6023 Ok(Self { lexer, current }) 6024 } 6025 6026 fn advance(&mut self) -> Result<(), RuntimeError> { 6027 self.current = self.lexer.next_token()?; 6028 Ok(()) 6029 } 6030 6031 fn parse_value(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { 6032 match self.current.take() { 6033 Some(JsonToken::Null) => { 6034 self.advance()?; 6035 Ok(Value::Null) 6036 } 6037 Some(JsonToken::True) => { 6038 self.advance()?; 6039 Ok(Value::Boolean(true)) 6040 } 6041 Some(JsonToken::False) => { 6042 self.advance()?; 6043 Ok(Value::Boolean(false)) 6044 } 6045 Some(JsonToken::Number(n)) => { 6046 self.advance()?; 6047 Ok(Value::Number(n)) 6048 } 6049 Some(JsonToken::String(s)) => { 6050 self.advance()?; 6051 Ok(Value::String(s)) 6052 } 6053 Some(JsonToken::LBracket) => self.parse_array(gc), 6054 Some(JsonToken::LBrace) => self.parse_object(gc), 6055 Some(other) => { 6056 // Put it back for error context. 6057 self.current = Some(other); 6058 Err(RuntimeError::syntax_error("Unexpected token in JSON")) 6059 } 6060 None => Err(RuntimeError::syntax_error("Unexpected end of JSON input")), 6061 } 6062 } 6063 6064 fn parse_array(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { 6065 // Current token was LBracket, already consumed via take(). 6066 self.advance()?; // move past '[' 6067 let mut items: Vec<Value> = Vec::new(); 6068 if self.current == Some(JsonToken::RBracket) { 6069 self.advance()?; 6070 let mut obj = ObjectData::new(); 6071 obj.properties.insert( 6072 "length".to_string(), 6073 Property { 6074 value: Value::Number(0.0), 6075 writable: true, 6076 enumerable: false, 6077 configurable: false, 6078 }, 6079 ); 6080 return Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))); 6081 } 6082 loop { 6083 let val = self.parse_value(gc)?; 6084 items.push(val); 6085 match &self.current { 6086 Some(JsonToken::Comma) => { 6087 self.advance()?; 6088 } 6089 Some(JsonToken::RBracket) => { 6090 self.advance()?; 6091 break; 6092 } 6093 _ => { 6094 return Err(RuntimeError::syntax_error( 6095 "Expected ',' or ']' in JSON array", 6096 )); 6097 } 6098 } 6099 } 6100 let mut obj = ObjectData::new(); 6101 for (i, v) in items.iter().enumerate() { 6102 obj.properties 6103 .insert(i.to_string(), Property::data(v.clone())); 6104 } 6105 obj.properties.insert( 6106 "length".to_string(), 6107 Property { 6108 value: Value::Number(items.len() as f64), 6109 writable: true, 6110 enumerable: false, 6111 configurable: false, 6112 }, 6113 ); 6114 Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))) 6115 } 6116 6117 fn parse_object(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { 6118 self.advance()?; // move past '{' 6119 let mut obj = ObjectData::new(); 6120 if self.current == Some(JsonToken::RBrace) { 6121 self.advance()?; 6122 return Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))); 6123 } 6124 loop { 6125 let key = match self.current.take() { 6126 Some(JsonToken::String(s)) => s, 6127 _ => { 6128 return Err(RuntimeError::syntax_error( 6129 "Expected string key in JSON object", 6130 )); 6131 } 6132 }; 6133 self.advance()?; 6134 if self.current != Some(JsonToken::Colon) { 6135 return Err(RuntimeError::syntax_error( 6136 "Expected ':' after key in JSON object", 6137 )); 6138 } 6139 self.advance()?; 6140 let val = self.parse_value(gc)?; 6141 obj.properties.insert(key, Property::data(val)); 6142 match &self.current { 6143 Some(JsonToken::Comma) => { 6144 self.advance()?; 6145 } 6146 Some(JsonToken::RBrace) => { 6147 self.advance()?; 6148 break; 6149 } 6150 _ => { 6151 return Err(RuntimeError::syntax_error( 6152 "Expected ',' or '}}' in JSON object", 6153 )); 6154 } 6155 } 6156 } 6157 Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))) 6158 } 6159} 6160 6161/// Public wrapper for `json_parse` used by the fetch module. 6162pub fn json_parse_pub(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6163 json_parse(args, ctx) 6164} 6165 6166fn json_parse(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6167 let text = args 6168 .first() 6169 .map(|v| v.to_js_string(ctx.gc)) 6170 .unwrap_or_default(); 6171 let mut parser = JsonParser::new(&text)?; 6172 let value = parser.parse_value(ctx.gc)?; 6173 // Ensure no trailing content. 6174 if parser.current.is_some() { 6175 return Err(RuntimeError::syntax_error( 6176 "Unexpected token after JSON value", 6177 )); 6178 } 6179 // TODO: reviver support — skip for now. 6180 Ok(value) 6181} 6182 6183// ── JSON.stringify ─────────────────────────────────────────── 6184 6185fn json_stringify(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6186 let value = args.first().cloned().unwrap_or(Value::Undefined); 6187 let indent = match args.get(2) { 6188 Some(Value::Number(n)) => { 6189 let n = *n as usize; 6190 if n > 0 { 6191 " ".repeat(n.min(10)) 6192 } else { 6193 String::new() 6194 } 6195 } 6196 Some(Value::String(s)) => s[..s.len().min(10)].to_string(), 6197 _ => String::new(), 6198 }; 6199 // Track visited objects for circular reference detection. 6200 let mut visited: Vec<GcRef> = Vec::new(); 6201 let result = json_stringify_value(&value, ctx.gc, &indent, "", &mut visited)?; 6202 match result { 6203 Some(s) => Ok(Value::String(s)), 6204 None => Ok(Value::Undefined), 6205 } 6206} 6207 6208fn json_stringify_value( 6209 value: &Value, 6210 gc: &Gc<HeapObject>, 6211 indent: &str, 6212 current_indent: &str, 6213 visited: &mut Vec<GcRef>, 6214) -> Result<Option<String>, RuntimeError> { 6215 match value { 6216 Value::Null => Ok(Some("null".to_string())), 6217 Value::Boolean(true) => Ok(Some("true".to_string())), 6218 Value::Boolean(false) => Ok(Some("false".to_string())), 6219 Value::Number(n) => { 6220 if n.is_nan() || n.is_infinite() { 6221 Ok(Some("null".to_string())) 6222 } else { 6223 Ok(Some(js_number_to_string(*n))) 6224 } 6225 } 6226 Value::String(s) => Ok(Some(json_quote_string(s))), 6227 Value::Undefined | Value::Function(_) => Ok(None), 6228 Value::Object(gc_ref) => { 6229 // Circular reference check. 6230 if visited.contains(gc_ref) { 6231 return Err(RuntimeError::type_error( 6232 "Converting circular structure to JSON", 6233 )); 6234 } 6235 visited.push(*gc_ref); 6236 6237 let result = match gc.get(*gc_ref) { 6238 Some(HeapObject::Object(data)) => { 6239 // Check for toJSON method. 6240 if let Some(prop) = data.properties.get("toJSON") { 6241 if matches!(prop.value, Value::Function(_)) { 6242 // We can't call JS functions from here easily, 6243 // but for Date objects we recognize the __date_ms__ pattern. 6244 if let Some(date_prop) = data.properties.get("__date_ms__") { 6245 let ms = date_prop.value.to_number(); 6246 if ms.is_nan() { 6247 visited.pop(); 6248 return Ok(Some("null".to_string())); 6249 } 6250 let (y, m, d, h, min, s, ms_part, _) = ms_to_utc_components(ms); 6251 visited.pop(); 6252 return Ok(Some(format!( 6253 "\"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z\"", 6254 y, 6255 m + 1, 6256 d, 6257 h, 6258 min, 6259 s, 6260 ms_part 6261 ))); 6262 } 6263 } 6264 } 6265 if array_length_exists(gc, *gc_ref) { 6266 json_stringify_array(gc, *gc_ref, indent, current_indent, visited) 6267 } else { 6268 json_stringify_object(gc, data, indent, current_indent, visited) 6269 } 6270 } 6271 _ => Ok(Some("{}".to_string())), 6272 }; 6273 6274 visited.pop(); 6275 result 6276 } 6277 } 6278} 6279 6280fn json_stringify_array( 6281 gc: &Gc<HeapObject>, 6282 arr: GcRef, 6283 indent: &str, 6284 current_indent: &str, 6285 visited: &mut Vec<GcRef>, 6286) -> Result<Option<String>, RuntimeError> { 6287 let len = array_length(gc, arr); 6288 if len == 0 { 6289 return Ok(Some("[]".to_string())); 6290 } 6291 let has_indent = !indent.is_empty(); 6292 let next_indent = if has_indent { 6293 format!("{}{}", current_indent, indent) 6294 } else { 6295 String::new() 6296 }; 6297 let mut parts: Vec<String> = Vec::new(); 6298 for i in 0..len { 6299 let val = array_get(gc, arr, i); 6300 match json_stringify_value(&val, gc, indent, &next_indent, visited)? { 6301 Some(s) => parts.push(s), 6302 None => parts.push("null".to_string()), 6303 } 6304 } 6305 if has_indent { 6306 Ok(Some(format!( 6307 "[\n{}{}\n{}]", 6308 next_indent, 6309 parts.join(&format!(",\n{}", next_indent)), 6310 current_indent 6311 ))) 6312 } else { 6313 Ok(Some(format!("[{}]", parts.join(",")))) 6314 } 6315} 6316 6317fn json_stringify_object( 6318 gc: &Gc<HeapObject>, 6319 data: &ObjectData, 6320 indent: &str, 6321 current_indent: &str, 6322 visited: &mut Vec<GcRef>, 6323) -> Result<Option<String>, RuntimeError> { 6324 let has_indent = !indent.is_empty(); 6325 let next_indent = if has_indent { 6326 format!("{}{}", current_indent, indent) 6327 } else { 6328 String::new() 6329 }; 6330 let mut parts: Vec<String> = Vec::new(); 6331 // Collect and sort keys for deterministic output. 6332 let mut keys: Vec<&String> = data.properties.keys().collect(); 6333 keys.sort(); 6334 for key in keys { 6335 let prop = &data.properties[key]; 6336 if !prop.enumerable { 6337 continue; 6338 } 6339 if let Some(val_str) = json_stringify_value(&prop.value, gc, indent, &next_indent, visited)? 6340 { 6341 if has_indent { 6342 parts.push(format!("{}: {}", json_quote_string(key), val_str)); 6343 } else { 6344 parts.push(format!("{}:{}", json_quote_string(key), val_str)); 6345 } 6346 } 6347 } 6348 if parts.is_empty() { 6349 return Ok(Some("{}".to_string())); 6350 } 6351 if has_indent { 6352 Ok(Some(format!( 6353 "{{\n{}{}\n{}}}", 6354 next_indent, 6355 parts.join(&format!(",\n{}", next_indent)), 6356 current_indent 6357 ))) 6358 } else { 6359 Ok(Some(format!("{{{}}}", parts.join(",")))) 6360 } 6361} 6362 6363fn json_quote_string(s: &str) -> String { 6364 let mut out = String::with_capacity(s.len() + 2); 6365 out.push('"'); 6366 for ch in s.chars() { 6367 match ch { 6368 '"' => out.push_str("\\\""), 6369 '\\' => out.push_str("\\\\"), 6370 '\n' => out.push_str("\\n"), 6371 '\r' => out.push_str("\\r"), 6372 '\t' => out.push_str("\\t"), 6373 '\u{0008}' => out.push_str("\\b"), 6374 '\u{000C}' => out.push_str("\\f"), 6375 c if c < '\u{0020}' => { 6376 out.push_str(&format!("\\u{:04x}", c as u32)); 6377 } 6378 c => out.push(c), 6379 } 6380 } 6381 out.push('"'); 6382 out 6383} 6384 6385// ── Console object ────────────────────────────────────────── 6386 6387fn init_console_object(vm: &mut Vm) { 6388 let mut data = ObjectData::new(); 6389 if let Some(proto) = vm.object_prototype { 6390 data.prototype = Some(proto); 6391 } 6392 let console_ref = vm.gc.alloc(HeapObject::Object(data)); 6393 6394 let methods: &[NativeMethod] = &[ 6395 ("log", console_log), 6396 ("info", console_log), 6397 ("debug", console_log), 6398 ("error", console_error), 6399 ("warn", console_warn), 6400 ]; 6401 for &(name, callback) in methods { 6402 let func = make_native(&mut vm.gc, name, callback); 6403 set_builtin_prop(&mut vm.gc, console_ref, name, Value::Function(func)); 6404 } 6405 6406 vm.set_global("console", Value::Object(console_ref)); 6407} 6408 6409/// Format a JS value for console output with richer detail than `to_js_string`. 6410/// Arrays show their contents and objects show their properties. 6411fn console_format_value( 6412 value: &Value, 6413 gc: &Gc<HeapObject>, 6414 depth: usize, 6415 seen: &mut HashSet<GcRef>, 6416) -> String { 6417 const MAX_DEPTH: usize = 4; 6418 match value { 6419 Value::Undefined => "undefined".to_string(), 6420 Value::Null => "null".to_string(), 6421 Value::Boolean(b) => b.to_string(), 6422 Value::Number(n) => js_number_to_string(*n), 6423 Value::String(s) => s.clone(), 6424 Value::Function(gc_ref) => gc 6425 .get(*gc_ref) 6426 .and_then(|obj| match obj { 6427 HeapObject::Function(f) => Some(format!("[Function: {}]", f.name)), 6428 _ => None, 6429 }) 6430 .unwrap_or_else(|| "[Function]".to_string()), 6431 Value::Object(gc_ref) => { 6432 if depth > MAX_DEPTH || seen.contains(gc_ref) { 6433 return "[Object]".to_string(); 6434 } 6435 seen.insert(*gc_ref); 6436 let result = match gc.get(*gc_ref) { 6437 Some(HeapObject::Object(obj_data)) => { 6438 // Check if it's an array (has a "length" property). 6439 if obj_data.properties.contains_key("length") { 6440 format_array(obj_data, gc, depth, seen) 6441 } else { 6442 format_object(obj_data, gc, depth, seen) 6443 } 6444 } 6445 _ => "[Object]".to_string(), 6446 }; 6447 seen.remove(gc_ref); 6448 result 6449 } 6450 } 6451} 6452 6453fn format_array( 6454 data: &ObjectData, 6455 gc: &Gc<HeapObject>, 6456 depth: usize, 6457 seen: &mut HashSet<GcRef>, 6458) -> String { 6459 let len = data 6460 .properties 6461 .get("length") 6462 .map(|p| p.value.to_number() as usize) 6463 .unwrap_or(0); 6464 let mut parts = Vec::with_capacity(len); 6465 for i in 0..len { 6466 let val = data 6467 .properties 6468 .get(&i.to_string()) 6469 .map(|p| &p.value) 6470 .unwrap_or(&Value::Undefined); 6471 parts.push(console_format_value(val, gc, depth + 1, seen)); 6472 } 6473 format!("[ {} ]", parts.join(", ")) 6474} 6475 6476fn format_object( 6477 data: &ObjectData, 6478 gc: &Gc<HeapObject>, 6479 depth: usize, 6480 seen: &mut HashSet<GcRef>, 6481) -> String { 6482 if data.properties.is_empty() { 6483 return "{}".to_string(); 6484 } 6485 let mut parts = Vec::new(); 6486 for (key, prop) in &data.properties { 6487 let val_str = console_format_value(&prop.value, gc, depth + 1, seen); 6488 parts.push(format!("{}: {}", key, val_str)); 6489 } 6490 parts.sort(); 6491 format!("{{ {} }}", parts.join(", ")) 6492} 6493 6494/// Format all arguments for a console method, separated by spaces. 6495fn console_format_args(args: &[Value], gc: &Gc<HeapObject>) -> String { 6496 let mut seen = HashSet::new(); 6497 args.iter() 6498 .map(|v| console_format_value(v, gc, 0, &mut seen)) 6499 .collect::<Vec<_>>() 6500 .join(" ") 6501} 6502 6503fn console_log(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6504 let msg = console_format_args(args, ctx.gc); 6505 ctx.console_output.log(&msg); 6506 Ok(Value::Undefined) 6507} 6508 6509fn console_error(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6510 let msg = console_format_args(args, ctx.gc); 6511 ctx.console_output.error(&msg); 6512 Ok(Value::Undefined) 6513} 6514 6515fn console_warn(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6516 let msg = console_format_args(args, ctx.gc); 6517 ctx.console_output.warn(&msg); 6518 Ok(Value::Undefined) 6519} 6520 6521// ── Global utility functions ───────────────────────────────── 6522 6523fn init_global_functions(vm: &mut Vm) { 6524 vm.define_native("parseInt", parse_int); 6525 vm.define_native("parseFloat", parse_float); 6526 vm.define_native("isNaN", is_nan); 6527 vm.define_native("isFinite", is_finite); 6528 6529 // Timer APIs. 6530 vm.define_native("setTimeout", crate::timers::set_timeout); 6531 vm.define_native("clearTimeout", crate::timers::clear_timeout); 6532 vm.define_native("setInterval", crate::timers::set_interval); 6533 vm.define_native("clearInterval", crate::timers::clear_interval); 6534 vm.define_native( 6535 "requestAnimationFrame", 6536 crate::timers::request_animation_frame, 6537 ); 6538 vm.define_native( 6539 "cancelAnimationFrame", 6540 crate::timers::cancel_animation_frame, 6541 ); 6542} 6543 6544fn parse_int(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6545 let s = args 6546 .first() 6547 .map(|v| v.to_js_string(ctx.gc)) 6548 .unwrap_or_default(); 6549 let radix = args.get(1).map(|v| v.to_number() as u32).unwrap_or(0); 6550 let s = s.trim(); 6551 if s.is_empty() { 6552 return Ok(Value::Number(f64::NAN)); 6553 } 6554 let (neg, s) = if let Some(rest) = s.strip_prefix('-') { 6555 (true, rest) 6556 } else if let Some(rest) = s.strip_prefix('+') { 6557 (false, rest) 6558 } else { 6559 (false, s) 6560 }; 6561 // Auto-detect hex. 6562 let (radix, s) = if radix == 0 || radix == 16 { 6563 if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { 6564 (16, rest) 6565 } else { 6566 (if radix == 0 { 10 } else { radix }, s) 6567 } 6568 } else { 6569 (radix, s) 6570 }; 6571 if !(2..=36).contains(&radix) { 6572 return Ok(Value::Number(f64::NAN)); 6573 } 6574 // Parse digits until invalid. 6575 let mut result: f64 = 0.0; 6576 let mut found = false; 6577 for c in s.chars() { 6578 let digit = match c.to_digit(radix) { 6579 Some(d) => d, 6580 None => break, 6581 }; 6582 result = result * radix as f64 + digit as f64; 6583 found = true; 6584 } 6585 if !found { 6586 return Ok(Value::Number(f64::NAN)); 6587 } 6588 if neg { 6589 result = -result; 6590 } 6591 Ok(Value::Number(result)) 6592} 6593 6594fn parse_float(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6595 let s = args 6596 .first() 6597 .map(|v| v.to_js_string(ctx.gc)) 6598 .unwrap_or_default(); 6599 let s = s.trim(); 6600 match s.parse::<f64>() { 6601 Ok(n) => Ok(Value::Number(n)), 6602 Err(_) => Ok(Value::Number(f64::NAN)), 6603 } 6604} 6605 6606fn is_nan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6607 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 6608 Ok(Value::Boolean(n.is_nan())) 6609} 6610 6611fn is_finite(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 6612 let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 6613 Ok(Value::Boolean(n.is_finite())) 6614} 6615 6616// ── Strict equality (for Array.indexOf etc.) ───────────────── 6617 6618fn strict_eq_values(a: &Value, b: &Value) -> bool { 6619 match (a, b) { 6620 (Value::Undefined, Value::Undefined) => true, 6621 (Value::Null, Value::Null) => true, 6622 (Value::Number(x), Value::Number(y)) => x == y, 6623 (Value::String(x), Value::String(y)) => x == y, 6624 (Value::Boolean(x), Value::Boolean(y)) => x == y, 6625 (Value::Object(x), Value::Object(y)) => x == y, 6626 (Value::Function(x), Value::Function(y)) => x == y, 6627 _ => false, 6628 } 6629} 6630 6631/// SameValueZero (like === but NaN === NaN is true). 6632fn same_value_zero(a: &Value, b: &Value) -> bool { 6633 match (a, b) { 6634 (Value::Number(x), Value::Number(y)) => { 6635 if x.is_nan() && y.is_nan() { 6636 return true; 6637 } 6638 x == y 6639 } 6640 _ => strict_eq_values(a, b), 6641 } 6642} 6643 6644// ── JS preamble for callback-based methods ─────────────────── 6645 6646fn init_js_preamble(vm: &mut Vm) { 6647 // NOTE: All preamble methods capture `this` into a local `_t` variable 6648 // because nested method calls (e.g. result.push()) clobber the global `this`. 6649 let preamble = r#" 6650Array.prototype.forEach = function(cb) { 6651 var _t = this; 6652 for (var i = 0; i < _t.length; i = i + 1) { 6653 cb(_t[i], i, _t); 6654 } 6655}; 6656Array.prototype.map = function(cb) { 6657 var _t = this; 6658 var result = []; 6659 for (var i = 0; i < _t.length; i = i + 1) { 6660 result.push(cb(_t[i], i, _t)); 6661 } 6662 return result; 6663}; 6664Array.prototype.filter = function(cb) { 6665 var _t = this; 6666 var result = []; 6667 for (var i = 0; i < _t.length; i = i + 1) { 6668 if (cb(_t[i], i, _t)) { 6669 result.push(_t[i]); 6670 } 6671 } 6672 return result; 6673}; 6674Array.prototype.reduce = function(cb, init) { 6675 var _t = this; 6676 var acc = init; 6677 var start = 0; 6678 if (acc === undefined) { 6679 acc = _t[0]; 6680 start = 1; 6681 } 6682 for (var i = start; i < _t.length; i = i + 1) { 6683 acc = cb(acc, _t[i], i, _t); 6684 } 6685 return acc; 6686}; 6687Array.prototype.reduceRight = function(cb, init) { 6688 var _t = this; 6689 var acc = init; 6690 var start = _t.length - 1; 6691 if (acc === undefined) { 6692 acc = _t[start]; 6693 start = start - 1; 6694 } 6695 for (var i = start; i >= 0; i = i - 1) { 6696 acc = cb(acc, _t[i], i, _t); 6697 } 6698 return acc; 6699}; 6700Array.prototype.find = function(cb) { 6701 var _t = this; 6702 for (var i = 0; i < _t.length; i = i + 1) { 6703 if (cb(_t[i], i, _t)) { return _t[i]; } 6704 } 6705 return undefined; 6706}; 6707Array.prototype.findIndex = function(cb) { 6708 var _t = this; 6709 for (var i = 0; i < _t.length; i = i + 1) { 6710 if (cb(_t[i], i, _t)) { return i; } 6711 } 6712 return -1; 6713}; 6714Array.prototype.some = function(cb) { 6715 var _t = this; 6716 for (var i = 0; i < _t.length; i = i + 1) { 6717 if (cb(_t[i], i, _t)) { return true; } 6718 } 6719 return false; 6720}; 6721Array.prototype.every = function(cb) { 6722 var _t = this; 6723 for (var i = 0; i < _t.length; i = i + 1) { 6724 if (!cb(_t[i], i, _t)) { return false; } 6725 } 6726 return true; 6727}; 6728Array.prototype.sort = function(cmp) { 6729 var _t = this; 6730 var len = _t.length; 6731 for (var i = 0; i < len; i = i + 1) { 6732 for (var j = 0; j < len - i - 1; j = j + 1) { 6733 var a = _t[j]; 6734 var b = _t[j + 1]; 6735 var order; 6736 if (cmp) { 6737 order = cmp(a, b); 6738 } else { 6739 var sa = "" + a; 6740 var sb = "" + b; 6741 if (sa > sb) { order = 1; } 6742 else if (sa < sb) { order = -1; } 6743 else { order = 0; } 6744 } 6745 if (order > 0) { 6746 _t[j] = b; 6747 _t[j + 1] = a; 6748 } 6749 } 6750 } 6751 return _t; 6752}; 6753Array.prototype.flat = function(depth) { 6754 var _t = this; 6755 if (depth === undefined) { depth = 1; } 6756 var result = []; 6757 for (var i = 0; i < _t.length; i = i + 1) { 6758 var elem = _t[i]; 6759 if (depth > 0 && Array.isArray(elem)) { 6760 var sub = elem.flat(depth - 1); 6761 for (var j = 0; j < sub.length; j = j + 1) { 6762 result.push(sub[j]); 6763 } 6764 } else { 6765 result.push(elem); 6766 } 6767 } 6768 return result; 6769}; 6770Array.prototype.flatMap = function(cb) { 6771 var _t = this; 6772 return _t.map(cb).flat(); 6773}; 6774"#; 6775 // Compile and execute the preamble. 6776 let ast = match crate::parser::Parser::parse(preamble) { 6777 Ok(ast) => ast, 6778 Err(_) => return, // Silently skip if preamble fails to parse. 6779 }; 6780 let func = match crate::compiler::compile(&ast) { 6781 Ok(func) => func, 6782 Err(_) => return, 6783 }; 6784 let _ = vm.execute(&func); 6785 6786 // Promise constructor and static methods (defined in JS so the executor 6787 // callback can be called as bytecode, not just native functions). 6788 let promise_preamble = r#" 6789function Promise(executor) { 6790 var p = __Promise_create(); 6791 var alreadyResolved = false; 6792 function resolve(value) { 6793 if (!alreadyResolved) { 6794 alreadyResolved = true; 6795 __Promise_resolve(p, value); 6796 } 6797 } 6798 function reject(reason) { 6799 if (!alreadyResolved) { 6800 alreadyResolved = true; 6801 __Promise_reject(p, reason); 6802 } 6803 } 6804 try { 6805 executor(resolve, reject); 6806 } catch (e) { 6807 reject(e); 6808 } 6809 return p; 6810} 6811Promise.resolve = __Promise_static_resolve; 6812Promise.reject = __Promise_static_reject; 6813Promise.all = __Promise_static_all; 6814Promise.race = __Promise_static_race; 6815Promise.allSettled = __Promise_static_allSettled; 6816Promise.any = __Promise_static_any; 6817"#; 6818 let ast = match crate::parser::Parser::parse(promise_preamble) { 6819 Ok(ast) => ast, 6820 Err(_) => return, 6821 }; 6822 let func = match crate::compiler::compile(&ast) { 6823 Ok(func) => func, 6824 Err(_) => return, 6825 }; 6826 let _ = vm.execute(&func); 6827}