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