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