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