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