we (web engine): Experimental web browser project to understand the limits of Claude
1//! Register-based JavaScript virtual machine.
2//!
3//! Executes bytecode produced by the compiler. Each call frame has a register
4//! file, and the VM dispatches instructions in a loop. Heap-allocated objects
5//! (plain objects and functions) are managed by a tri-color mark-and-sweep
6//! garbage collector.
7
8use crate::bytecode::{Constant, Function, Op, Reg};
9use crate::gc::{Gc, GcRef, Traceable};
10use std::cell::RefCell;
11use std::collections::HashMap;
12use std::fmt;
13use std::rc::Rc;
14use we_dom::Document;
15
16// ── Heap objects (GC-managed) ────────────────────────────────
17
18/// A GC-managed heap object: a plain object, a function, a closure cell, or a generator.
19pub enum HeapObject {
20 Object(ObjectData),
21 Function(Box<FunctionData>),
22 /// A mutable cell holding one Value — used for closure-captured variables.
23 Cell(Value),
24 /// A suspended generator function instance.
25 Generator(Box<GeneratorData>),
26}
27
28/// State of a generator object.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum GeneratorState {
31 /// Created but next() not yet called.
32 NotStarted,
33 /// Suspended at a yield point.
34 Suspended,
35 /// Currently executing (re-entrancy guard).
36 Executing,
37 /// Completed (returned or threw).
38 Completed,
39}
40
41/// Data for a suspended generator function.
42pub struct GeneratorData {
43 pub state: GeneratorState,
44 /// The generator function's bytecode.
45 pub func: Function,
46 /// Captured upvalues.
47 pub upvalues: Vec<GcRef>,
48 /// Saved register file for this generator's frame.
49 pub registers: Vec<Value>,
50 /// Saved instruction pointer (where to resume).
51 pub ip: usize,
52 /// The GcRef of the result prototype (for {value, done} objects).
53 pub prototype: Option<GcRef>,
54 /// Saved exception handlers (for try/catch across await/yield points).
55 pub exception_handlers: Vec<(usize, Reg)>,
56}
57
58impl Traceable for HeapObject {
59 fn trace(&self, visitor: &mut dyn FnMut(GcRef)) {
60 match self {
61 HeapObject::Object(data) => {
62 for prop in data.properties.values() {
63 if let Some(r) = prop.value.gc_ref() {
64 visitor(r);
65 }
66 }
67 if let Some(proto) = data.prototype {
68 visitor(proto);
69 }
70 }
71 HeapObject::Function(fdata) => {
72 if let Some(proto) = fdata.prototype_obj {
73 visitor(proto);
74 }
75 for prop in fdata.properties.values() {
76 if let Some(r) = prop.value.gc_ref() {
77 visitor(r);
78 }
79 }
80 for &uv in &fdata.upvalues {
81 visitor(uv);
82 }
83 }
84 HeapObject::Cell(val) => {
85 if let Some(r) = val.gc_ref() {
86 visitor(r);
87 }
88 }
89 HeapObject::Generator(gen) => {
90 for &uv in &gen.upvalues {
91 visitor(uv);
92 }
93 for val in &gen.registers {
94 if let Some(r) = val.gc_ref() {
95 visitor(r);
96 }
97 }
98 if let Some(proto) = gen.prototype {
99 visitor(proto);
100 }
101 }
102 }
103 }
104}
105
106/// A property descriptor stored in an object's property map.
107#[derive(Clone)]
108pub struct Property {
109 /// The property's value (for data properties).
110 pub value: Value,
111 /// Whether the value can be changed via assignment.
112 pub writable: bool,
113 /// Whether the property shows up in `for...in` and `Object.keys`.
114 pub enumerable: bool,
115 /// Whether the property can be deleted or its attributes changed.
116 pub configurable: bool,
117}
118
119impl Property {
120 /// Create a new data property with all flags set to true (the JS default for
121 /// properties created by assignment).
122 pub fn data(value: Value) -> Self {
123 Self {
124 value,
125 writable: true,
126 enumerable: true,
127 configurable: true,
128 }
129 }
130
131 /// Create a non-enumerable, non-configurable property (e.g. built-in methods).
132 pub fn builtin(value: Value) -> Self {
133 Self {
134 value,
135 writable: true,
136 enumerable: false,
137 configurable: false,
138 }
139 }
140}
141
142/// A JS plain object (properties stored as a HashMap with descriptors).
143pub struct ObjectData {
144 pub properties: HashMap<String, Property>,
145 pub prototype: Option<GcRef>,
146 /// Whether new properties can be added (Object.preventExtensions).
147 pub extensible: bool,
148}
149
150impl ObjectData {
151 pub fn new() -> Self {
152 Self {
153 properties: HashMap::new(),
154 prototype: None,
155 extensible: true,
156 }
157 }
158}
159
160impl Default for ObjectData {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166/// A runtime function value: either bytecode or native.
167///
168/// In JavaScript, functions are objects and can have arbitrary properties
169/// (e.g. `assert.sameValue = function() {}`).
170pub struct FunctionData {
171 pub name: String,
172 pub kind: FunctionKind,
173 /// The `.prototype` property object (for use as a constructor with `instanceof`).
174 pub prototype_obj: Option<GcRef>,
175 /// Arbitrary properties set on this function (functions are objects in JS).
176 pub properties: HashMap<String, Property>,
177 /// Captured upvalue cells (GcRefs to HeapObject::Cell values).
178 pub upvalues: Vec<GcRef>,
179}
180
181#[derive(Clone)]
182pub enum FunctionKind {
183 /// Bytecode function.
184 Bytecode(BytecodeFunc),
185 /// Native (Rust) function.
186 Native(NativeFunc),
187}
188
189#[derive(Clone)]
190pub struct BytecodeFunc {
191 pub func: Function,
192}
193
194/// A native function callable from JS.
195#[derive(Clone)]
196pub struct NativeFunc {
197 pub callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>,
198}
199
200/// Trait for console output. Allows redirecting console output to a dev tools
201/// panel or capturing it in tests. The default implementation writes to
202/// stdout/stderr.
203pub trait ConsoleOutput {
204 fn log(&self, message: &str);
205 fn error(&self, message: &str);
206 fn warn(&self, message: &str);
207}
208
209/// Default console output that writes to stdout/stderr.
210pub struct StdConsoleOutput;
211
212impl ConsoleOutput for StdConsoleOutput {
213 fn log(&self, message: &str) {
214 println!("{}", message);
215 }
216 fn error(&self, message: &str) {
217 eprintln!("{}", message);
218 }
219 fn warn(&self, message: &str) {
220 eprintln!("{}", message);
221 }
222}
223
224/// A single event listener registered on a DOM node.
225pub struct EventListener {
226 pub event_type: String,
227 pub callback: GcRef,
228 pub capture: bool,
229 pub once: bool,
230}
231
232/// Bridge between JS and the DOM. Holds a shared document and a cache
233/// mapping `NodeId` indices to their JS wrapper `GcRef` so that the same
234/// DOM node always returns the same JS object (identity).
235pub struct DomBridge {
236 pub document: RefCell<Document>,
237 pub node_wrappers: RefCell<HashMap<usize, GcRef>>,
238 /// Event listeners keyed by NodeId index.
239 pub event_listeners: RefCell<HashMap<usize, Vec<EventListener>>>,
240}
241
242/// Context passed to native functions, providing GC access and `this` binding.
243pub struct NativeContext<'a> {
244 pub gc: &'a mut Gc<HeapObject>,
245 pub this: Value,
246 pub console_output: &'a dyn ConsoleOutput,
247 pub dom_bridge: Option<&'a DomBridge>,
248}
249
250// ── JS Value ──────────────────────────────────────────────────
251
252/// A JavaScript runtime value.
253///
254/// Primitive types (Undefined, Null, Boolean, Number, String) are stored
255/// inline. Objects and Functions are heap-allocated via the GC and referenced
256/// by a [`GcRef`] handle.
257#[derive(Clone)]
258pub enum Value {
259 Undefined,
260 Null,
261 Boolean(bool),
262 Number(f64),
263 String(String),
264 /// A GC-managed plain object.
265 Object(GcRef),
266 /// A GC-managed function.
267 Function(GcRef),
268}
269
270impl fmt::Debug for Value {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 match self {
273 Value::Undefined => write!(f, "undefined"),
274 Value::Null => write!(f, "null"),
275 Value::Boolean(b) => write!(f, "{b}"),
276 Value::Number(n) => write!(f, "{n}"),
277 Value::String(s) => write!(f, "\"{}\"", s),
278 Value::Object(_) => write!(f, "[object Object]"),
279 Value::Function(_) => write!(f, "[Function]"),
280 }
281 }
282}
283
284impl fmt::Display for Value {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 match self {
287 Value::Undefined => write!(f, "undefined"),
288 Value::Null => write!(f, "null"),
289 Value::Boolean(b) => write!(f, "{b}"),
290 Value::Number(n) => format_number(*n, f),
291 Value::String(s) => write!(f, "{s}"),
292 Value::Object(_) => write!(f, "[object Object]"),
293 Value::Function(_) => write!(f, "function() {{ [native code] }}"),
294 }
295 }
296}
297
298/// Format a number following JS conventions (no trailing .0 for integers).
299fn format_number(n: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 if n.is_nan() {
301 write!(f, "NaN")
302 } else if n.is_infinite() {
303 if n.is_sign_positive() {
304 write!(f, "Infinity")
305 } else {
306 write!(f, "-Infinity")
307 }
308 } else if n == 0.0 {
309 write!(f, "0")
310 } else if n.fract() == 0.0 && n.abs() < 1e20 {
311 write!(f, "{}", n as i64)
312 } else {
313 write!(f, "{n}")
314 }
315}
316
317impl Value {
318 /// Abstract `ToBoolean` (ECMA-262 §7.1.2).
319 pub fn to_boolean(&self) -> bool {
320 match self {
321 Value::Undefined | Value::Null => false,
322 Value::Boolean(b) => *b,
323 Value::Number(n) => *n != 0.0 && !n.is_nan(),
324 Value::String(s) => !s.is_empty(),
325 Value::Object(_) | Value::Function(_) => true,
326 }
327 }
328
329 /// Abstract `ToNumber` (ECMA-262 §7.1.3).
330 pub fn to_number(&self) -> f64 {
331 match self {
332 Value::Undefined => f64::NAN,
333 Value::Null => 0.0,
334 Value::Boolean(true) => 1.0,
335 Value::Boolean(false) => 0.0,
336 Value::Number(n) => *n,
337 Value::String(s) => {
338 let s = s.trim();
339 if s.is_empty() {
340 0.0
341 } else if s == "Infinity" || s == "+Infinity" {
342 f64::INFINITY
343 } else if s == "-Infinity" {
344 f64::NEG_INFINITY
345 } else {
346 s.parse::<f64>().unwrap_or(f64::NAN)
347 }
348 }
349 Value::Object(_) | Value::Function(_) => f64::NAN,
350 }
351 }
352
353 /// Abstract `ToString` (ECMA-262 §7.1.12).
354 ///
355 /// Requires `&Gc` to look up function names for `Value::Function`.
356 pub fn to_js_string(&self, gc: &Gc<HeapObject>) -> String {
357 match self {
358 Value::Undefined => "undefined".to_string(),
359 Value::Null => "null".to_string(),
360 Value::Boolean(true) => "true".to_string(),
361 Value::Boolean(false) => "false".to_string(),
362 Value::Number(n) => js_number_to_string(*n),
363 Value::String(s) => s.clone(),
364 Value::Object(_) => "[object Object]".to_string(),
365 Value::Function(gc_ref) => gc
366 .get(*gc_ref)
367 .and_then(|obj| match obj {
368 HeapObject::Function(f) => {
369 Some(format!("function {}() {{ [native code] }}", f.name))
370 }
371 _ => None,
372 })
373 .unwrap_or_else(|| "function() { [native code] }".to_string()),
374 }
375 }
376
377 /// `typeof` operator result.
378 pub fn type_of(&self) -> &'static str {
379 match self {
380 Value::Undefined => "undefined",
381 Value::Null => "object", // yes, this is the spec
382 Value::Boolean(_) => "boolean",
383 Value::Number(_) => "number",
384 Value::String(_) => "string",
385 Value::Object(_) => "object",
386 Value::Function(_) => "function",
387 }
388 }
389
390 /// Is this value nullish (null or undefined)?
391 pub fn is_nullish(&self) -> bool {
392 matches!(self, Value::Undefined | Value::Null)
393 }
394
395 /// Extract the `GcRef` if this value is an Object or Function.
396 pub fn gc_ref(&self) -> Option<GcRef> {
397 match self {
398 Value::Object(r) | Value::Function(r) => Some(*r),
399 _ => None,
400 }
401 }
402}
403
404/// Format a number as JS would.
405pub(crate) fn js_number_to_string(n: f64) -> String {
406 if n.is_nan() {
407 "NaN".to_string()
408 } else if n.is_infinite() {
409 if n.is_sign_positive() {
410 "Infinity".to_string()
411 } else {
412 "-Infinity".to_string()
413 }
414 } else if n == 0.0 {
415 "0".to_string()
416 } else if n.fract() == 0.0 && n.abs() < 1e20 {
417 format!("{}", n as i64)
418 } else {
419 format!("{n}")
420 }
421}
422
423// ── Runtime errors ────────────────────────────────────────────
424
425/// JavaScript runtime error types.
426#[derive(Debug, Clone)]
427pub struct RuntimeError {
428 pub kind: ErrorKind,
429 pub message: String,
430}
431
432#[derive(Debug, Clone, Copy, PartialEq, Eq)]
433pub enum ErrorKind {
434 TypeError,
435 ReferenceError,
436 RangeError,
437 SyntaxError,
438 Error,
439}
440
441impl fmt::Display for RuntimeError {
442 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443 let name = match self.kind {
444 ErrorKind::TypeError => "TypeError",
445 ErrorKind::ReferenceError => "ReferenceError",
446 ErrorKind::RangeError => "RangeError",
447 ErrorKind::SyntaxError => "SyntaxError",
448 ErrorKind::Error => "Error",
449 };
450 write!(f, "{name}: {}", self.message)
451 }
452}
453
454impl RuntimeError {
455 pub fn type_error(msg: impl Into<String>) -> Self {
456 Self {
457 kind: ErrorKind::TypeError,
458 message: msg.into(),
459 }
460 }
461
462 pub fn reference_error(msg: impl Into<String>) -> Self {
463 Self {
464 kind: ErrorKind::ReferenceError,
465 message: msg.into(),
466 }
467 }
468
469 pub fn range_error(msg: impl Into<String>) -> Self {
470 Self {
471 kind: ErrorKind::RangeError,
472 message: msg.into(),
473 }
474 }
475
476 pub fn syntax_error(msg: impl Into<String>) -> Self {
477 Self {
478 kind: ErrorKind::SyntaxError,
479 message: msg.into(),
480 }
481 }
482
483 /// Convert to a JS Value (an error object). Allocates through the GC.
484 pub fn to_value(&self, gc: &mut Gc<HeapObject>) -> Value {
485 let mut obj = ObjectData::new();
486 obj.properties.insert(
487 "message".to_string(),
488 Property::data(Value::String(self.message.clone())),
489 );
490 let name = match self.kind {
491 ErrorKind::TypeError => "TypeError",
492 ErrorKind::ReferenceError => "ReferenceError",
493 ErrorKind::RangeError => "RangeError",
494 ErrorKind::SyntaxError => "SyntaxError",
495 ErrorKind::Error => "Error",
496 };
497 obj.properties.insert(
498 "name".to_string(),
499 Property::data(Value::String(name.to_string())),
500 );
501 Value::Object(gc.alloc(HeapObject::Object(obj)))
502 }
503}
504
505// ── Property access helpers ──────────────────────────────────
506
507/// Get a property from an object, walking the prototype chain.
508fn gc_get_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str) -> Value {
509 let proto = {
510 match gc.get(obj_ref) {
511 Some(HeapObject::Object(data)) => {
512 if let Some(prop) = data.properties.get(key) {
513 return prop.value.clone();
514 }
515 data.prototype
516 }
517 Some(HeapObject::Function(fdata)) => {
518 // Check user-defined properties first.
519 if let Some(prop) = fdata.properties.get(key) {
520 return prop.value.clone();
521 }
522 // Functions have a `.prototype` property.
523 if key == "prototype" {
524 if let Some(proto_ref) = fdata.prototype_obj {
525 return Value::Object(proto_ref);
526 }
527 return Value::Undefined;
528 }
529 None
530 }
531 _ => return Value::Undefined,
532 }
533 };
534 if let Some(proto_ref) = proto {
535 gc_get_property(gc, proto_ref, key)
536 } else {
537 Value::Undefined
538 }
539}
540
541/// Check if an object has a property (own or inherited).
542fn gc_has_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str) -> bool {
543 let proto = {
544 match gc.get(obj_ref) {
545 Some(HeapObject::Object(data)) => {
546 if data.properties.contains_key(key) {
547 return true;
548 }
549 data.prototype
550 }
551 Some(HeapObject::Function(fdata)) => {
552 if fdata.properties.contains_key(key) || key == "prototype" {
553 return true;
554 }
555 None
556 }
557 _ => return false,
558 }
559 };
560 if let Some(proto_ref) = proto {
561 gc_has_property(gc, proto_ref, key)
562 } else {
563 false
564 }
565}
566
567/// Collect all enumerable string keys of an object (own + inherited), in proper order.
568/// Integer indices first (sorted numerically), then string keys in insertion order.
569fn gc_enumerate_keys(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> {
570 let mut seen = std::collections::HashSet::new();
571 let mut integer_keys: Vec<(u32, String)> = Vec::new();
572 let mut string_keys: Vec<String> = Vec::new();
573 let mut current = Some(obj_ref);
574
575 while let Some(cur_ref) = current {
576 match gc.get(cur_ref) {
577 Some(HeapObject::Object(data)) => {
578 for (key, prop) in &data.properties {
579 if prop.enumerable && seen.insert(key.clone()) {
580 if let Ok(idx) = key.parse::<u32>() {
581 integer_keys.push((idx, key.clone()));
582 } else {
583 string_keys.push(key.clone());
584 }
585 }
586 }
587 current = data.prototype;
588 }
589 _ => break,
590 }
591 }
592
593 // Integer indices sorted numerically first, then string keys in collected order.
594 integer_keys.sort_by_key(|(idx, _)| *idx);
595 let mut result: Vec<String> = integer_keys.into_iter().map(|(_, k)| k).collect();
596 result.extend(string_keys);
597 result
598}
599
600/// Check if `obj_ref` is an instance of the constructor at `ctor_ref`.
601/// Walks the prototype chain of `obj_ref` looking for `ctor.prototype`.
602fn gc_instanceof(gc: &Gc<HeapObject>, obj_ref: GcRef, ctor_ref: GcRef) -> bool {
603 // Get the constructor's .prototype object.
604 let ctor_proto = match gc.get(ctor_ref) {
605 Some(HeapObject::Function(fdata)) => match fdata.prototype_obj {
606 Some(p) => p,
607 None => return false,
608 },
609 _ => return false,
610 };
611
612 // Walk the prototype chain of obj_ref.
613 let mut current = match gc.get(obj_ref) {
614 Some(HeapObject::Object(data)) => data.prototype,
615 _ => None,
616 };
617
618 while let Some(proto_ref) = current {
619 if proto_ref == ctor_proto {
620 return true;
621 }
622 current = match gc.get(proto_ref) {
623 Some(HeapObject::Object(data)) => data.prototype,
624 _ => None,
625 };
626 }
627 false
628}
629
630/// Get a string property (length, index access).
631fn string_get_property(s: &str, key: &str) -> Value {
632 if key == "length" {
633 Value::Number(s.len() as f64)
634 } else if let Ok(idx) = key.parse::<usize>() {
635 s.chars()
636 .nth(idx)
637 .map(|c| Value::String(c.to_string()))
638 .unwrap_or(Value::Undefined)
639 } else {
640 Value::Undefined
641 }
642}
643
644// ── Type conversion helpers ──────────────────────────────────
645
646/// ToInt32 (ECMA-262 §7.1.5).
647fn to_int32(val: &Value) -> i32 {
648 let n = val.to_number();
649 if n.is_nan() || n.is_infinite() || n == 0.0 {
650 return 0;
651 }
652 let i = n.trunc() as i64;
653 (i & 0xFFFF_FFFF) as i32
654}
655
656/// ToUint32 (ECMA-262 §7.1.6).
657fn to_uint32(val: &Value) -> u32 {
658 let n = val.to_number();
659 if n.is_nan() || n.is_infinite() || n == 0.0 {
660 return 0;
661 }
662 let i = n.trunc() as i64;
663 (i & 0xFFFF_FFFF) as u32
664}
665
666// ── Equality ─────────────────────────────────────────────────
667
668/// Abstract equality comparison (==) per ECMA-262 §7.2.14.
669fn abstract_eq(x: &Value, y: &Value) -> bool {
670 match (x, y) {
671 (Value::Undefined, Value::Undefined) => true,
672 (Value::Null, Value::Null) => true,
673 (Value::Undefined, Value::Null) | (Value::Null, Value::Undefined) => true,
674 (Value::Number(a), Value::Number(b)) => a == b,
675 (Value::String(a), Value::String(b)) => a == b,
676 (Value::Boolean(a), Value::Boolean(b)) => a == b,
677 // Number / String → convert String to Number.
678 (Value::Number(_), Value::String(_)) => abstract_eq(x, &Value::Number(y.to_number())),
679 (Value::String(_), Value::Number(_)) => abstract_eq(&Value::Number(x.to_number()), y),
680 // Boolean → Number.
681 (Value::Boolean(_), _) => abstract_eq(&Value::Number(x.to_number()), y),
682 (_, Value::Boolean(_)) => abstract_eq(x, &Value::Number(y.to_number())),
683 // Same GcRef → equal.
684 (Value::Object(a), Value::Object(b)) => a == b,
685 (Value::Function(a), Value::Function(b)) => a == b,
686 _ => false,
687 }
688}
689
690/// Strict equality comparison (===) per ECMA-262 §7.2.15.
691fn strict_eq(x: &Value, y: &Value) -> bool {
692 match (x, y) {
693 (Value::Undefined, Value::Undefined) => true,
694 (Value::Null, Value::Null) => true,
695 (Value::Number(a), Value::Number(b)) => a == b,
696 (Value::String(a), Value::String(b)) => a == b,
697 (Value::Boolean(a), Value::Boolean(b)) => a == b,
698 // Reference identity for heap objects.
699 (Value::Object(a), Value::Object(b)) => a == b,
700 (Value::Function(a), Value::Function(b)) => a == b,
701 _ => false,
702 }
703}
704
705// ── Relational comparison ────────────────────────────────────
706
707/// Abstract relational comparison. Returns false for NaN comparisons.
708fn abstract_relational(
709 lhs: &Value,
710 rhs: &Value,
711 predicate: fn(std::cmp::Ordering) -> bool,
712) -> bool {
713 // If both are strings, compare lexicographically.
714 if let (Value::String(a), Value::String(b)) = (lhs, rhs) {
715 return predicate(a.cmp(b));
716 }
717 // Otherwise, compare as numbers.
718 let a = lhs.to_number();
719 let b = rhs.to_number();
720 if a.is_nan() || b.is_nan() {
721 return false;
722 }
723 predicate(a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal))
724}
725
726// ── Addition ─────────────────────────────────────────────────
727
728/// The + operator: string concat if either operand is a string, else numeric add.
729fn add_values(lhs: &Value, rhs: &Value, gc: &Gc<HeapObject>) -> Value {
730 match (lhs, rhs) {
731 (Value::String(a), _) => Value::String(format!("{a}{}", rhs.to_js_string(gc))),
732 (_, Value::String(b)) => Value::String(format!("{}{b}", lhs.to_js_string(gc))),
733 _ => Value::Number(lhs.to_number() + rhs.to_number()),
734 }
735}
736
737// ── Call frame ────────────────────────────────────────────────
738
739/// A single call frame on the VM's call stack.
740struct CallFrame {
741 /// The function being executed.
742 func: Function,
743 /// Instruction pointer (byte offset into func.code).
744 ip: usize,
745 /// Base register index in the VM's register file.
746 base: usize,
747 /// Register to write the return value into (absolute index in register file).
748 return_reg: usize,
749 /// Exception handler stack for this frame.
750 exception_handlers: Vec<ExceptionHandler>,
751 /// Captured upvalue cells from the closure that created this call frame.
752 upvalues: Vec<GcRef>,
753}
754
755/// An exception handler entry (for try/catch).
756struct ExceptionHandler {
757 /// IP to jump to on exception (the catch block start).
758 catch_ip: usize,
759 /// Register to store the caught exception value.
760 catch_reg: Reg,
761}
762
763// ── VM ───────────────────────────────────────────────────────
764
765/// The JavaScript virtual machine.
766pub struct Vm {
767 /// Register file (flat array shared across frames via base offsets).
768 registers: Vec<Value>,
769 /// Call stack.
770 frames: Vec<CallFrame>,
771 /// Global variables.
772 globals: HashMap<String, Value>,
773 /// Garbage collector managing heap objects.
774 pub gc: Gc<HeapObject>,
775 /// Optional instruction limit. If set, the VM will return an error after
776 /// executing this many instructions (prevents infinite loops).
777 instruction_limit: Option<u64>,
778 /// Number of instructions executed so far.
779 instructions_executed: u64,
780 /// Built-in Object.prototype (root of the prototype chain).
781 pub object_prototype: Option<GcRef>,
782 /// Built-in Array.prototype (set on newly created arrays).
783 pub array_prototype: Option<GcRef>,
784 /// Built-in String.prototype (for primitive auto-boxing).
785 pub string_prototype: Option<GcRef>,
786 /// Built-in Number.prototype (for primitive auto-boxing).
787 pub number_prototype: Option<GcRef>,
788 /// Built-in Boolean.prototype (for primitive auto-boxing).
789 pub boolean_prototype: Option<GcRef>,
790 /// Built-in Date.prototype (for Date constructor objects).
791 pub date_prototype: Option<GcRef>,
792 /// Built-in RegExp.prototype (for RegExp constructor objects).
793 pub regexp_prototype: Option<GcRef>,
794 /// Built-in Promise.prototype (for Promise objects).
795 pub promise_prototype: Option<GcRef>,
796 /// Console output sink (configurable for dev tools or testing).
797 console_output: Box<dyn ConsoleOutput>,
798 /// DOM bridge for JS-DOM interop (set via `attach_document`).
799 pub(crate) dom_bridge: Option<Rc<DomBridge>>,
800}
801
802/// Maximum register file size.
803const MAX_REGISTERS: usize = 4096;
804/// Maximum call depth.
805const MAX_CALL_DEPTH: usize = 512;
806
807impl Vm {
808 pub fn new() -> Self {
809 let mut vm = Self {
810 registers: vec![Value::Undefined; 256],
811 frames: Vec::new(),
812 globals: HashMap::new(),
813 gc: Gc::new(),
814 instruction_limit: None,
815 instructions_executed: 0,
816 object_prototype: None,
817 array_prototype: None,
818 string_prototype: None,
819 number_prototype: None,
820 boolean_prototype: None,
821 date_prototype: None,
822 regexp_prototype: None,
823 promise_prototype: None,
824 console_output: Box::new(StdConsoleOutput),
825 dom_bridge: None,
826 };
827 crate::builtins::init_builtins(&mut vm);
828 vm
829 }
830
831 /// Replace the console output sink (e.g. for dev tools or testing).
832 pub fn set_console_output(&mut self, output: Box<dyn ConsoleOutput>) {
833 self.console_output = output;
834 }
835
836 /// Attach a DOM document to this VM, registering the `document` global
837 /// and enabling DOM-JS interop.
838 pub fn attach_document(&mut self, doc: Document) {
839 let bridge = Rc::new(DomBridge {
840 document: RefCell::new(doc),
841 node_wrappers: RefCell::new(HashMap::new()),
842 event_listeners: RefCell::new(HashMap::new()),
843 });
844 self.dom_bridge = Some(bridge);
845 crate::dom_bridge::init_document_object(self);
846 crate::dom_bridge::init_event_system(self);
847 }
848
849 /// Detach the DOM document from the VM, returning it.
850 ///
851 /// This removes the `document` global and disconnects the DOM bridge.
852 /// Returns `None` if no document was attached or if there are outstanding
853 /// references to the bridge.
854 pub fn detach_document(&mut self) -> Option<Document> {
855 let bridge = self.dom_bridge.take()?;
856 self.globals.remove("document");
857 match Rc::try_unwrap(bridge) {
858 Ok(bridge) => Some(bridge.document.into_inner()),
859 Err(rc) => {
860 // Something still holds a reference — reattach.
861 self.dom_bridge = Some(rc);
862 None
863 }
864 }
865 }
866
867 /// Set an instruction limit. The VM will return a RuntimeError after
868 /// executing this many instructions.
869 pub fn set_instruction_limit(&mut self, limit: u64) {
870 self.instruction_limit = Some(limit);
871 }
872
873 /// Execute a compiled top-level function and return the completion value.
874 pub fn execute(&mut self, func: &Function) -> Result<Value, RuntimeError> {
875 let reg_count = func.register_count as usize;
876 self.ensure_registers(reg_count);
877
878 self.frames.push(CallFrame {
879 func: func.clone(),
880 ip: 0,
881 base: 0,
882 return_reg: 0,
883 exception_handlers: Vec::new(),
884 upvalues: Vec::new(),
885 });
886
887 let result = self.run()?;
888 self.drain_microtasks()?;
889 Ok(result)
890 }
891
892 /// Call a function (native or bytecode) from outside the execution loop.
893 /// Used by the microtask drain to execute promise callbacks.
894 pub fn call_function(
895 &mut self,
896 func_ref: GcRef,
897 args: &[Value],
898 ) -> Result<Value, RuntimeError> {
899 let (kind, upvalues) = match self.gc.get(func_ref) {
900 Some(HeapObject::Function(f)) => (f.kind.clone(), f.upvalues.clone()),
901 _ => return Err(RuntimeError::type_error("not a function")),
902 };
903
904 match kind {
905 FunctionKind::Native(native) => {
906 // Set async resume data if this function has it.
907 if let Some(HeapObject::Function(f)) = self.gc.get(func_ref) {
908 if let Some(prop) = f.properties.get("__async_data__") {
909 if let Value::Object(data_ref) = &prop.value {
910 ASYNC_RESUME_DATA.with(|cell| cell.set(Some(*data_ref)));
911 }
912 }
913 }
914
915 let this = self
916 .globals
917 .get("this")
918 .cloned()
919 .unwrap_or(Value::Undefined);
920 let dom_ref = self.dom_bridge.as_deref();
921 let mut ctx = NativeContext {
922 gc: &mut self.gc,
923 this,
924 console_output: &*self.console_output,
925 dom_bridge: dom_ref,
926 };
927 let result = (native.callback)(args, &mut ctx)?;
928
929 // Check for generator resume marker.
930 if let Value::Object(r) = &result {
931 let is_gen_resume = matches!(
932 gc_get_property(&self.gc, *r, "__generator_resume__"),
933 Value::Boolean(true)
934 );
935 if is_gen_resume {
936 let gen_ref = match gc_get_property(&self.gc, *r, "__gen_ref__") {
937 Value::Object(gr) => gr,
938 _ => return Ok(Value::Undefined),
939 };
940 let send_val = gc_get_property(&self.gc, *r, "__send_value__");
941 let kind_str = match gc_get_property(&self.gc, *r, "__resume_kind__") {
942 Value::String(s) => s,
943 _ => "next".to_string(),
944 };
945 return match kind_str.as_str() {
946 "next" => self.run_generator(gen_ref, send_val),
947 "return" => {
948 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
949 gen.state = GeneratorState::Completed;
950 }
951 Ok(self.make_iterator_result(send_val, true))
952 }
953 "throw" => {
954 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
955 gen.state = GeneratorState::Completed;
956 }
957 Err(RuntimeError::type_error("Generator throw"))
958 }
959 _ => Ok(Value::Undefined),
960 };
961 }
962
963 // Check for async resume marker.
964 let is_async_resume = matches!(
965 gc_get_property(&self.gc, *r, "__async_resume__"),
966 Value::Boolean(true)
967 );
968 if is_async_resume {
969 let gen_ref = match gc_get_property(&self.gc, *r, "__gen_ref__") {
970 Value::Object(gr) => gr,
971 _ => return Ok(Value::Undefined),
972 };
973 let result_promise =
974 match gc_get_property(&self.gc, *r, "__result_promise__") {
975 Value::Object(pr) => pr,
976 _ => return Ok(Value::Undefined),
977 };
978 let is_throw = matches!(
979 gc_get_property(&self.gc, *r, "__is_throw__"),
980 Value::Boolean(true)
981 );
982 let value = gc_get_property(&self.gc, *r, "__value__");
983 self.drive_async_step(gen_ref, result_promise, value, is_throw);
984 return Ok(Value::Undefined);
985 }
986
987 // Check for async generator resume marker.
988 let is_ag_resume = matches!(
989 gc_get_property(&self.gc, *r, "__async_generator_resume__"),
990 Value::Boolean(true)
991 );
992 if is_ag_resume {
993 let gen_ref = match gc_get_property(&self.gc, *r, "__gen_ref__") {
994 Value::Object(gr) => gr,
995 _ => return Ok(Value::Undefined),
996 };
997 let send_val = gc_get_property(&self.gc, *r, "__send_value__");
998 let kind_str = match gc_get_property(&self.gc, *r, "__resume_kind__") {
999 Value::String(s) => s,
1000 _ => "next".to_string(),
1001 };
1002
1003 // Create a promise for the result.
1004 let promise = crate::builtins::create_promise_object_pub(&mut self.gc);
1005
1006 match kind_str.as_str() {
1007 "next" => match self.run_generator(gen_ref, send_val) {
1008 Ok(iter_result) => {
1009 crate::builtins::resolve_promise_internal(
1010 &mut self.gc,
1011 promise,
1012 iter_result,
1013 );
1014 }
1015 Err(err) => {
1016 let reason = err.to_value(&mut self.gc);
1017 crate::builtins::reject_promise_internal(
1018 &mut self.gc,
1019 promise,
1020 reason,
1021 );
1022 }
1023 },
1024 "return" => {
1025 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
1026 gen.state = GeneratorState::Completed;
1027 }
1028 let result = self.make_iterator_result(send_val, true);
1029 crate::builtins::resolve_promise_internal(
1030 &mut self.gc,
1031 promise,
1032 result,
1033 );
1034 }
1035 _ => {}
1036 }
1037 return Ok(Value::Object(promise));
1038 }
1039
1040 // Check for event dispatch marker.
1041 let is_event_dispatch = matches!(
1042 gc_get_property(&self.gc, *r, "__event_dispatch__"),
1043 Value::Boolean(true)
1044 );
1045 if is_event_dispatch {
1046 let target_idx = match gc_get_property(&self.gc, *r, "__target_id__") {
1047 Value::Number(n) => n as usize,
1048 _ => return Ok(Value::Boolean(true)),
1049 };
1050 let evt_ref = match gc_get_property(&self.gc, *r, "__event_ref__") {
1051 Value::Object(er) => er,
1052 _ => return Ok(Value::Boolean(true)),
1053 };
1054 return Ok(crate::dom_bridge::run_event_dispatch(
1055 self, target_idx, evt_ref,
1056 ));
1057 }
1058 }
1059
1060 Ok(result)
1061 }
1062 FunctionKind::Bytecode(bc) => {
1063 let callee_func = bc.func;
1064
1065 // Async function: create generator + promise, drive async.
1066 if callee_func.is_async && !callee_func.is_generator {
1067 let gen_ref = self.create_raw_generator(callee_func, upvalues, args);
1068 let result_promise = crate::builtins::create_promise_object_pub(&mut self.gc);
1069 self.drive_async_step(gen_ref, result_promise, Value::Undefined, false);
1070 return Ok(Value::Object(result_promise));
1071 }
1072
1073 // Async generator function: create async generator wrapper.
1074 if callee_func.is_async && callee_func.is_generator {
1075 let gen_ref = self.create_raw_generator(callee_func, upvalues, args);
1076 let wrapper = self.create_async_generator_wrapper(gen_ref);
1077 return Ok(Value::Object(wrapper));
1078 }
1079
1080 // Generator function: create a generator object instead of executing.
1081 if callee_func.is_generator {
1082 let gen_obj = self.create_generator_object(callee_func, upvalues, args);
1083 return Ok(Value::Object(gen_obj));
1084 }
1085
1086 // Save current frames and run the function in isolation.
1087 let saved_frames = std::mem::take(&mut self.frames);
1088
1089 // Compute base after any existing register usage.
1090 let base = saved_frames
1091 .last()
1092 .map(|f| f.base + f.func.register_count as usize)
1093 .unwrap_or(0);
1094
1095 let reg_count = callee_func.register_count as usize;
1096 self.ensure_registers(base + reg_count);
1097
1098 // Copy arguments.
1099 let param_count = callee_func.param_count as usize;
1100 for (i, arg) in args.iter().enumerate() {
1101 if i < param_count {
1102 self.registers[base + i] = arg.clone();
1103 }
1104 }
1105 for i in args.len()..param_count {
1106 self.registers[base + i] = Value::Undefined;
1107 }
1108
1109 self.frames.push(CallFrame {
1110 func: callee_func,
1111 ip: 0,
1112 base,
1113 return_reg: base,
1114 exception_handlers: Vec::new(),
1115 upvalues,
1116 });
1117
1118 let result = self.run();
1119
1120 // Restore saved frames.
1121 self.frames = saved_frames;
1122
1123 result
1124 }
1125 }
1126 }
1127
1128 /// Drain the microtask queue. Called after execute() and recursively
1129 /// until no more microtasks are pending.
1130 fn drain_microtasks(&mut self) -> Result<(), RuntimeError> {
1131 loop {
1132 let tasks = crate::builtins::take_microtasks();
1133 if tasks.is_empty() {
1134 break;
1135 }
1136
1137 for task in tasks {
1138 match task.handler {
1139 Some(handler_ref) => {
1140 // Call the handler with the value.
1141 let result =
1142 self.call_function(handler_ref, std::slice::from_ref(&task.value));
1143 if let Some(chained) = task.chained_promise {
1144 // Check if this is a "finally" chain.
1145 let is_finally = matches!(
1146 crate::builtins::promise_get_prop_pub(
1147 &self.gc,
1148 chained,
1149 "__finally__"
1150 ),
1151 Value::Boolean(true)
1152 );
1153
1154 if is_finally {
1155 // finally: ignore handler result, propagate parent's result.
1156 match result {
1157 Ok(_) => {
1158 let parent = crate::builtins::promise_get_prop_pub(
1159 &self.gc,
1160 chained,
1161 "__finally_parent__",
1162 );
1163 if let Some(parent_ref) = parent.gc_ref() {
1164 let parent_state = crate::builtins::promise_state_pub(
1165 &self.gc, parent_ref,
1166 );
1167 let parent_result =
1168 crate::builtins::promise_get_prop_pub(
1169 &self.gc,
1170 parent_ref,
1171 crate::builtins::PROMISE_RESULT_KEY,
1172 );
1173 if parent_state == crate::builtins::PROMISE_FULFILLED {
1174 crate::builtins::resolve_promise_internal(
1175 &mut self.gc,
1176 chained,
1177 parent_result,
1178 );
1179 } else {
1180 crate::builtins::reject_promise_internal(
1181 &mut self.gc,
1182 chained,
1183 parent_result,
1184 );
1185 }
1186 }
1187 }
1188 Err(err) => {
1189 let err_val = err.to_value(&mut self.gc);
1190 crate::builtins::reject_promise_internal(
1191 &mut self.gc,
1192 chained,
1193 err_val,
1194 );
1195 }
1196 }
1197 } else {
1198 match result {
1199 Ok(val) => {
1200 // If result is a promise, chain it.
1201 if crate::builtins::is_promise_pub(&self.gc, &val) {
1202 if let Some(val_ref) = val.gc_ref() {
1203 let state = crate::builtins::promise_state_pub(
1204 &self.gc, val_ref,
1205 );
1206 if state == crate::builtins::PROMISE_FULFILLED {
1207 let r = crate::builtins::promise_get_prop_pub(
1208 &self.gc,
1209 val_ref,
1210 crate::builtins::PROMISE_RESULT_KEY,
1211 );
1212 crate::builtins::resolve_promise_internal(
1213 &mut self.gc,
1214 chained,
1215 r,
1216 );
1217 } else if state == crate::builtins::PROMISE_REJECTED
1218 {
1219 let r = crate::builtins::promise_get_prop_pub(
1220 &self.gc,
1221 val_ref,
1222 crate::builtins::PROMISE_RESULT_KEY,
1223 );
1224 crate::builtins::reject_promise_internal(
1225 &mut self.gc,
1226 chained,
1227 r,
1228 );
1229 } else {
1230 crate::builtins::chain_promise_pub(
1231 &mut self.gc,
1232 val_ref,
1233 chained,
1234 );
1235 }
1236 }
1237 } else {
1238 crate::builtins::resolve_promise_internal(
1239 &mut self.gc,
1240 chained,
1241 val,
1242 );
1243 }
1244 }
1245 Err(err) => {
1246 let err_val = err.to_value(&mut self.gc);
1247 crate::builtins::reject_promise_internal(
1248 &mut self.gc,
1249 chained,
1250 err_val,
1251 );
1252 }
1253 }
1254 }
1255 }
1256 }
1257 None => {
1258 // No handler: identity for fulfillment, thrower for rejection.
1259 if let Some(chained) = task.chained_promise {
1260 if task.is_fulfillment {
1261 crate::builtins::resolve_promise_internal(
1262 &mut self.gc,
1263 chained,
1264 task.value,
1265 );
1266 } else {
1267 crate::builtins::reject_promise_internal(
1268 &mut self.gc,
1269 chained,
1270 task.value,
1271 );
1272 }
1273 }
1274 }
1275 }
1276 }
1277 }
1278 Ok(())
1279 }
1280
1281 /// Execute all due timer callbacks. After each callback, drain the
1282 /// microtask queue (so Promise `.then()` runs between timer callbacks).
1283 fn drain_due_timers(&mut self) -> Result<(), RuntimeError> {
1284 let due = crate::timers::take_due_timers();
1285 for timer in due {
1286 // For requestAnimationFrame, pass the timestamp as an argument.
1287 let args = match timer.raf_timestamp {
1288 Some(ts) => vec![Value::Number(ts)],
1289 None => vec![],
1290 };
1291 let _ = self.call_function(timer.callback, &args);
1292 self.drain_microtasks()?;
1293 }
1294 Ok(())
1295 }
1296
1297 /// Resolve/reject promises for completed fetch() requests.
1298 fn drain_completed_fetches(&mut self) -> Result<(), RuntimeError> {
1299 let completed = crate::fetch::take_completed_fetches();
1300 for fetch in completed {
1301 match fetch.result {
1302 Ok(result) => {
1303 let response = crate::fetch::create_response_object(&mut self.gc, &result);
1304 crate::builtins::resolve_promise_internal(
1305 &mut self.gc,
1306 fetch.promise,
1307 Value::Object(response),
1308 );
1309 }
1310 Err(err_msg) => {
1311 let err_val = Value::String(err_msg);
1312 crate::builtins::reject_promise_internal(&mut self.gc, fetch.promise, err_val);
1313 }
1314 }
1315 self.drain_microtasks()?;
1316 }
1317 Ok(())
1318 }
1319
1320 /// Pump the event loop: drain due timers, completed fetches, and microtasks.
1321 ///
1322 /// Call this from tests or the platform event loop to fire any pending
1323 /// timers whose delay has elapsed. Each timer callback is followed by
1324 /// a microtask drain.
1325 pub fn pump_event_loop(&mut self) -> Result<(), RuntimeError> {
1326 self.drain_due_timers()?;
1327 self.drain_completed_fetches()
1328 }
1329
1330 /// Run the event loop until all pending timers and fetches have completed.
1331 /// Useful in tests to deterministically execute all scheduled work.
1332 /// `max_iterations` caps the loop to prevent infinite loops with
1333 /// recurring intervals; pass 0 for unlimited.
1334 pub fn run_event_loop(&mut self, max_iterations: usize) -> Result<(), RuntimeError> {
1335 let mut iterations = 0;
1336 while crate::timers::has_pending_timers() || crate::fetch::has_pending_fetches() {
1337 if max_iterations > 0 && iterations >= max_iterations {
1338 break;
1339 }
1340 self.drain_due_timers()?;
1341 self.drain_completed_fetches()?;
1342 iterations += 1;
1343 // If work is still pending, sleep briefly to avoid spinning.
1344 if crate::timers::has_pending_timers() || crate::fetch::has_pending_fetches() {
1345 std::thread::sleep(std::time::Duration::from_millis(1));
1346 }
1347 }
1348 Ok(())
1349 }
1350
1351 /// Ensure the register file has at least `needed` slots.
1352 fn ensure_registers(&mut self, needed: usize) {
1353 if needed > self.registers.len() {
1354 if needed > MAX_REGISTERS {
1355 return;
1356 }
1357 self.registers.resize(needed, Value::Undefined);
1358 }
1359 }
1360
1361 /// Read a u8 from the current frame's bytecode and advance IP.
1362 #[inline]
1363 fn read_u8(frame: &mut CallFrame) -> u8 {
1364 let b = frame.func.code[frame.ip];
1365 frame.ip += 1;
1366 b
1367 }
1368
1369 /// Read a u16 (little-endian) from the current frame's bytecode and advance IP.
1370 #[inline]
1371 fn read_u16(frame: &mut CallFrame) -> u16 {
1372 let lo = frame.func.code[frame.ip];
1373 let hi = frame.func.code[frame.ip + 1];
1374 frame.ip += 2;
1375 u16::from_le_bytes([lo, hi])
1376 }
1377
1378 /// Read an i32 (little-endian) from the current frame's bytecode and advance IP.
1379 #[inline]
1380 fn read_i32(frame: &mut CallFrame) -> i32 {
1381 let bytes = [
1382 frame.func.code[frame.ip],
1383 frame.func.code[frame.ip + 1],
1384 frame.func.code[frame.ip + 2],
1385 frame.func.code[frame.ip + 3],
1386 ];
1387 frame.ip += 4;
1388 i32::from_le_bytes(bytes)
1389 }
1390
1391 // ── Generator helpers ──────────────────────────────────────
1392
1393 /// Create a generator object from a generator function.
1394 fn create_generator_object(
1395 &mut self,
1396 func: Function,
1397 upvalues: Vec<GcRef>,
1398 args: &[Value],
1399 ) -> GcRef {
1400 // Pre-fill registers with arguments.
1401 let reg_count = func.register_count as usize;
1402 let mut regs = vec![Value::Undefined; reg_count];
1403 for (i, arg) in args.iter().enumerate() {
1404 if i < func.param_count as usize {
1405 regs[i] = arg.clone();
1406 }
1407 }
1408
1409 let gen_data = GeneratorData {
1410 state: GeneratorState::NotStarted,
1411 func,
1412 upvalues,
1413 registers: regs,
1414 ip: 0,
1415 prototype: self.object_prototype,
1416 exception_handlers: Vec::new(),
1417 };
1418
1419 let gen_ref = self.gc.alloc(HeapObject::Generator(Box::new(gen_data)));
1420
1421 // Wrap in an object with next/return/throw methods.
1422 let mut obj = ObjectData::new();
1423 obj.prototype = self.object_prototype;
1424
1425 // Store the generator GcRef so methods can find it.
1426 obj.properties.insert(
1427 "__gen__".to_string(),
1428 Property {
1429 value: Value::Object(gen_ref),
1430 writable: false,
1431 enumerable: false,
1432 configurable: false,
1433 },
1434 );
1435
1436 // next() method
1437 let next_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
1438 name: "next".to_string(),
1439 kind: FunctionKind::Native(NativeFunc {
1440 callback: generator_next,
1441 }),
1442 prototype_obj: None,
1443 properties: HashMap::new(),
1444 upvalues: Vec::new(),
1445 })));
1446 obj.properties.insert(
1447 "next".to_string(),
1448 Property::builtin(Value::Function(next_fn)),
1449 );
1450
1451 // return() method
1452 let return_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
1453 name: "return".to_string(),
1454 kind: FunctionKind::Native(NativeFunc {
1455 callback: generator_return,
1456 }),
1457 prototype_obj: None,
1458 properties: HashMap::new(),
1459 upvalues: Vec::new(),
1460 })));
1461 obj.properties.insert(
1462 "return".to_string(),
1463 Property::builtin(Value::Function(return_fn)),
1464 );
1465
1466 // throw() method
1467 let throw_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
1468 name: "throw".to_string(),
1469 kind: FunctionKind::Native(NativeFunc {
1470 callback: generator_throw,
1471 }),
1472 prototype_obj: None,
1473 properties: HashMap::new(),
1474 upvalues: Vec::new(),
1475 })));
1476 obj.properties.insert(
1477 "throw".to_string(),
1478 Property::builtin(Value::Function(throw_fn)),
1479 );
1480
1481 // @@iterator method (generators are iterable - returns self)
1482 let iter_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
1483 name: "[Symbol.iterator]".to_string(),
1484 kind: FunctionKind::Native(NativeFunc {
1485 callback: generator_symbol_iterator,
1486 }),
1487 prototype_obj: None,
1488 properties: HashMap::new(),
1489 upvalues: Vec::new(),
1490 })));
1491 obj.properties.insert(
1492 "@@iterator".to_string(),
1493 Property::builtin(Value::Function(iter_fn)),
1494 );
1495
1496 self.gc.alloc(HeapObject::Object(obj))
1497 }
1498
1499 /// Create a raw GeneratorData (HeapObject::Generator) without the wrapper object.
1500 /// Used by async functions which manage their own driving logic.
1501 fn create_raw_generator(
1502 &mut self,
1503 func: Function,
1504 upvalues: Vec<GcRef>,
1505 args: &[Value],
1506 ) -> GcRef {
1507 let reg_count = func.register_count as usize;
1508 let mut regs = vec![Value::Undefined; reg_count];
1509 for (i, arg) in args.iter().enumerate() {
1510 if i < func.param_count as usize {
1511 regs[i] = arg.clone();
1512 }
1513 }
1514
1515 let gen_data = GeneratorData {
1516 state: GeneratorState::NotStarted,
1517 func,
1518 upvalues,
1519 registers: regs,
1520 ip: 0,
1521 prototype: self.object_prototype,
1522 exception_handlers: Vec::new(),
1523 };
1524
1525 self.gc.alloc(HeapObject::Generator(Box::new(gen_data)))
1526 }
1527
1528 /// Create a {value, done} iterator result object.
1529 fn make_iterator_result(&mut self, value: Value, done: bool) -> Value {
1530 let mut obj = ObjectData::new();
1531 obj.prototype = self.object_prototype;
1532 obj.properties
1533 .insert("value".to_string(), Property::data(value));
1534 obj.properties
1535 .insert("done".to_string(), Property::data(Value::Boolean(done)));
1536 let gc_ref = self.gc.alloc(HeapObject::Object(obj));
1537 Value::Object(gc_ref)
1538 }
1539
1540 /// Run a generator until its next yield/return.
1541 /// Returns the yielded/returned value.
1542 pub fn run_generator(
1543 &mut self,
1544 gen_ref: GcRef,
1545 send_value: Value,
1546 ) -> Result<Value, RuntimeError> {
1547 // Extract generator data.
1548 let (func, upvalues, mut regs, ip, state, saved_exc_handlers) = match self.gc.get(gen_ref) {
1549 Some(HeapObject::Generator(gen)) => {
1550 if gen.state == GeneratorState::Completed {
1551 return Ok(self.make_iterator_result(Value::Undefined, true));
1552 }
1553 if gen.state == GeneratorState::Executing {
1554 return Err(RuntimeError::type_error("Generator is already executing"));
1555 }
1556 (
1557 gen.func.clone(),
1558 gen.upvalues.clone(),
1559 gen.registers.clone(),
1560 gen.ip,
1561 gen.state,
1562 gen.exception_handlers.clone(),
1563 )
1564 }
1565 _ => return Err(RuntimeError::type_error("not a generator")),
1566 };
1567
1568 // Mark as executing.
1569 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
1570 gen.state = GeneratorState::Executing;
1571 }
1572
1573 // If resuming from a yield/await, write the sent value into the dst register.
1574 if state == GeneratorState::Suspended && ip >= 3 {
1575 // The Yield/Await instruction was: op dst, src (3 bytes total: op + dst + src)
1576 // After executing, ip points past it. The dst byte is at ip - 2.
1577 let dst_reg = func.code[ip - 2] as usize;
1578 regs[dst_reg] = send_value;
1579 }
1580
1581 // Save current VM state.
1582 let saved_frames = std::mem::take(&mut self.frames);
1583 let saved_instructions = self.instructions_executed;
1584
1585 // Use a base past any existing register usage to avoid clobbering
1586 // the caller's register file.
1587 let base = saved_frames
1588 .last()
1589 .map(|f| f.base + f.func.register_count as usize)
1590 .unwrap_or(0);
1591
1592 let reg_count = func.register_count as usize;
1593 self.ensure_registers(base + reg_count + 1);
1594
1595 // Set up registers for the generator.
1596 for (i, val) in regs.iter().enumerate() {
1597 self.registers[base + i] = val.clone();
1598 }
1599
1600 // Push frame. return_reg points to a slot that holds the generator ref
1601 // so Yield can find it. We use a slot just past the registers.
1602 self.registers[base + reg_count] = Value::Object(gen_ref);
1603
1604 // Restore exception handlers.
1605 let exception_handlers = saved_exc_handlers
1606 .iter()
1607 .map(|&(catch_ip, catch_reg)| ExceptionHandler {
1608 catch_ip,
1609 catch_reg,
1610 })
1611 .collect();
1612
1613 self.frames.push(CallFrame {
1614 func,
1615 ip,
1616 base,
1617 return_reg: base + reg_count,
1618 exception_handlers,
1619 upvalues,
1620 });
1621
1622 let result = self.run();
1623
1624 // Restore VM state.
1625 self.frames = saved_frames;
1626 self.instructions_executed = saved_instructions;
1627
1628 match result {
1629 Ok(val) => {
1630 // Normal return from generator (either via Return or end of function).
1631 // Check if it was a Yield (state == Suspended) or a Return (state stays Executing).
1632 let gen_state = match self.gc.get(gen_ref) {
1633 Some(HeapObject::Generator(gen)) => gen.state,
1634 _ => GeneratorState::Completed,
1635 };
1636 if gen_state == GeneratorState::Suspended {
1637 // Yield already created the result; `val` is the {value, done} object.
1638 Ok(val)
1639 } else {
1640 // Return: mark completed and wrap result.
1641 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
1642 gen.state = GeneratorState::Completed;
1643 }
1644 Ok(self.make_iterator_result(val, true))
1645 }
1646 }
1647 Err(err) => {
1648 // Generator threw: mark completed.
1649 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
1650 gen.state = GeneratorState::Completed;
1651 }
1652 Err(err)
1653 }
1654 }
1655 }
1656
1657 /// Throw a value into a suspended generator. Used for `await` on rejected
1658 /// promises — the rejection reason is thrown so that try/catch can handle it.
1659 fn throw_into_generator(
1660 &mut self,
1661 gen_ref: GcRef,
1662 throw_value: Value,
1663 ) -> Result<Value, RuntimeError> {
1664 // Extract generator data.
1665 let (func, upvalues, regs, ip, state, saved_exc_handlers) = match self.gc.get(gen_ref) {
1666 Some(HeapObject::Generator(gen)) => {
1667 if gen.state == GeneratorState::Completed {
1668 return Ok(self.make_iterator_result(Value::Undefined, true));
1669 }
1670 if gen.state == GeneratorState::Executing {
1671 return Err(RuntimeError::type_error("Generator is already executing"));
1672 }
1673 (
1674 gen.func.clone(),
1675 gen.upvalues.clone(),
1676 gen.registers.clone(),
1677 gen.ip,
1678 gen.state,
1679 gen.exception_handlers.clone(),
1680 )
1681 }
1682 _ => return Err(RuntimeError::type_error("not a generator")),
1683 };
1684
1685 if state == GeneratorState::NotStarted {
1686 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
1687 gen.state = GeneratorState::Completed;
1688 }
1689 return Err(RuntimeError {
1690 kind: ErrorKind::Error,
1691 message: throw_value.to_js_string(&self.gc),
1692 });
1693 }
1694
1695 // Mark as executing.
1696 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
1697 gen.state = GeneratorState::Executing;
1698 }
1699
1700 // Save current VM state.
1701 let saved_frames = std::mem::take(&mut self.frames);
1702 let saved_instructions = self.instructions_executed;
1703
1704 let base = saved_frames
1705 .last()
1706 .map(|f| f.base + f.func.register_count as usize)
1707 .unwrap_or(0);
1708
1709 let reg_count = func.register_count as usize;
1710 self.ensure_registers(base + reg_count + 1);
1711
1712 for (i, val) in regs.iter().enumerate() {
1713 self.registers[base + i] = val.clone();
1714 }
1715
1716 self.registers[base + reg_count] = Value::Object(gen_ref);
1717
1718 // Restore exception handlers from the generator.
1719 let exception_handlers = saved_exc_handlers
1720 .iter()
1721 .map(|&(catch_ip, catch_reg)| ExceptionHandler {
1722 catch_ip,
1723 catch_reg,
1724 })
1725 .collect();
1726
1727 self.frames.push(CallFrame {
1728 func,
1729 ip,
1730 base,
1731 return_reg: base + reg_count,
1732 exception_handlers,
1733 upvalues,
1734 });
1735
1736 // Instead of writing send_value to dst register, throw the value.
1737 let caught = self.handle_exception(throw_value);
1738 let result = if caught {
1739 self.run()
1740 } else {
1741 let msg = "Uncaught (in async)".to_string();
1742 Err(RuntimeError {
1743 kind: ErrorKind::Error,
1744 message: msg,
1745 })
1746 };
1747
1748 // Restore VM state.
1749 self.frames = saved_frames;
1750 self.instructions_executed = saved_instructions;
1751
1752 match result {
1753 Ok(val) => {
1754 let gen_state = match self.gc.get(gen_ref) {
1755 Some(HeapObject::Generator(gen)) => gen.state,
1756 _ => GeneratorState::Completed,
1757 };
1758 if gen_state == GeneratorState::Suspended {
1759 Ok(val)
1760 } else {
1761 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
1762 gen.state = GeneratorState::Completed;
1763 }
1764 Ok(self.make_iterator_result(val, true))
1765 }
1766 }
1767 Err(err) => {
1768 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
1769 gen.state = GeneratorState::Completed;
1770 }
1771 Err(err)
1772 }
1773 }
1774 }
1775
1776 // ── Iterator protocol helpers ────────────────────────────────
1777
1778 /// Get an iterator from a value by calling its [Symbol.iterator]() method.
1779 pub fn get_iterator(&mut self, iterable: &Value) -> Result<Value, RuntimeError> {
1780 // Get the @@iterator property.
1781 let iter_fn = match iterable {
1782 Value::Object(gc_ref) | Value::Function(gc_ref) => {
1783 gc_get_property(&self.gc, *gc_ref, "@@iterator")
1784 }
1785 Value::String(_) => {
1786 // Strings have @@iterator on their prototype.
1787 self.string_prototype
1788 .map(|p| gc_get_property(&self.gc, p, "@@iterator"))
1789 .unwrap_or(Value::Undefined)
1790 }
1791 _ => Value::Undefined,
1792 };
1793
1794 let iter_fn_ref = match iter_fn {
1795 Value::Function(r) => r,
1796 _ => {
1797 return Err(RuntimeError::type_error(
1798 "object is not iterable (no Symbol.iterator)",
1799 ));
1800 }
1801 };
1802
1803 // Call [Symbol.iterator]() with `this` set to the iterable.
1804 // We temporarily set `this` in globals for the native call.
1805 let old_this = self.globals.get("this").cloned();
1806 self.globals.insert("this".to_string(), iterable.clone());
1807 let result = self.call_function(iter_fn_ref, &[]);
1808 match old_this {
1809 Some(v) => self.globals.insert("this".to_string(), v),
1810 None => self.globals.remove("this"),
1811 };
1812 result
1813 }
1814
1815 /// Call iterator.next() and return (value, done).
1816 pub fn iterator_next(&mut self, iterator: &Value) -> Result<(Value, bool), RuntimeError> {
1817 let iter_ref = match iterator {
1818 Value::Object(r) | Value::Function(r) => *r,
1819 _ => return Err(RuntimeError::type_error("iterator is not an object")),
1820 };
1821
1822 let next_fn = gc_get_property(&self.gc, iter_ref, "next");
1823 let next_fn_ref = match next_fn {
1824 Value::Function(r) => r,
1825 _ => return Err(RuntimeError::type_error("iterator.next is not a function")),
1826 };
1827
1828 // Call next() with `this` = iterator.
1829 let old_this = self.globals.get("this").cloned();
1830 self.globals.insert("this".to_string(), iterator.clone());
1831 let result = self.call_function(next_fn_ref, &[])?;
1832 match old_this {
1833 Some(v) => self.globals.insert("this".to_string(), v),
1834 None => self.globals.remove("this"),
1835 };
1836
1837 // Extract value and done from the result object.
1838 let (value, done) = match result {
1839 Value::Object(r) => {
1840 let val = gc_get_property(&self.gc, r, "value");
1841 let d = gc_get_property(&self.gc, r, "done");
1842 (val, d.to_boolean())
1843 }
1844 _ => (Value::Undefined, true),
1845 };
1846
1847 Ok((value, done))
1848 }
1849
1850 // ── Async function helpers ──────────────────────────────────
1851
1852 /// Drive one step of an async function. Runs the internal generator until
1853 /// it yields (await) or returns, then wires up promise reactions for the
1854 /// next step.
1855 fn drive_async_step(
1856 &mut self,
1857 gen_ref: GcRef,
1858 result_promise: GcRef,
1859 send_value: Value,
1860 is_throw: bool,
1861 ) {
1862 let result = if is_throw {
1863 // Throw into the generator — resume it and throw so that
1864 // try/catch inside the async function can handle it.
1865 self.throw_into_generator(gen_ref, send_value)
1866 } else {
1867 self.run_generator(gen_ref, send_value)
1868 };
1869
1870 match result {
1871 Ok(iter_result) => {
1872 // Extract {value, done} from the iterator result.
1873 let (value, done) = match &iter_result {
1874 Value::Object(r) => {
1875 let val = gc_get_property(&self.gc, *r, "value");
1876 let d = gc_get_property(&self.gc, *r, "done");
1877 (val, d.to_boolean())
1878 }
1879 _ => (Value::Undefined, true),
1880 };
1881
1882 if done {
1883 // Async function returned — resolve the result promise.
1884 crate::builtins::resolve_promise_internal(&mut self.gc, result_promise, value);
1885 } else {
1886 // Async function awaited — set up promise chain to resume.
1887 self.setup_async_resume(gen_ref, result_promise, value);
1888 }
1889 }
1890 Err(err) => {
1891 // Async function threw — reject the result promise.
1892 // For plain `throw expr` (ErrorKind::Error), reject with the
1893 // message string to preserve the original thrown value.
1894 // For typed errors (TypeError, etc.), wrap in an error object.
1895 let reason = if err.kind == ErrorKind::Error {
1896 Value::String(err.message.clone())
1897 } else {
1898 err.to_value(&mut self.gc)
1899 };
1900 crate::builtins::reject_promise_internal(&mut self.gc, result_promise, reason);
1901 }
1902 }
1903 }
1904
1905 /// Set up promise reactions so that when the awaited value settles, the
1906 /// async function resumes.
1907 fn setup_async_resume(&mut self, gen_ref: GcRef, result_promise: GcRef, awaited_value: Value) {
1908 // Create the fulfill callback.
1909 let fulfill_data = self.make_async_resume_data(gen_ref, result_promise, false);
1910 let fulfill_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
1911 name: "__async_fulfill__".to_string(),
1912 kind: FunctionKind::Native(NativeFunc {
1913 callback: async_resume_callback,
1914 }),
1915 prototype_obj: None,
1916 properties: {
1917 let mut m = HashMap::new();
1918 m.insert(
1919 "__async_data__".to_string(),
1920 Property::builtin(Value::Object(fulfill_data)),
1921 );
1922 m
1923 },
1924 upvalues: Vec::new(),
1925 })));
1926
1927 // Create the reject callback.
1928 let reject_data = self.make_async_resume_data(gen_ref, result_promise, true);
1929 let reject_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
1930 name: "__async_reject__".to_string(),
1931 kind: FunctionKind::Native(NativeFunc {
1932 callback: async_resume_callback,
1933 }),
1934 prototype_obj: None,
1935 properties: {
1936 let mut m = HashMap::new();
1937 m.insert(
1938 "__async_data__".to_string(),
1939 Property::builtin(Value::Object(reject_data)),
1940 );
1941 m
1942 },
1943 upvalues: Vec::new(),
1944 })));
1945
1946 // If the awaited value is a promise, react to it.
1947 if crate::builtins::is_promise_pub(&self.gc, &awaited_value) {
1948 let val_ref = awaited_value.gc_ref().unwrap();
1949 let state = crate::builtins::promise_state_pub(&self.gc, val_ref);
1950 if state == crate::builtins::PROMISE_FULFILLED {
1951 let r = crate::builtins::promise_get_prop_pub(
1952 &self.gc,
1953 val_ref,
1954 crate::builtins::PROMISE_RESULT_KEY,
1955 );
1956 crate::builtins::enqueue_microtask_pub(crate::builtins::Microtask {
1957 handler: Some(fulfill_fn),
1958 value: r,
1959 chained_promise: None,
1960 is_fulfillment: true,
1961 });
1962 } else if state == crate::builtins::PROMISE_REJECTED {
1963 let r = crate::builtins::promise_get_prop_pub(
1964 &self.gc,
1965 val_ref,
1966 crate::builtins::PROMISE_RESULT_KEY,
1967 );
1968 crate::builtins::enqueue_microtask_pub(crate::builtins::Microtask {
1969 handler: Some(reject_fn),
1970 value: r,
1971 chained_promise: None,
1972 is_fulfillment: false,
1973 });
1974 } else {
1975 // Pending promise: add reactions.
1976 crate::builtins::add_reaction_pub(
1977 &mut self.gc,
1978 val_ref,
1979 Value::Function(fulfill_fn),
1980 Value::Function(reject_fn),
1981 );
1982 }
1983 } else {
1984 // Not a promise: resume immediately via microtask with the value.
1985 crate::builtins::enqueue_microtask_pub(crate::builtins::Microtask {
1986 handler: Some(fulfill_fn),
1987 value: awaited_value,
1988 chained_promise: None,
1989 is_fulfillment: true,
1990 });
1991 }
1992 }
1993
1994 /// Create an object holding the data needed to resume an async function.
1995 fn make_async_resume_data(
1996 &mut self,
1997 gen_ref: GcRef,
1998 result_promise: GcRef,
1999 is_throw: bool,
2000 ) -> GcRef {
2001 let mut data = ObjectData::new();
2002 data.properties.insert(
2003 "__gen_ref__".to_string(),
2004 Property::builtin(Value::Object(gen_ref)),
2005 );
2006 data.properties.insert(
2007 "__result_promise__".to_string(),
2008 Property::builtin(Value::Object(result_promise)),
2009 );
2010 data.properties.insert(
2011 "__is_throw__".to_string(),
2012 Property::builtin(Value::Boolean(is_throw)),
2013 );
2014 self.gc.alloc(HeapObject::Object(data))
2015 }
2016
2017 /// Create an async generator wrapper object (for `async function*`).
2018 fn create_async_generator_wrapper(&mut self, gen_ref: GcRef) -> GcRef {
2019 let mut obj = ObjectData::new();
2020 obj.prototype = self.object_prototype;
2021
2022 obj.properties.insert(
2023 "__gen__".to_string(),
2024 Property {
2025 value: Value::Object(gen_ref),
2026 writable: false,
2027 enumerable: false,
2028 configurable: false,
2029 },
2030 );
2031
2032 // next() method — returns a Promise for the next iteration result.
2033 let next_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
2034 name: "next".to_string(),
2035 kind: FunctionKind::Native(NativeFunc {
2036 callback: async_generator_next,
2037 }),
2038 prototype_obj: None,
2039 properties: HashMap::new(),
2040 upvalues: Vec::new(),
2041 })));
2042 obj.properties.insert(
2043 "next".to_string(),
2044 Property::builtin(Value::Function(next_fn)),
2045 );
2046
2047 // return() method
2048 let return_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
2049 name: "return".to_string(),
2050 kind: FunctionKind::Native(NativeFunc {
2051 callback: async_generator_return,
2052 }),
2053 prototype_obj: None,
2054 properties: HashMap::new(),
2055 upvalues: Vec::new(),
2056 })));
2057 obj.properties.insert(
2058 "return".to_string(),
2059 Property::builtin(Value::Function(return_fn)),
2060 );
2061
2062 // @@asyncIterator method — returns self.
2063 let iter_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
2064 name: "[Symbol.asyncIterator]".to_string(),
2065 kind: FunctionKind::Native(NativeFunc {
2066 callback: generator_symbol_iterator,
2067 }),
2068 prototype_obj: None,
2069 properties: HashMap::new(),
2070 upvalues: Vec::new(),
2071 })));
2072 obj.properties.insert(
2073 "@@asyncIterator".to_string(),
2074 Property::builtin(Value::Function(iter_fn)),
2075 );
2076
2077 self.gc.alloc(HeapObject::Object(obj))
2078 }
2079
2080 /// Resolve a dynamic DOM property for a wrapper object.
2081 /// Returns `Some(value)` if the key is a recognized DOM property, `None` otherwise.
2082 fn resolve_dom_property(&mut self, gc_ref: GcRef, key: &str) -> Option<Value> {
2083 let bridge = Rc::clone(self.dom_bridge.as_ref()?);
2084 crate::dom_bridge::resolve_dom_get(&mut self.gc, &bridge, gc_ref, key)
2085 }
2086
2087 /// Handle a DOM property set on a wrapper object.
2088 /// Returns `true` if the property was handled (caller should skip normal set).
2089 fn handle_dom_property_set(&mut self, gc_ref: GcRef, key: &str, val: &Value) -> bool {
2090 if let Some(bridge) = self.dom_bridge.clone() {
2091 // Check for style proxy objects first.
2092 if crate::dom_bridge::handle_style_set(&mut self.gc, &bridge, gc_ref, key, val) {
2093 return true;
2094 }
2095 // Then check for DOM node wrapper sets.
2096 crate::dom_bridge::handle_dom_set(&mut self.gc, &bridge, gc_ref, key, val)
2097 } else {
2098 false
2099 }
2100 }
2101
2102 /// Collect all GcRef values reachable from the mutator (roots for GC).
2103 fn collect_roots(&self) -> Vec<GcRef> {
2104 let mut roots = Vec::new();
2105 for val in &self.registers {
2106 if let Some(r) = val.gc_ref() {
2107 roots.push(r);
2108 }
2109 }
2110 for val in self.globals.values() {
2111 if let Some(r) = val.gc_ref() {
2112 roots.push(r);
2113 }
2114 }
2115 for frame in &self.frames {
2116 for &uv in &frame.upvalues {
2117 roots.push(uv);
2118 }
2119 }
2120 // Built-in prototype roots.
2121 if let Some(r) = self.object_prototype {
2122 roots.push(r);
2123 }
2124 if let Some(r) = self.array_prototype {
2125 roots.push(r);
2126 }
2127 if let Some(r) = self.string_prototype {
2128 roots.push(r);
2129 }
2130 if let Some(r) = self.number_prototype {
2131 roots.push(r);
2132 }
2133 if let Some(r) = self.boolean_prototype {
2134 roots.push(r);
2135 }
2136 if let Some(r) = self.promise_prototype {
2137 roots.push(r);
2138 }
2139 // DOM wrapper identity cache: keep cached wrappers alive so that the
2140 // same DOM node always returns the same JS object.
2141 if let Some(bridge) = &self.dom_bridge {
2142 for &wrapper_ref in bridge.node_wrappers.borrow().values() {
2143 roots.push(wrapper_ref);
2144 }
2145 // Event listener callbacks must also be GC roots.
2146 for listeners in bridge.event_listeners.borrow().values() {
2147 for listener in listeners {
2148 roots.push(listener.callback);
2149 }
2150 }
2151 }
2152 // Pending timer callbacks must be GC roots.
2153 roots.extend(crate::timers::timer_gc_roots());
2154 // Pending fetch promises must be GC roots.
2155 roots.extend(crate::fetch::fetch_gc_roots());
2156 roots
2157 }
2158
2159 /// Main dispatch loop.
2160 fn run(&mut self) -> Result<Value, RuntimeError> {
2161 loop {
2162 let fi = self.frames.len() - 1;
2163
2164 // Check if we've reached the end of bytecode.
2165 if self.frames[fi].ip >= self.frames[fi].func.code.len() {
2166 if self.frames.len() == 1 {
2167 self.frames.pop();
2168 return Ok(Value::Undefined);
2169 }
2170 let old = self.frames.pop().unwrap();
2171 self.registers[old.return_reg] = Value::Undefined;
2172 continue;
2173 }
2174
2175 // Instruction limit check (for test harnesses).
2176 if let Some(limit) = self.instruction_limit {
2177 self.instructions_executed += 1;
2178 if self.instructions_executed > limit {
2179 return Err(RuntimeError {
2180 kind: ErrorKind::Error,
2181 message: "instruction limit exceeded".into(),
2182 });
2183 }
2184 }
2185
2186 let opcode_byte = self.frames[fi].func.code[self.frames[fi].ip];
2187 self.frames[fi].ip += 1;
2188
2189 let Some(op) = Op::from_byte(opcode_byte) else {
2190 return Err(RuntimeError {
2191 kind: ErrorKind::Error,
2192 message: format!("unknown opcode: 0x{opcode_byte:02X}"),
2193 });
2194 };
2195
2196 match op {
2197 // ── Register loads ──────────────────────────────
2198 Op::LoadConst => {
2199 let dst = Self::read_u8(&mut self.frames[fi]);
2200 let idx = Self::read_u16(&mut self.frames[fi]) as usize;
2201 let base = self.frames[fi].base;
2202 let val = match &self.frames[fi].func.constants[idx] {
2203 Constant::Number(n) => Value::Number(*n),
2204 Constant::String(s) => Value::String(s.clone()),
2205 };
2206 self.registers[base + dst as usize] = val;
2207 }
2208 Op::LoadNull => {
2209 let dst = Self::read_u8(&mut self.frames[fi]);
2210 let base = self.frames[fi].base;
2211 self.registers[base + dst as usize] = Value::Null;
2212 }
2213 Op::LoadUndefined => {
2214 let dst = Self::read_u8(&mut self.frames[fi]);
2215 let base = self.frames[fi].base;
2216 self.registers[base + dst as usize] = Value::Undefined;
2217 }
2218 Op::LoadTrue => {
2219 let dst = Self::read_u8(&mut self.frames[fi]);
2220 let base = self.frames[fi].base;
2221 self.registers[base + dst as usize] = Value::Boolean(true);
2222 }
2223 Op::LoadFalse => {
2224 let dst = Self::read_u8(&mut self.frames[fi]);
2225 let base = self.frames[fi].base;
2226 self.registers[base + dst as usize] = Value::Boolean(false);
2227 }
2228 Op::LoadInt8 => {
2229 let dst = Self::read_u8(&mut self.frames[fi]);
2230 let val = Self::read_u8(&mut self.frames[fi]) as i8;
2231 let base = self.frames[fi].base;
2232 self.registers[base + dst as usize] = Value::Number(val as f64);
2233 }
2234 Op::Move => {
2235 let dst = Self::read_u8(&mut self.frames[fi]);
2236 let src = Self::read_u8(&mut self.frames[fi]);
2237 let base = self.frames[fi].base;
2238 let val = self.registers[base + src as usize].clone();
2239 self.registers[base + dst as usize] = val;
2240 }
2241
2242 // ── Global access ──────────────────────────────
2243 Op::LoadGlobal => {
2244 let dst = Self::read_u8(&mut self.frames[fi]);
2245 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
2246 let base = self.frames[fi].base;
2247 let name = &self.frames[fi].func.names[name_idx];
2248 let val = self.globals.get(name).cloned().unwrap_or(Value::Undefined);
2249 self.registers[base + dst as usize] = val;
2250 }
2251 Op::StoreGlobal => {
2252 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
2253 let src = Self::read_u8(&mut self.frames[fi]);
2254 let base = self.frames[fi].base;
2255 let name = self.frames[fi].func.names[name_idx].clone();
2256 let val = self.registers[base + src as usize].clone();
2257 self.globals.insert(name, val);
2258 }
2259
2260 // ── Arithmetic ─────────────────────────────────
2261 Op::Add => {
2262 let dst = Self::read_u8(&mut self.frames[fi]);
2263 let lhs_r = Self::read_u8(&mut self.frames[fi]);
2264 let rhs_r = Self::read_u8(&mut self.frames[fi]);
2265 let base = self.frames[fi].base;
2266 let result = add_values(
2267 &self.registers[base + lhs_r as usize],
2268 &self.registers[base + rhs_r as usize],
2269 &self.gc,
2270 );
2271 self.registers[base + dst as usize] = result;
2272 }
2273 Op::Sub => {
2274 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2275 let result = self.registers[base + lhs_r].to_number()
2276 - self.registers[base + rhs_r].to_number();
2277 self.registers[base + dst] = Value::Number(result);
2278 }
2279 Op::Mul => {
2280 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2281 let result = self.registers[base + lhs_r].to_number()
2282 * self.registers[base + rhs_r].to_number();
2283 self.registers[base + dst] = Value::Number(result);
2284 }
2285 Op::Div => {
2286 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2287 let result = self.registers[base + lhs_r].to_number()
2288 / self.registers[base + rhs_r].to_number();
2289 self.registers[base + dst] = Value::Number(result);
2290 }
2291 Op::Rem => {
2292 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2293 let result = self.registers[base + lhs_r].to_number()
2294 % self.registers[base + rhs_r].to_number();
2295 self.registers[base + dst] = Value::Number(result);
2296 }
2297 Op::Exp => {
2298 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2299 let result = self.registers[base + lhs_r]
2300 .to_number()
2301 .powf(self.registers[base + rhs_r].to_number());
2302 self.registers[base + dst] = Value::Number(result);
2303 }
2304 Op::Neg => {
2305 let dst = Self::read_u8(&mut self.frames[fi]);
2306 let src = Self::read_u8(&mut self.frames[fi]);
2307 let base = self.frames[fi].base;
2308 let result = -self.registers[base + src as usize].to_number();
2309 self.registers[base + dst as usize] = Value::Number(result);
2310 }
2311
2312 // ── Bitwise ────────────────────────────────────
2313 Op::BitAnd => {
2314 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2315 let a = to_int32(&self.registers[base + lhs_r]);
2316 let b = to_int32(&self.registers[base + rhs_r]);
2317 self.registers[base + dst] = Value::Number((a & b) as f64);
2318 }
2319 Op::BitOr => {
2320 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2321 let a = to_int32(&self.registers[base + lhs_r]);
2322 let b = to_int32(&self.registers[base + rhs_r]);
2323 self.registers[base + dst] = Value::Number((a | b) as f64);
2324 }
2325 Op::BitXor => {
2326 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2327 let a = to_int32(&self.registers[base + lhs_r]);
2328 let b = to_int32(&self.registers[base + rhs_r]);
2329 self.registers[base + dst] = Value::Number((a ^ b) as f64);
2330 }
2331 Op::ShiftLeft => {
2332 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2333 let a = to_int32(&self.registers[base + lhs_r]);
2334 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
2335 self.registers[base + dst] = Value::Number((a << b) as f64);
2336 }
2337 Op::ShiftRight => {
2338 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2339 let a = to_int32(&self.registers[base + lhs_r]);
2340 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
2341 self.registers[base + dst] = Value::Number((a >> b) as f64);
2342 }
2343 Op::UShiftRight => {
2344 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2345 let a = to_uint32(&self.registers[base + lhs_r]);
2346 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
2347 self.registers[base + dst] = Value::Number((a >> b) as f64);
2348 }
2349 Op::BitNot => {
2350 let dst = Self::read_u8(&mut self.frames[fi]);
2351 let src = Self::read_u8(&mut self.frames[fi]);
2352 let base = self.frames[fi].base;
2353 let result = !to_int32(&self.registers[base + src as usize]);
2354 self.registers[base + dst as usize] = Value::Number(result as f64);
2355 }
2356
2357 // ── Comparison ─────────────────────────────────
2358 Op::Eq => {
2359 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2360 let result =
2361 abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
2362 self.registers[base + dst] = Value::Boolean(result);
2363 }
2364 Op::StrictEq => {
2365 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2366 let result =
2367 strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
2368 self.registers[base + dst] = Value::Boolean(result);
2369 }
2370 Op::NotEq => {
2371 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2372 let result =
2373 !abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
2374 self.registers[base + dst] = Value::Boolean(result);
2375 }
2376 Op::StrictNotEq => {
2377 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2378 let result =
2379 !strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
2380 self.registers[base + dst] = Value::Boolean(result);
2381 }
2382 Op::LessThan => {
2383 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2384 let result = abstract_relational(
2385 &self.registers[base + lhs_r],
2386 &self.registers[base + rhs_r],
2387 |ord| ord == std::cmp::Ordering::Less,
2388 );
2389 self.registers[base + dst] = Value::Boolean(result);
2390 }
2391 Op::LessEq => {
2392 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2393 let result = abstract_relational(
2394 &self.registers[base + lhs_r],
2395 &self.registers[base + rhs_r],
2396 |ord| ord != std::cmp::Ordering::Greater,
2397 );
2398 self.registers[base + dst] = Value::Boolean(result);
2399 }
2400 Op::GreaterThan => {
2401 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2402 let result = abstract_relational(
2403 &self.registers[base + lhs_r],
2404 &self.registers[base + rhs_r],
2405 |ord| ord == std::cmp::Ordering::Greater,
2406 );
2407 self.registers[base + dst] = Value::Boolean(result);
2408 }
2409 Op::GreaterEq => {
2410 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
2411 let result = abstract_relational(
2412 &self.registers[base + lhs_r],
2413 &self.registers[base + rhs_r],
2414 |ord| ord != std::cmp::Ordering::Less,
2415 );
2416 self.registers[base + dst] = Value::Boolean(result);
2417 }
2418
2419 // ── Logical / unary ────────────────────────────
2420 Op::LogicalNot => {
2421 let dst = Self::read_u8(&mut self.frames[fi]);
2422 let src = Self::read_u8(&mut self.frames[fi]);
2423 let base = self.frames[fi].base;
2424 let result = !self.registers[base + src as usize].to_boolean();
2425 self.registers[base + dst as usize] = Value::Boolean(result);
2426 }
2427 Op::TypeOf => {
2428 let dst = Self::read_u8(&mut self.frames[fi]);
2429 let src = Self::read_u8(&mut self.frames[fi]);
2430 let base = self.frames[fi].base;
2431 let t = self.registers[base + src as usize].type_of();
2432 self.registers[base + dst as usize] = Value::String(t.to_string());
2433 }
2434 Op::InstanceOf => {
2435 let dst = Self::read_u8(&mut self.frames[fi]);
2436 let lhs_r = Self::read_u8(&mut self.frames[fi]);
2437 let rhs_r = Self::read_u8(&mut self.frames[fi]);
2438 let base = self.frames[fi].base;
2439 let result = match (
2440 &self.registers[base + lhs_r as usize],
2441 &self.registers[base + rhs_r as usize],
2442 ) {
2443 (Value::Object(obj_ref), Value::Function(ctor_ref)) => {
2444 gc_instanceof(&self.gc, *obj_ref, *ctor_ref)
2445 }
2446 (_, Value::Function(_)) => false,
2447 _ => {
2448 return Err(RuntimeError::type_error(
2449 "Right-hand side of instanceof is not callable",
2450 ));
2451 }
2452 };
2453 self.registers[base + dst as usize] = Value::Boolean(result);
2454 }
2455 Op::In => {
2456 let dst = Self::read_u8(&mut self.frames[fi]);
2457 let key_r = Self::read_u8(&mut self.frames[fi]);
2458 let obj_r = Self::read_u8(&mut self.frames[fi]);
2459 let base = self.frames[fi].base;
2460 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
2461 let result = match self.registers[base + obj_r as usize] {
2462 Value::Object(gc_ref) => {
2463 Value::Boolean(gc_has_property(&self.gc, gc_ref, &key))
2464 }
2465 _ => {
2466 return Err(RuntimeError::type_error(
2467 "Cannot use 'in' operator to search for property in non-object",
2468 ));
2469 }
2470 };
2471 self.registers[base + dst as usize] = result;
2472 }
2473 Op::Void => {
2474 let dst = Self::read_u8(&mut self.frames[fi]);
2475 let _src = Self::read_u8(&mut self.frames[fi]);
2476 let base = self.frames[fi].base;
2477 self.registers[base + dst as usize] = Value::Undefined;
2478 }
2479
2480 // ── Control flow ───────────────────────────────
2481 Op::Jump => {
2482 let offset = Self::read_i32(&mut self.frames[fi]);
2483 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
2484 }
2485 Op::JumpIfTrue => {
2486 let reg = Self::read_u8(&mut self.frames[fi]);
2487 let offset = Self::read_i32(&mut self.frames[fi]);
2488 let base = self.frames[fi].base;
2489 if self.registers[base + reg as usize].to_boolean() {
2490 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
2491 }
2492 }
2493 Op::JumpIfFalse => {
2494 let reg = Self::read_u8(&mut self.frames[fi]);
2495 let offset = Self::read_i32(&mut self.frames[fi]);
2496 let base = self.frames[fi].base;
2497 if !self.registers[base + reg as usize].to_boolean() {
2498 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
2499 }
2500 }
2501 Op::JumpIfNullish => {
2502 let reg = Self::read_u8(&mut self.frames[fi]);
2503 let offset = Self::read_i32(&mut self.frames[fi]);
2504 let base = self.frames[fi].base;
2505 if self.registers[base + reg as usize].is_nullish() {
2506 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
2507 }
2508 }
2509
2510 // ── Functions / calls ──────────────────────────
2511 Op::Call => {
2512 let dst = Self::read_u8(&mut self.frames[fi]);
2513 let func_r = Self::read_u8(&mut self.frames[fi]);
2514 let args_start = Self::read_u8(&mut self.frames[fi]);
2515 let arg_count = Self::read_u8(&mut self.frames[fi]);
2516 let base = self.frames[fi].base;
2517
2518 // Extract function GcRef.
2519 let func_gc_ref = match self.registers[base + func_r as usize] {
2520 Value::Function(r) => r,
2521 _ => {
2522 let desc =
2523 self.registers[base + func_r as usize].to_js_string(&self.gc);
2524 let err = RuntimeError::type_error(format!("{desc} is not a function"));
2525 let err_val = err.to_value(&mut self.gc);
2526 if !self.handle_exception(err_val) {
2527 return Err(err);
2528 }
2529 continue;
2530 }
2531 };
2532
2533 // Collect arguments.
2534 let mut args = Vec::with_capacity(arg_count as usize);
2535 for i in 0..arg_count {
2536 args.push(self.registers[base + (args_start + i) as usize].clone());
2537 }
2538
2539 // Read function data from GC (scoped borrow).
2540 let call_info = {
2541 match self.gc.get(func_gc_ref) {
2542 Some(HeapObject::Function(fdata)) => match &fdata.kind {
2543 FunctionKind::Native(n) => CallInfo::Native(n.callback),
2544 FunctionKind::Bytecode(bc) => {
2545 CallInfo::Bytecode(bc.func.clone(), fdata.upvalues.clone())
2546 }
2547 },
2548 _ => {
2549 let err = RuntimeError::type_error("not a function");
2550 let err_val = err.to_value(&mut self.gc);
2551 if !self.handle_exception(err_val) {
2552 return Err(err);
2553 }
2554 continue;
2555 }
2556 }
2557 };
2558
2559 match call_info {
2560 CallInfo::Native(callback) => {
2561 // Set async resume data if the function has it.
2562 if let Some(HeapObject::Function(f)) = self.gc.get(func_gc_ref) {
2563 if let Some(prop) = f.properties.get("__async_data__") {
2564 if let Value::Object(data_ref) = &prop.value {
2565 ASYNC_RESUME_DATA.with(|cell| cell.set(Some(*data_ref)));
2566 }
2567 }
2568 }
2569
2570 let this = self
2571 .globals
2572 .get("this")
2573 .cloned()
2574 .unwrap_or(Value::Undefined);
2575 let dom_ref = self.dom_bridge.as_deref();
2576 let mut ctx = NativeContext {
2577 gc: &mut self.gc,
2578 this,
2579 console_output: &*self.console_output,
2580 dom_bridge: dom_ref,
2581 };
2582 match callback(&args, &mut ctx) {
2583 Ok(val) => {
2584 // Check if this is a generator resume request.
2585 if let Value::Object(r) = &val {
2586 let is_resume = matches!(
2587 gc_get_property(&self.gc, *r, "__generator_resume__"),
2588 Value::Boolean(true)
2589 );
2590 if is_resume {
2591 let gen_ref = match gc_get_property(
2592 &self.gc,
2593 *r,
2594 "__gen_ref__",
2595 ) {
2596 Value::Object(gr) => gr,
2597 _ => {
2598 self.registers[base + dst as usize] =
2599 Value::Undefined;
2600 continue;
2601 }
2602 };
2603 let send_val =
2604 gc_get_property(&self.gc, *r, "__send_value__");
2605 let kind = match gc_get_property(
2606 &self.gc,
2607 *r,
2608 "__resume_kind__",
2609 ) {
2610 Value::String(s) => s,
2611 _ => "next".to_string(),
2612 };
2613
2614 match kind.as_str() {
2615 "next" => {
2616 match self.run_generator(gen_ref, send_val) {
2617 Ok(result) => {
2618 self.registers[base + dst as usize] =
2619 result;
2620 }
2621 Err(err) => {
2622 let err_val =
2623 err.to_value(&mut self.gc);
2624 if !self.handle_exception(err_val) {
2625 return Err(err);
2626 }
2627 }
2628 }
2629 }
2630 "return" => {
2631 // Force the generator to complete.
2632 if let Some(HeapObject::Generator(gen)) =
2633 self.gc.get_mut(gen_ref)
2634 {
2635 gen.state = GeneratorState::Completed;
2636 }
2637 let result =
2638 self.make_iterator_result(send_val, true);
2639 self.registers[base + dst as usize] = result;
2640 }
2641 "throw" => {
2642 // Mark generator as completed and throw.
2643 if let Some(HeapObject::Generator(gen)) =
2644 self.gc.get_mut(gen_ref)
2645 {
2646 gen.state = GeneratorState::Completed;
2647 }
2648 if !self.handle_exception(send_val) {
2649 return Err(RuntimeError::type_error(
2650 "Generator throw",
2651 ));
2652 }
2653 }
2654 _ => {
2655 self.registers[base + dst as usize] =
2656 Value::Undefined;
2657 }
2658 }
2659 continue;
2660 }
2661
2662 // Check for async resume marker.
2663 let is_async_resume = matches!(
2664 gc_get_property(&self.gc, *r, "__async_resume__"),
2665 Value::Boolean(true)
2666 );
2667 if is_async_resume {
2668 let ar_gen = match gc_get_property(
2669 &self.gc,
2670 *r,
2671 "__gen_ref__",
2672 ) {
2673 Value::Object(gr) => gr,
2674 _ => {
2675 self.registers[base + dst as usize] =
2676 Value::Undefined;
2677 continue;
2678 }
2679 };
2680 let ar_promise = match gc_get_property(
2681 &self.gc,
2682 *r,
2683 "__result_promise__",
2684 ) {
2685 Value::Object(pr) => pr,
2686 _ => {
2687 self.registers[base + dst as usize] =
2688 Value::Undefined;
2689 continue;
2690 }
2691 };
2692 let ar_throw = matches!(
2693 gc_get_property(&self.gc, *r, "__is_throw__"),
2694 Value::Boolean(true)
2695 );
2696 let ar_value =
2697 gc_get_property(&self.gc, *r, "__value__");
2698 self.drive_async_step(
2699 ar_gen, ar_promise, ar_value, ar_throw,
2700 );
2701 self.registers[base + dst as usize] = Value::Undefined;
2702 continue;
2703 }
2704
2705 // Check for async generator resume marker.
2706 let is_ag_resume = matches!(
2707 gc_get_property(
2708 &self.gc,
2709 *r,
2710 "__async_generator_resume__"
2711 ),
2712 Value::Boolean(true)
2713 );
2714 if is_ag_resume {
2715 let ag_gen = match gc_get_property(
2716 &self.gc,
2717 *r,
2718 "__gen_ref__",
2719 ) {
2720 Value::Object(gr) => gr,
2721 _ => {
2722 self.registers[base + dst as usize] =
2723 Value::Undefined;
2724 continue;
2725 }
2726 };
2727 let ag_send =
2728 gc_get_property(&self.gc, *r, "__send_value__");
2729 let ag_kind = match gc_get_property(
2730 &self.gc,
2731 *r,
2732 "__resume_kind__",
2733 ) {
2734 Value::String(s) => s,
2735 _ => "next".to_string(),
2736 };
2737
2738 let promise =
2739 crate::builtins::create_promise_object_pub(
2740 &mut self.gc,
2741 );
2742
2743 match ag_kind.as_str() {
2744 "next" => {
2745 match self.run_generator(ag_gen, ag_send) {
2746 Ok(iter_result) => {
2747 crate::builtins::resolve_promise_internal(
2748 &mut self.gc,
2749 promise,
2750 iter_result,
2751 );
2752 }
2753 Err(err) => {
2754 let reason = err.to_value(&mut self.gc);
2755 crate::builtins::reject_promise_internal(
2756 &mut self.gc,
2757 promise,
2758 reason,
2759 );
2760 }
2761 }
2762 }
2763 "return" => {
2764 if let Some(HeapObject::Generator(gen)) =
2765 self.gc.get_mut(ag_gen)
2766 {
2767 gen.state = GeneratorState::Completed;
2768 }
2769 let result =
2770 self.make_iterator_result(ag_send, true);
2771 crate::builtins::resolve_promise_internal(
2772 &mut self.gc,
2773 promise,
2774 result,
2775 );
2776 }
2777 _ => {}
2778 }
2779 self.registers[base + dst as usize] =
2780 Value::Object(promise);
2781 continue;
2782 }
2783
2784 // Check for event dispatch marker.
2785 let is_event_dispatch = matches!(
2786 gc_get_property(&self.gc, *r, "__event_dispatch__"),
2787 Value::Boolean(true)
2788 );
2789 if is_event_dispatch {
2790 let target_idx = match gc_get_property(
2791 &self.gc,
2792 *r,
2793 "__target_id__",
2794 ) {
2795 Value::Number(n) => n as usize,
2796 _ => {
2797 self.registers[base + dst as usize] =
2798 Value::Boolean(true);
2799 continue;
2800 }
2801 };
2802 let evt_ref = match gc_get_property(
2803 &self.gc,
2804 *r,
2805 "__event_ref__",
2806 ) {
2807 Value::Object(er) => er,
2808 _ => {
2809 self.registers[base + dst as usize] =
2810 Value::Boolean(true);
2811 continue;
2812 }
2813 };
2814 let result = crate::dom_bridge::run_event_dispatch(
2815 self, target_idx, evt_ref,
2816 );
2817 self.registers[base + dst as usize] = result;
2818 continue;
2819 }
2820 }
2821 self.registers[base + dst as usize] = val;
2822 }
2823 Err(err) => {
2824 let err_val = err.to_value(&mut self.gc);
2825 if !self.handle_exception(err_val) {
2826 return Err(err);
2827 }
2828 }
2829 }
2830 }
2831 CallInfo::Bytecode(callee_func, callee_upvalues) => {
2832 // Async function: create generator + promise, drive async.
2833 if callee_func.is_async && !callee_func.is_generator {
2834 let gen_ref =
2835 self.create_raw_generator(callee_func, callee_upvalues, &args);
2836 let result_promise =
2837 crate::builtins::create_promise_object_pub(&mut self.gc);
2838 self.drive_async_step(
2839 gen_ref,
2840 result_promise,
2841 Value::Undefined,
2842 false,
2843 );
2844 self.registers[base + dst as usize] = Value::Object(result_promise);
2845 continue;
2846 }
2847
2848 // Async generator function: create async generator wrapper.
2849 if callee_func.is_async && callee_func.is_generator {
2850 let gen_ref =
2851 self.create_raw_generator(callee_func, callee_upvalues, &args);
2852 let wrapper = self.create_async_generator_wrapper(gen_ref);
2853 self.registers[base + dst as usize] = Value::Object(wrapper);
2854 continue;
2855 }
2856
2857 // Generator function: create a generator object instead of executing.
2858 if callee_func.is_generator {
2859 let gen_obj = self.create_generator_object(
2860 callee_func,
2861 callee_upvalues,
2862 &args,
2863 );
2864 self.registers[base + dst as usize] = Value::Object(gen_obj);
2865 continue;
2866 }
2867
2868 if self.frames.len() >= MAX_CALL_DEPTH {
2869 let err =
2870 RuntimeError::range_error("Maximum call stack size exceeded");
2871 let err_val = err.to_value(&mut self.gc);
2872 if !self.handle_exception(err_val) {
2873 return Err(err);
2874 }
2875 continue;
2876 }
2877
2878 let callee_base = base + self.frames[fi].func.register_count as usize;
2879 let callee_regs = callee_func.register_count as usize;
2880 self.ensure_registers(callee_base + callee_regs);
2881
2882 // Copy arguments into callee's registers.
2883 for i in 0..callee_func.param_count.min(arg_count) {
2884 self.registers[callee_base + i as usize] = args[i as usize].clone();
2885 }
2886 // Fill remaining params with undefined.
2887 for i in arg_count..callee_func.param_count {
2888 self.registers[callee_base + i as usize] = Value::Undefined;
2889 }
2890
2891 self.frames.push(CallFrame {
2892 func: callee_func,
2893 ip: 0,
2894 base: callee_base,
2895 return_reg: base + dst as usize,
2896 exception_handlers: Vec::new(),
2897 upvalues: callee_upvalues,
2898 });
2899 }
2900 }
2901 }
2902 Op::Return => {
2903 let reg = Self::read_u8(&mut self.frames[fi]);
2904 let base = self.frames[fi].base;
2905 let val = self.registers[base + reg as usize].clone();
2906
2907 if self.frames.len() == 1 {
2908 self.frames.pop();
2909 return Ok(val);
2910 }
2911
2912 let old = self.frames.pop().unwrap();
2913 self.registers[old.return_reg] = val;
2914 }
2915 Op::Throw => {
2916 let reg = Self::read_u8(&mut self.frames[fi]);
2917 let base = self.frames[fi].base;
2918 let val = self.registers[base + reg as usize].clone();
2919
2920 if !self.handle_exception(val) {
2921 let msg = self.registers[base + reg as usize].to_js_string(&self.gc);
2922 return Err(RuntimeError {
2923 kind: ErrorKind::Error,
2924 message: msg,
2925 });
2926 }
2927 }
2928 Op::CreateClosure => {
2929 let dst = Self::read_u8(&mut self.frames[fi]);
2930 let func_idx = Self::read_u16(&mut self.frames[fi]) as usize;
2931 let base = self.frames[fi].base;
2932 let inner_func = self.frames[fi].func.functions[func_idx].clone();
2933 let name = inner_func.name.clone();
2934
2935 // Resolve upvalues from the parent scope.
2936 let mut upvalues = Vec::with_capacity(inner_func.upvalue_defs.len());
2937 for def in &inner_func.upvalue_defs {
2938 let cell_ref = if def.is_local {
2939 // Parent has a cell in register `def.index`.
2940 match &self.registers[base + def.index as usize] {
2941 Value::Object(r) => *r,
2942 _ => {
2943 return Err(RuntimeError {
2944 kind: ErrorKind::Error,
2945 message:
2946 "CreateClosure: upvalue register does not hold a cell"
2947 .into(),
2948 });
2949 }
2950 }
2951 } else {
2952 // Transitive: parent's own upvalue at `def.index`.
2953 self.frames[fi].upvalues[def.index as usize]
2954 };
2955 upvalues.push(cell_ref);
2956 }
2957
2958 // Create a .prototype object for the function (for instanceof).
2959 let proto_obj = self.gc.alloc(HeapObject::Object(ObjectData::new()));
2960 let gc_ref = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
2961 name,
2962 kind: FunctionKind::Bytecode(BytecodeFunc { func: inner_func }),
2963 prototype_obj: Some(proto_obj),
2964 properties: HashMap::new(),
2965 upvalues,
2966 })));
2967 // Set .prototype.constructor = this function.
2968 if let Some(HeapObject::Object(data)) = self.gc.get_mut(proto_obj) {
2969 data.properties.insert(
2970 "constructor".to_string(),
2971 Property {
2972 value: Value::Function(gc_ref),
2973 writable: true,
2974 enumerable: false,
2975 configurable: true,
2976 },
2977 );
2978 }
2979 self.registers[base + dst as usize] = Value::Function(gc_ref);
2980
2981 // Trigger GC if needed.
2982 if self.gc.should_collect() {
2983 let roots = self.collect_roots();
2984 self.gc.collect(&roots);
2985 }
2986 }
2987
2988 // ── Object / property ──────────────────────────
2989 Op::GetProperty => {
2990 let dst = Self::read_u8(&mut self.frames[fi]);
2991 let obj_r = Self::read_u8(&mut self.frames[fi]);
2992 let key_r = Self::read_u8(&mut self.frames[fi]);
2993 let base = self.frames[fi].base;
2994 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
2995 // Save gc_ref for DOM interception.
2996 let obj_gc_ref = match self.registers[base + obj_r as usize] {
2997 Value::Object(r) | Value::Function(r) => Some(r),
2998 _ => None,
2999 };
3000 let val = match self.registers[base + obj_r as usize] {
3001 Value::Object(gc_ref) | Value::Function(gc_ref) => {
3002 gc_get_property(&self.gc, gc_ref, &key)
3003 }
3004 Value::String(ref s) => {
3005 let v = string_get_property(s, &key);
3006 if matches!(v, Value::Undefined) {
3007 self.string_prototype
3008 .map(|p| gc_get_property(&self.gc, p, &key))
3009 .unwrap_or(Value::Undefined)
3010 } else {
3011 v
3012 }
3013 }
3014 Value::Number(_) => self
3015 .number_prototype
3016 .map(|p| gc_get_property(&self.gc, p, &key))
3017 .unwrap_or(Value::Undefined),
3018 Value::Boolean(_) => self
3019 .boolean_prototype
3020 .map(|p| gc_get_property(&self.gc, p, &key))
3021 .unwrap_or(Value::Undefined),
3022 _ => Value::Undefined,
3023 };
3024 // DOM dynamic property interception.
3025 let val = match val {
3026 Value::Undefined if obj_gc_ref.is_some() => self
3027 .resolve_dom_property(obj_gc_ref.unwrap(), &key)
3028 .unwrap_or(Value::Undefined),
3029 other => other,
3030 };
3031 self.registers[base + dst as usize] = val;
3032 }
3033 Op::SetProperty => {
3034 let obj_r = Self::read_u8(&mut self.frames[fi]);
3035 let key_r = Self::read_u8(&mut self.frames[fi]);
3036 let val_r = Self::read_u8(&mut self.frames[fi]);
3037 let base = self.frames[fi].base;
3038 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
3039 let val = self.registers[base + val_r as usize].clone();
3040 let obj_gc = match self.registers[base + obj_r as usize] {
3041 Value::Object(r) => Some(r),
3042 Value::Function(r) => Some(r),
3043 _ => None,
3044 };
3045 // DOM property set interception.
3046 let dom_handled = obj_gc
3047 .map(|r| self.handle_dom_property_set(r, &key, &val))
3048 .unwrap_or(false);
3049 if !dom_handled {
3050 if let Some(gc_ref) = obj_gc {
3051 match self.gc.get_mut(gc_ref) {
3052 Some(HeapObject::Object(data)) => {
3053 if let Some(prop) = data.properties.get_mut(&key) {
3054 if prop.writable {
3055 prop.value = val;
3056 }
3057 } else {
3058 data.properties.insert(key, Property::data(val));
3059 }
3060 }
3061 Some(HeapObject::Function(fdata)) => {
3062 if let Some(prop) = fdata.properties.get_mut(&key) {
3063 if prop.writable {
3064 prop.value = val;
3065 }
3066 } else {
3067 fdata.properties.insert(key, Property::data(val));
3068 }
3069 }
3070 _ => {}
3071 }
3072 }
3073 }
3074 }
3075 Op::CreateObject => {
3076 let dst = Self::read_u8(&mut self.frames[fi]);
3077 let base = self.frames[fi].base;
3078 let mut obj = ObjectData::new();
3079 obj.prototype = self.object_prototype;
3080 let gc_ref = self.gc.alloc(HeapObject::Object(obj));
3081 self.registers[base + dst as usize] = Value::Object(gc_ref);
3082
3083 if self.gc.should_collect() {
3084 let roots = self.collect_roots();
3085 self.gc.collect(&roots);
3086 }
3087 }
3088 Op::CreateArray => {
3089 let dst = Self::read_u8(&mut self.frames[fi]);
3090 let base = self.frames[fi].base;
3091 let mut obj = ObjectData::new();
3092 obj.prototype = self.array_prototype;
3093 obj.properties.insert(
3094 "length".to_string(),
3095 Property {
3096 value: Value::Number(0.0),
3097 writable: true,
3098 enumerable: false,
3099 configurable: false,
3100 },
3101 );
3102 let gc_ref = self.gc.alloc(HeapObject::Object(obj));
3103 self.registers[base + dst as usize] = Value::Object(gc_ref);
3104
3105 if self.gc.should_collect() {
3106 let roots = self.collect_roots();
3107 self.gc.collect(&roots);
3108 }
3109 }
3110 Op::GetPropertyByName => {
3111 let dst = Self::read_u8(&mut self.frames[fi]);
3112 let obj_r = Self::read_u8(&mut self.frames[fi]);
3113 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
3114 let base = self.frames[fi].base;
3115 let key = self.frames[fi].func.names[name_idx].clone();
3116 let obj_gc_ref = match self.registers[base + obj_r as usize] {
3117 Value::Object(r) | Value::Function(r) => Some(r),
3118 _ => None,
3119 };
3120 let val = match self.registers[base + obj_r as usize] {
3121 Value::Object(gc_ref) | Value::Function(gc_ref) => {
3122 gc_get_property(&self.gc, gc_ref, &key)
3123 }
3124 Value::String(ref s) => {
3125 let v = string_get_property(s, &key);
3126 if matches!(v, Value::Undefined) {
3127 self.string_prototype
3128 .map(|p| gc_get_property(&self.gc, p, &key))
3129 .unwrap_or(Value::Undefined)
3130 } else {
3131 v
3132 }
3133 }
3134 Value::Number(_) => self
3135 .number_prototype
3136 .map(|p| gc_get_property(&self.gc, p, &key))
3137 .unwrap_or(Value::Undefined),
3138 Value::Boolean(_) => self
3139 .boolean_prototype
3140 .map(|p| gc_get_property(&self.gc, p, &key))
3141 .unwrap_or(Value::Undefined),
3142 _ => Value::Undefined,
3143 };
3144 // DOM dynamic property interception.
3145 let val = match val {
3146 Value::Undefined if obj_gc_ref.is_some() => self
3147 .resolve_dom_property(obj_gc_ref.unwrap(), &key)
3148 .unwrap_or(Value::Undefined),
3149 other => other,
3150 };
3151 self.registers[base + dst as usize] = val;
3152 }
3153 Op::SetPropertyByName => {
3154 let obj_r = Self::read_u8(&mut self.frames[fi]);
3155 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
3156 let val_r = Self::read_u8(&mut self.frames[fi]);
3157 let base = self.frames[fi].base;
3158 let key = self.frames[fi].func.names[name_idx].clone();
3159 let val = self.registers[base + val_r as usize].clone();
3160 let obj_gc = match self.registers[base + obj_r as usize] {
3161 Value::Object(r) => Some(r),
3162 Value::Function(r) => Some(r),
3163 _ => None,
3164 };
3165 let dom_handled = obj_gc
3166 .map(|r| self.handle_dom_property_set(r, &key, &val))
3167 .unwrap_or(false);
3168 if !dom_handled {
3169 if let Some(gc_ref) = obj_gc {
3170 match self.gc.get_mut(gc_ref) {
3171 Some(HeapObject::Object(data)) => {
3172 if let Some(prop) = data.properties.get_mut(&key) {
3173 if prop.writable {
3174 prop.value = val;
3175 }
3176 } else {
3177 data.properties.insert(key, Property::data(val));
3178 }
3179 }
3180 Some(HeapObject::Function(fdata)) => {
3181 if let Some(prop) = fdata.properties.get_mut(&key) {
3182 if prop.writable {
3183 prop.value = val;
3184 }
3185 } else {
3186 fdata.properties.insert(key, Property::data(val));
3187 }
3188 }
3189 _ => {}
3190 }
3191 }
3192 }
3193 }
3194
3195 // ── Misc ───────────────────────────────────────
3196 Op::Delete => {
3197 let dst = Self::read_u8(&mut self.frames[fi]);
3198 let obj_r = Self::read_u8(&mut self.frames[fi]);
3199 let key_r = Self::read_u8(&mut self.frames[fi]);
3200 let base = self.frames[fi].base;
3201 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
3202 let result =
3203 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] {
3204 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
3205 match data.properties.get(&key) {
3206 Some(prop) if !prop.configurable => false,
3207 Some(_) => {
3208 data.properties.remove(&key);
3209 true
3210 }
3211 None => true,
3212 }
3213 } else {
3214 true
3215 }
3216 } else {
3217 true
3218 };
3219 self.registers[base + dst as usize] = Value::Boolean(result);
3220 }
3221 Op::ForInInit => {
3222 let dst = Self::read_u8(&mut self.frames[fi]);
3223 let obj_r = Self::read_u8(&mut self.frames[fi]);
3224 let base = self.frames[fi].base;
3225 let keys = match self.registers[base + obj_r as usize] {
3226 Value::Object(gc_ref) => gc_enumerate_keys(&self.gc, gc_ref),
3227 _ => Vec::new(),
3228 };
3229 // Store keys as an array object.
3230 let mut arr = ObjectData::new();
3231 for (i, key) in keys.iter().enumerate() {
3232 arr.properties
3233 .insert(i.to_string(), Property::data(Value::String(key.clone())));
3234 }
3235 arr.properties.insert(
3236 "length".to_string(),
3237 Property {
3238 value: Value::Number(keys.len() as f64),
3239 writable: true,
3240 enumerable: false,
3241 configurable: false,
3242 },
3243 );
3244 let gc_ref = self.gc.alloc(HeapObject::Object(arr));
3245 self.registers[base + dst as usize] = Value::Object(gc_ref);
3246 }
3247 Op::ForInNext => {
3248 let dst_val = Self::read_u8(&mut self.frames[fi]);
3249 let dst_done = Self::read_u8(&mut self.frames[fi]);
3250 let keys_r = Self::read_u8(&mut self.frames[fi]);
3251 let idx_r = Self::read_u8(&mut self.frames[fi]);
3252 let base = self.frames[fi].base;
3253 let idx = self.registers[base + idx_r as usize].to_number() as usize;
3254 let len = match self.registers[base + keys_r as usize] {
3255 Value::Object(gc_ref) => {
3256 gc_get_property(&self.gc, gc_ref, "length").to_number() as usize
3257 }
3258 _ => 0,
3259 };
3260 if idx >= len {
3261 self.registers[base + dst_done as usize] = Value::Boolean(true);
3262 self.registers[base + dst_val as usize] = Value::Undefined;
3263 } else {
3264 let key_str = idx.to_string();
3265 let key = match self.registers[base + keys_r as usize] {
3266 Value::Object(gc_ref) => gc_get_property(&self.gc, gc_ref, &key_str),
3267 _ => Value::Undefined,
3268 };
3269 self.registers[base + dst_val as usize] = key;
3270 self.registers[base + dst_done as usize] = Value::Boolean(false);
3271 }
3272 }
3273 Op::SetPrototype => {
3274 let obj_r = Self::read_u8(&mut self.frames[fi]);
3275 let proto_r = Self::read_u8(&mut self.frames[fi]);
3276 let base = self.frames[fi].base;
3277 let proto = match &self.registers[base + proto_r as usize] {
3278 Value::Object(r) => Some(*r),
3279 Value::Null => None,
3280 _ => None,
3281 };
3282 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] {
3283 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
3284 data.prototype = proto;
3285 }
3286 }
3287 }
3288 Op::GetPrototype => {
3289 let dst = Self::read_u8(&mut self.frames[fi]);
3290 let obj_r = Self::read_u8(&mut self.frames[fi]);
3291 let base = self.frames[fi].base;
3292 let proto = match self.registers[base + obj_r as usize] {
3293 Value::Object(gc_ref) => match self.gc.get(gc_ref) {
3294 Some(HeapObject::Object(data)) => {
3295 data.prototype.map(Value::Object).unwrap_or(Value::Null)
3296 }
3297 _ => Value::Null,
3298 },
3299 _ => Value::Null,
3300 };
3301 self.registers[base + dst as usize] = proto;
3302 }
3303
3304 // ── Exception handling ─────────────────────────────
3305 Op::PushExceptionHandler => {
3306 let catch_reg = Self::read_u8(&mut self.frames[fi]);
3307 let offset = Self::read_i32(&mut self.frames[fi]);
3308 let catch_ip = (self.frames[fi].ip as i32 + offset) as usize;
3309 self.frames[fi].exception_handlers.push(ExceptionHandler {
3310 catch_ip,
3311 catch_reg,
3312 });
3313 }
3314 Op::PopExceptionHandler => {
3315 self.frames[fi].exception_handlers.pop();
3316 }
3317
3318 // ── Closure / upvalue ops ─────────────────────────
3319 Op::NewCell => {
3320 let dst = Self::read_u8(&mut self.frames[fi]);
3321 let base = self.frames[fi].base;
3322 let cell = self.gc.alloc(HeapObject::Cell(Value::Undefined));
3323 self.registers[base + dst as usize] = Value::Object(cell);
3324
3325 if self.gc.should_collect() {
3326 let roots = self.collect_roots();
3327 self.gc.collect(&roots);
3328 }
3329 }
3330 Op::CellLoad => {
3331 let dst = Self::read_u8(&mut self.frames[fi]);
3332 let cell_reg = Self::read_u8(&mut self.frames[fi]);
3333 let base = self.frames[fi].base;
3334 let cell_ref = match &self.registers[base + cell_reg as usize] {
3335 Value::Object(r) => *r,
3336 _ => {
3337 return Err(RuntimeError {
3338 kind: ErrorKind::Error,
3339 message: "CellLoad: register does not hold a cell".into(),
3340 });
3341 }
3342 };
3343 let val = match self.gc.get(cell_ref) {
3344 Some(HeapObject::Cell(v)) => v.clone(),
3345 _ => Value::Undefined,
3346 };
3347 self.registers[base + dst as usize] = val;
3348 }
3349 Op::CellStore => {
3350 let cell_reg = Self::read_u8(&mut self.frames[fi]);
3351 let src = Self::read_u8(&mut self.frames[fi]);
3352 let base = self.frames[fi].base;
3353 let cell_ref = match &self.registers[base + cell_reg as usize] {
3354 Value::Object(r) => *r,
3355 _ => {
3356 return Err(RuntimeError {
3357 kind: ErrorKind::Error,
3358 message: "CellStore: register does not hold a cell".into(),
3359 });
3360 }
3361 };
3362 let val = self.registers[base + src as usize].clone();
3363 if let Some(HeapObject::Cell(cell_val)) = self.gc.get_mut(cell_ref) {
3364 *cell_val = val;
3365 }
3366 }
3367 Op::LoadUpvalue => {
3368 let dst = Self::read_u8(&mut self.frames[fi]);
3369 let idx = Self::read_u8(&mut self.frames[fi]) as usize;
3370 let base = self.frames[fi].base;
3371 let cell_ref = self.frames[fi].upvalues[idx];
3372 let val = match self.gc.get(cell_ref) {
3373 Some(HeapObject::Cell(v)) => v.clone(),
3374 _ => Value::Undefined,
3375 };
3376 self.registers[base + dst as usize] = val;
3377 }
3378 Op::StoreUpvalue => {
3379 let idx = Self::read_u8(&mut self.frames[fi]) as usize;
3380 let src = Self::read_u8(&mut self.frames[fi]);
3381 let base = self.frames[fi].base;
3382 let val = self.registers[base + src as usize].clone();
3383 let cell_ref = self.frames[fi].upvalues[idx];
3384 if let Some(HeapObject::Cell(cell_val)) = self.gc.get_mut(cell_ref) {
3385 *cell_val = val;
3386 }
3387 }
3388
3389 // ── Iterator / generator ─────────────────────────
3390 Op::Yield => {
3391 let _dst = Self::read_u8(&mut self.frames[fi]);
3392 let src = Self::read_u8(&mut self.frames[fi]);
3393 let base = self.frames[fi].base;
3394 let yield_val = self.registers[base + src as usize].clone();
3395
3396 // Save the generator's state.
3397 let frame = &self.frames[fi];
3398 let gen_ref = match self.registers.get(frame.return_reg) {
3399 Some(Value::Object(r)) => *r,
3400 _ => {
3401 return Err(RuntimeError {
3402 kind: ErrorKind::Error,
3403 message: "Yield outside generator".into(),
3404 });
3405 }
3406 };
3407
3408 // Save registers and IP into the generator object.
3409 let saved_ip = self.frames[fi].ip;
3410 let saved_base = self.frames[fi].base;
3411 let reg_count = self.frames[fi].func.register_count as usize;
3412 let saved_regs: Vec<Value> =
3413 self.registers[saved_base..saved_base + reg_count].to_vec();
3414
3415 // Save exception handlers as (catch_ip, catch_reg) pairs.
3416 let saved_handlers: Vec<(usize, Reg)> = self.frames[fi]
3417 .exception_handlers
3418 .iter()
3419 .map(|h| (h.catch_ip, h.catch_reg))
3420 .collect();
3421
3422 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
3423 gen.ip = saved_ip;
3424 gen.registers = saved_regs;
3425 gen.state = GeneratorState::Suspended;
3426 gen.exception_handlers = saved_handlers;
3427 }
3428
3429 // Pop the generator frame.
3430 self.frames.pop();
3431
3432 // Create {value, done: false} result.
3433 let result = self.make_iterator_result(yield_val, false);
3434
3435 // Generators always run via run_generator which uses an isolated
3436 // frame stack. After Yield pops the generator frame, the stack is
3437 // empty and we return the {value, done} result to run_generator.
3438 return Ok(result);
3439 }
3440
3441 // Await works identically to Yield at the opcode level: save state and
3442 // suspend. The async driver (not the generator protocol) handles resume.
3443 Op::Await => {
3444 let _dst = Self::read_u8(&mut self.frames[fi]);
3445 let src = Self::read_u8(&mut self.frames[fi]);
3446 let base = self.frames[fi].base;
3447 let await_val = self.registers[base + src as usize].clone();
3448
3449 // Save the generator's state (same as Yield).
3450 let frame = &self.frames[fi];
3451 let gen_ref = match self.registers.get(frame.return_reg) {
3452 Some(Value::Object(r)) => *r,
3453 _ => {
3454 return Err(RuntimeError {
3455 kind: ErrorKind::Error,
3456 message: "Await outside async function".into(),
3457 });
3458 }
3459 };
3460
3461 let saved_ip = self.frames[fi].ip;
3462 let saved_base = self.frames[fi].base;
3463 let reg_count = self.frames[fi].func.register_count as usize;
3464 let saved_regs: Vec<Value> =
3465 self.registers[saved_base..saved_base + reg_count].to_vec();
3466
3467 let saved_handlers: Vec<(usize, Reg)> = self.frames[fi]
3468 .exception_handlers
3469 .iter()
3470 .map(|h| (h.catch_ip, h.catch_reg))
3471 .collect();
3472
3473 if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) {
3474 gen.ip = saved_ip;
3475 gen.registers = saved_regs;
3476 gen.state = GeneratorState::Suspended;
3477 gen.exception_handlers = saved_handlers;
3478 }
3479
3480 self.frames.pop();
3481
3482 let result = self.make_iterator_result(await_val, false);
3483 return Ok(result);
3484 }
3485
3486 Op::Spread => {
3487 let dst = Self::read_u8(&mut self.frames[fi]);
3488 let src = Self::read_u8(&mut self.frames[fi]);
3489 let base = self.frames[fi].base;
3490 let iterable = self.registers[base + src as usize].clone();
3491
3492 // Get the iterator from the iterable.
3493 let iterator = self.get_iterator(&iterable)?;
3494
3495 // Iterate and push each element into the dst array.
3496 loop {
3497 let (value, done) = self.iterator_next(&iterator)?;
3498 if done {
3499 break;
3500 }
3501 // Push value into dst array.
3502 let dst_ref = match self.registers[base + dst as usize] {
3503 Value::Object(r) => r,
3504 _ => break,
3505 };
3506 if let Some(HeapObject::Object(data)) = self.gc.get_mut(dst_ref) {
3507 let len = match data.properties.get("length") {
3508 Some(prop) => prop.value.to_number() as usize,
3509 None => 0,
3510 };
3511 data.properties
3512 .insert(len.to_string(), Property::data(value));
3513 data.properties.insert(
3514 "length".to_string(),
3515 Property {
3516 value: Value::Number((len + 1) as f64),
3517 writable: true,
3518 enumerable: false,
3519 configurable: false,
3520 },
3521 );
3522 }
3523 }
3524 }
3525 }
3526 }
3527 }
3528
3529 /// Read 3 register operands and return (dst, lhs, rhs, base) as usize indices.
3530 fn read_3reg(&mut self, fi: usize) -> (usize, usize, usize, usize) {
3531 let dst = Self::read_u8(&mut self.frames[fi]) as usize;
3532 let lhs = Self::read_u8(&mut self.frames[fi]) as usize;
3533 let rhs = Self::read_u8(&mut self.frames[fi]) as usize;
3534 let base = self.frames[fi].base;
3535 (dst, lhs, rhs, base)
3536 }
3537
3538 /// Try to find an exception handler on the call stack.
3539 /// Returns true if a handler was found (execution resumes there).
3540 fn handle_exception(&mut self, value: Value) -> bool {
3541 while let Some(frame) = self.frames.last_mut() {
3542 if let Some(handler) = frame.exception_handlers.pop() {
3543 let base = frame.base;
3544 frame.ip = handler.catch_ip;
3545 self.registers[base + handler.catch_reg as usize] = value;
3546 return true;
3547 }
3548 if self.frames.len() == 1 {
3549 break;
3550 }
3551 self.frames.pop();
3552 }
3553 false
3554 }
3555
3556 /// Register a native function as a global.
3557 pub fn define_native(
3558 &mut self,
3559 name: &str,
3560 callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>,
3561 ) {
3562 let gc_ref = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
3563 name: name.to_string(),
3564 kind: FunctionKind::Native(NativeFunc { callback }),
3565 prototype_obj: None,
3566 properties: HashMap::new(),
3567 upvalues: Vec::new(),
3568 })));
3569 self.globals
3570 .insert(name.to_string(), Value::Function(gc_ref));
3571 }
3572
3573 /// Get a global variable value.
3574 pub fn get_global(&self, name: &str) -> Option<&Value> {
3575 self.globals.get(name)
3576 }
3577
3578 /// Set a global variable.
3579 pub fn set_global(&mut self, name: &str, val: Value) {
3580 self.globals.insert(name.to_string(), val);
3581 }
3582
3583 /// Remove a global variable.
3584 pub fn remove_global(&mut self, name: &str) {
3585 self.globals.remove(name);
3586 }
3587}
3588
3589impl Default for Vm {
3590 fn default() -> Self {
3591 Self::new()
3592 }
3593}
3594
3595/// Internal enum to avoid holding a GC borrow across the call setup.
3596enum CallInfo {
3597 Native(fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>),
3598 Bytecode(Function, Vec<GcRef>),
3599}
3600
3601// ── Generator native callbacks ──────────────────────────────
3602
3603/// Native callback for generator.next(value).
3604/// `this` is the generator wrapper object containing __gen__.
3605fn generator_next(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> {
3606 // The generator wrapper stores the actual generator as __gen__.
3607 let gen_ref = match &ctx.this {
3608 Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") {
3609 Value::Object(gen_r) => gen_r,
3610 _ => return Err(RuntimeError::type_error("not a generator")),
3611 },
3612 _ => return Err(RuntimeError::type_error("not a generator")),
3613 };
3614
3615 let send_value = args.first().cloned().unwrap_or(Value::Undefined);
3616
3617 // We can't call run_generator from a NativeContext since we only have &mut Gc.
3618 // Instead, store the request and let the caller handle it.
3619 // This is a limitation — we need to restructure.
3620 // For now, use a different approach: store the gen_ref and value in a special
3621 // return value that the VM intercepts.
3622 // Actually, generator.next() needs to be handled specially by the VM.
3623 // Let's return a sentinel that the VM's call handling can detect.
3624
3625 // Store gen_ref and send_value for the VM to process.
3626 // We use a special object with __generator_resume__ marker.
3627 let mut obj = ObjectData::new();
3628 obj.properties.insert(
3629 "__generator_resume__".to_string(),
3630 Property::builtin(Value::Boolean(true)),
3631 );
3632 obj.properties.insert(
3633 "__gen_ref__".to_string(),
3634 Property::builtin(Value::Object(gen_ref)),
3635 );
3636 obj.properties
3637 .insert("__send_value__".to_string(), Property::builtin(send_value));
3638 obj.properties.insert(
3639 "__resume_kind__".to_string(),
3640 Property::builtin(Value::String("next".to_string())),
3641 );
3642 let r = ctx.gc.alloc(HeapObject::Object(obj));
3643 Ok(Value::Object(r))
3644}
3645
3646/// Native callback for generator.return(value).
3647fn generator_return(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> {
3648 let gen_ref = match &ctx.this {
3649 Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") {
3650 Value::Object(gen_r) => gen_r,
3651 _ => return Err(RuntimeError::type_error("not a generator")),
3652 },
3653 _ => return Err(RuntimeError::type_error("not a generator")),
3654 };
3655
3656 let return_value = args.first().cloned().unwrap_or(Value::Undefined);
3657
3658 let mut obj = ObjectData::new();
3659 obj.properties.insert(
3660 "__generator_resume__".to_string(),
3661 Property::builtin(Value::Boolean(true)),
3662 );
3663 obj.properties.insert(
3664 "__gen_ref__".to_string(),
3665 Property::builtin(Value::Object(gen_ref)),
3666 );
3667 obj.properties.insert(
3668 "__send_value__".to_string(),
3669 Property::builtin(return_value),
3670 );
3671 obj.properties.insert(
3672 "__resume_kind__".to_string(),
3673 Property::builtin(Value::String("return".to_string())),
3674 );
3675 let r = ctx.gc.alloc(HeapObject::Object(obj));
3676 Ok(Value::Object(r))
3677}
3678
3679/// Native callback for generator.throw(error).
3680fn generator_throw(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> {
3681 let gen_ref = match &ctx.this {
3682 Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") {
3683 Value::Object(gen_r) => gen_r,
3684 _ => return Err(RuntimeError::type_error("not a generator")),
3685 },
3686 _ => return Err(RuntimeError::type_error("not a generator")),
3687 };
3688
3689 let error_value = args.first().cloned().unwrap_or(Value::Undefined);
3690
3691 let mut obj = ObjectData::new();
3692 obj.properties.insert(
3693 "__generator_resume__".to_string(),
3694 Property::builtin(Value::Boolean(true)),
3695 );
3696 obj.properties.insert(
3697 "__gen_ref__".to_string(),
3698 Property::builtin(Value::Object(gen_ref)),
3699 );
3700 obj.properties
3701 .insert("__send_value__".to_string(), Property::builtin(error_value));
3702 obj.properties.insert(
3703 "__resume_kind__".to_string(),
3704 Property::builtin(Value::String("throw".to_string())),
3705 );
3706 let r = ctx.gc.alloc(HeapObject::Object(obj));
3707 Ok(Value::Object(r))
3708}
3709
3710/// Native callback for generator[Symbol.iterator]() — returns `this`.
3711fn generator_symbol_iterator(
3712 _args: &[Value],
3713 ctx: &mut NativeContext,
3714) -> Result<Value, RuntimeError> {
3715 Ok(ctx.this.clone())
3716}
3717
3718// ── Async function native callbacks ──────────────────────────
3719
3720/// Native callback for async function resume. Called from microtask drain.
3721/// Returns a marker object with `__async_resume__` so the VM can detect it
3722/// and call `drive_async_step`.
3723///
3724/// The resume data (gen_ref, result_promise, is_throw) is stored on the
3725/// function's `__async_data__` property. The VM extracts it into the
3726/// `ASYNC_RESUME_DATA` thread-local before invoking this callback.
3727fn async_resume_callback(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> {
3728 let value = args.first().cloned().unwrap_or(Value::Undefined);
3729 let data_ref = ASYNC_RESUME_DATA.with(|cell| cell.take());
3730 if let Some(data) = data_ref {
3731 let gen_ref = match gc_get_property(ctx.gc, data, "__gen_ref__") {
3732 Value::Object(r) => r,
3733 _ => return Ok(value),
3734 };
3735 let result_promise = match gc_get_property(ctx.gc, data, "__result_promise__") {
3736 Value::Object(r) => r,
3737 _ => return Ok(value),
3738 };
3739 let is_throw = matches!(
3740 gc_get_property(ctx.gc, data, "__is_throw__"),
3741 Value::Boolean(true)
3742 );
3743
3744 // Return a marker for the VM to detect.
3745 let mut obj = ObjectData::new();
3746 obj.properties.insert(
3747 "__async_resume__".to_string(),
3748 Property::builtin(Value::Boolean(true)),
3749 );
3750 obj.properties.insert(
3751 "__gen_ref__".to_string(),
3752 Property::builtin(Value::Object(gen_ref)),
3753 );
3754 obj.properties.insert(
3755 "__result_promise__".to_string(),
3756 Property::builtin(Value::Object(result_promise)),
3757 );
3758 obj.properties.insert(
3759 "__is_throw__".to_string(),
3760 Property::builtin(Value::Boolean(is_throw)),
3761 );
3762 obj.properties
3763 .insert("__value__".to_string(), Property::builtin(value));
3764 let r = ctx.gc.alloc(HeapObject::Object(obj));
3765 Ok(Value::Object(r))
3766 } else {
3767 Ok(value)
3768 }
3769}
3770
3771thread_local! {
3772 static ASYNC_RESUME_DATA: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) };
3773}
3774
3775/// Native callback for async generator `.next(value)`.
3776fn async_generator_next(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> {
3777 let gen_ref = match &ctx.this {
3778 Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") {
3779 Value::Object(gen_r) => gen_r,
3780 _ => return Err(RuntimeError::type_error("not an async generator")),
3781 },
3782 _ => return Err(RuntimeError::type_error("not an async generator")),
3783 };
3784
3785 let send_value = args.first().cloned().unwrap_or(Value::Undefined);
3786
3787 // Return a marker for the VM to handle.
3788 let mut obj = ObjectData::new();
3789 obj.properties.insert(
3790 "__async_generator_resume__".to_string(),
3791 Property::builtin(Value::Boolean(true)),
3792 );
3793 obj.properties.insert(
3794 "__gen_ref__".to_string(),
3795 Property::builtin(Value::Object(gen_ref)),
3796 );
3797 obj.properties
3798 .insert("__send_value__".to_string(), Property::builtin(send_value));
3799 obj.properties.insert(
3800 "__resume_kind__".to_string(),
3801 Property::builtin(Value::String("next".to_string())),
3802 );
3803 let r = ctx.gc.alloc(HeapObject::Object(obj));
3804 Ok(Value::Object(r))
3805}
3806
3807/// Native callback for async generator `.return(value)`.
3808fn async_generator_return(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> {
3809 let gen_ref = match &ctx.this {
3810 Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") {
3811 Value::Object(gen_r) => gen_r,
3812 _ => return Err(RuntimeError::type_error("not an async generator")),
3813 },
3814 _ => return Err(RuntimeError::type_error("not an async generator")),
3815 };
3816
3817 let return_value = args.first().cloned().unwrap_or(Value::Undefined);
3818
3819 let mut obj = ObjectData::new();
3820 obj.properties.insert(
3821 "__async_generator_resume__".to_string(),
3822 Property::builtin(Value::Boolean(true)),
3823 );
3824 obj.properties.insert(
3825 "__gen_ref__".to_string(),
3826 Property::builtin(Value::Object(gen_ref)),
3827 );
3828 obj.properties.insert(
3829 "__send_value__".to_string(),
3830 Property::builtin(return_value),
3831 );
3832 obj.properties.insert(
3833 "__resume_kind__".to_string(),
3834 Property::builtin(Value::String("return".to_string())),
3835 );
3836 let r = ctx.gc.alloc(HeapObject::Object(obj));
3837 Ok(Value::Object(r))
3838}
3839
3840// ── Tests ────────────────────────────────────────────────────
3841
3842#[cfg(test)]
3843mod tests {
3844 use super::*;
3845 use crate::bytecode::{BytecodeBuilder, Constant, Op};
3846 use crate::compiler;
3847 use crate::parser::Parser;
3848
3849 /// Helper: compile and execute JS source, return the completion value.
3850 fn eval(source: &str) -> Result<Value, RuntimeError> {
3851 let program = Parser::parse(source).expect("parse failed");
3852 let func = compiler::compile(&program).expect("compile failed");
3853 let mut vm = Vm::new();
3854 vm.execute(&func)
3855 }
3856
3857 // ── Value tests ─────────────────────────────────────────
3858
3859 #[test]
3860 fn test_to_boolean() {
3861 let mut gc: Gc<HeapObject> = Gc::new();
3862 assert!(!Value::Undefined.to_boolean());
3863 assert!(!Value::Null.to_boolean());
3864 assert!(!Value::Boolean(false).to_boolean());
3865 assert!(Value::Boolean(true).to_boolean());
3866 assert!(!Value::Number(0.0).to_boolean());
3867 assert!(!Value::Number(f64::NAN).to_boolean());
3868 assert!(Value::Number(1.0).to_boolean());
3869 assert!(!Value::String(String::new()).to_boolean());
3870 assert!(Value::String("hello".to_string()).to_boolean());
3871 let obj_ref = gc.alloc(HeapObject::Object(ObjectData::new()));
3872 assert!(Value::Object(obj_ref).to_boolean());
3873 }
3874
3875 #[test]
3876 fn test_to_number() {
3877 assert!(Value::Undefined.to_number().is_nan());
3878 assert_eq!(Value::Null.to_number(), 0.0);
3879 assert_eq!(Value::Boolean(true).to_number(), 1.0);
3880 assert_eq!(Value::Boolean(false).to_number(), 0.0);
3881 assert_eq!(Value::Number(42.0).to_number(), 42.0);
3882 assert_eq!(Value::String("42".to_string()).to_number(), 42.0);
3883 assert_eq!(Value::String("".to_string()).to_number(), 0.0);
3884 assert!(Value::String("abc".to_string()).to_number().is_nan());
3885 }
3886
3887 #[test]
3888 fn test_type_of() {
3889 let mut gc: Gc<HeapObject> = Gc::new();
3890 assert_eq!(Value::Undefined.type_of(), "undefined");
3891 assert_eq!(Value::Null.type_of(), "object");
3892 assert_eq!(Value::Boolean(true).type_of(), "boolean");
3893 assert_eq!(Value::Number(1.0).type_of(), "number");
3894 assert_eq!(Value::String("hi".to_string()).type_of(), "string");
3895 let obj_ref = gc.alloc(HeapObject::Object(ObjectData::new()));
3896 assert_eq!(Value::Object(obj_ref).type_of(), "object");
3897 }
3898
3899 // ── VM bytecode-level tests ─────────────────────────────
3900
3901 #[test]
3902 fn test_load_const_number() {
3903 let mut b = BytecodeBuilder::new("<test>".into(), 0);
3904 b.func.register_count = 1;
3905 let ci = b.add_constant(Constant::Number(42.0));
3906 b.emit_reg_u16(Op::LoadConst, 0, ci);
3907 b.emit_reg(Op::Return, 0);
3908 let func = b.finish();
3909
3910 let mut vm = Vm::new();
3911 let result = vm.execute(&func).unwrap();
3912 match result {
3913 Value::Number(n) => assert_eq!(n, 42.0),
3914 _ => panic!("expected Number, got {result:?}"),
3915 }
3916 }
3917
3918 #[test]
3919 fn test_arithmetic_ops() {
3920 let mut b = BytecodeBuilder::new("<test>".into(), 0);
3921 b.func.register_count = 3;
3922 let c10 = b.add_constant(Constant::Number(10.0));
3923 let c3 = b.add_constant(Constant::Number(3.0));
3924 b.emit_reg_u16(Op::LoadConst, 0, c10);
3925 b.emit_reg_u16(Op::LoadConst, 1, c3);
3926 b.emit_reg3(Op::Add, 2, 0, 1);
3927 b.emit_reg(Op::Return, 2);
3928 let func = b.finish();
3929
3930 let mut vm = Vm::new();
3931 match vm.execute(&func).unwrap() {
3932 Value::Number(n) => assert_eq!(n, 13.0),
3933 v => panic!("expected 13, got {v:?}"),
3934 }
3935 }
3936
3937 #[test]
3938 fn test_string_concat() {
3939 let mut b = BytecodeBuilder::new("<test>".into(), 0);
3940 b.func.register_count = 3;
3941 let c1 = b.add_constant(Constant::String("hello".into()));
3942 let c2 = b.add_constant(Constant::String(" world".into()));
3943 b.emit_reg_u16(Op::LoadConst, 0, c1);
3944 b.emit_reg_u16(Op::LoadConst, 1, c2);
3945 b.emit_reg3(Op::Add, 2, 0, 1);
3946 b.emit_reg(Op::Return, 2);
3947 let func = b.finish();
3948
3949 let mut vm = Vm::new();
3950 match vm.execute(&func).unwrap() {
3951 Value::String(s) => assert_eq!(s, "hello world"),
3952 v => panic!("expected string, got {v:?}"),
3953 }
3954 }
3955
3956 #[test]
3957 fn test_jump_if_false() {
3958 let mut b = BytecodeBuilder::new("<test>".into(), 0);
3959 b.func.register_count = 2;
3960 b.emit_reg(Op::LoadFalse, 0);
3961 let patch = b.emit_cond_jump(Op::JumpIfFalse, 0);
3962 b.emit_load_int8(1, 1);
3963 let skip = b.emit_jump(Op::Jump);
3964 b.patch_jump(patch);
3965 b.emit_load_int8(1, 2);
3966 b.patch_jump(skip);
3967 b.emit_reg(Op::Return, 1);
3968 let func = b.finish();
3969
3970 let mut vm = Vm::new();
3971 match vm.execute(&func).unwrap() {
3972 Value::Number(n) => assert_eq!(n, 2.0),
3973 v => panic!("expected 2, got {v:?}"),
3974 }
3975 }
3976
3977 #[test]
3978 fn test_globals() {
3979 let mut b = BytecodeBuilder::new("<test>".into(), 0);
3980 b.func.register_count = 2;
3981 let name = b.add_name("x");
3982 b.emit_load_int8(0, 42);
3983 b.emit_store_global(name, 0);
3984 b.emit_load_global(1, name);
3985 b.emit_reg(Op::Return, 1);
3986 let func = b.finish();
3987
3988 let mut vm = Vm::new();
3989 match vm.execute(&func).unwrap() {
3990 Value::Number(n) => assert_eq!(n, 42.0),
3991 v => panic!("expected 42, got {v:?}"),
3992 }
3993 }
3994
3995 #[test]
3996 fn test_function_call() {
3997 let mut inner_b = BytecodeBuilder::new("add1".into(), 1);
3998 inner_b.func.register_count = 2;
3999 inner_b.emit_load_int8(1, 1);
4000 inner_b.emit_reg3(Op::Add, 0, 0, 1);
4001 inner_b.emit_reg(Op::Return, 0);
4002 let inner = inner_b.finish();
4003
4004 let mut b = BytecodeBuilder::new("<test>".into(), 0);
4005 b.func.register_count = 4;
4006 let fi = b.add_function(inner);
4007 b.emit_reg_u16(Op::CreateClosure, 0, fi);
4008 b.emit_load_int8(1, 10);
4009 b.emit_call(2, 0, 1, 1);
4010 b.emit_reg(Op::Return, 2);
4011 let func = b.finish();
4012
4013 let mut vm = Vm::new();
4014 match vm.execute(&func).unwrap() {
4015 Value::Number(n) => assert_eq!(n, 11.0),
4016 v => panic!("expected 11, got {v:?}"),
4017 }
4018 }
4019
4020 #[test]
4021 fn test_native_function() {
4022 let mut b = BytecodeBuilder::new("<test>".into(), 0);
4023 b.func.register_count = 3;
4024 let name = b.add_name("double");
4025 b.emit_load_global(0, name);
4026 b.emit_load_int8(1, 21);
4027 b.emit_call(2, 0, 1, 1);
4028 b.emit_reg(Op::Return, 2);
4029 let func = b.finish();
4030
4031 let mut vm = Vm::new();
4032 vm.define_native("double", |args, _ctx| {
4033 let n = args.first().unwrap_or(&Value::Undefined).to_number();
4034 Ok(Value::Number(n * 2.0))
4035 });
4036 match vm.execute(&func).unwrap() {
4037 Value::Number(n) => assert_eq!(n, 42.0),
4038 v => panic!("expected 42, got {v:?}"),
4039 }
4040 }
4041
4042 #[test]
4043 fn test_object_property() {
4044 let mut b = BytecodeBuilder::new("<test>".into(), 0);
4045 b.func.register_count = 3;
4046 let name = b.add_name("x");
4047 b.emit_reg(Op::CreateObject, 0);
4048 b.emit_load_int8(1, 42);
4049 b.emit_set_prop_name(0, name, 1);
4050 b.emit_get_prop_name(2, 0, name);
4051 b.emit_reg(Op::Return, 2);
4052 let func = b.finish();
4053
4054 let mut vm = Vm::new();
4055 match vm.execute(&func).unwrap() {
4056 Value::Number(n) => assert_eq!(n, 42.0),
4057 v => panic!("expected 42, got {v:?}"),
4058 }
4059 }
4060
4061 #[test]
4062 fn test_typeof_operator() {
4063 let mut b = BytecodeBuilder::new("<test>".into(), 0);
4064 b.func.register_count = 2;
4065 b.emit_load_int8(0, 5);
4066 b.emit_reg_reg(Op::TypeOf, 1, 0);
4067 b.emit_reg(Op::Return, 1);
4068 let func = b.finish();
4069
4070 let mut vm = Vm::new();
4071 match vm.execute(&func).unwrap() {
4072 Value::String(s) => assert_eq!(s, "number"),
4073 v => panic!("expected 'number', got {v:?}"),
4074 }
4075 }
4076
4077 #[test]
4078 fn test_comparison() {
4079 let mut b = BytecodeBuilder::new("<test>".into(), 0);
4080 b.func.register_count = 3;
4081 b.emit_load_int8(0, 5);
4082 b.emit_load_int8(1, 10);
4083 b.emit_reg3(Op::LessThan, 2, 0, 1);
4084 b.emit_reg(Op::Return, 2);
4085 let func = b.finish();
4086
4087 let mut vm = Vm::new();
4088 match vm.execute(&func).unwrap() {
4089 Value::Boolean(b) => assert!(b),
4090 v => panic!("expected true, got {v:?}"),
4091 }
4092 }
4093
4094 #[test]
4095 fn test_abstract_equality() {
4096 assert!(abstract_eq(&Value::Null, &Value::Undefined));
4097 assert!(abstract_eq(&Value::Undefined, &Value::Null));
4098 assert!(abstract_eq(
4099 &Value::Number(1.0),
4100 &Value::String("1".to_string())
4101 ));
4102 assert!(abstract_eq(&Value::Boolean(true), &Value::Number(1.0)));
4103 assert!(!abstract_eq(&Value::Number(0.0), &Value::Null));
4104 }
4105
4106 #[test]
4107 fn test_strict_equality() {
4108 assert!(strict_eq(&Value::Number(1.0), &Value::Number(1.0)));
4109 assert!(!strict_eq(
4110 &Value::Number(1.0),
4111 &Value::String("1".to_string())
4112 ));
4113 assert!(strict_eq(&Value::Null, &Value::Null));
4114 assert!(!strict_eq(&Value::Null, &Value::Undefined));
4115 }
4116
4117 #[test]
4118 fn test_bitwise_ops() {
4119 let mut b = BytecodeBuilder::new("<test>".into(), 0);
4120 b.func.register_count = 3;
4121 b.emit_load_int8(0, 0x0F);
4122 b.emit_load_int8(1, 0x03);
4123 b.emit_reg3(Op::BitAnd, 2, 0, 1);
4124 b.emit_reg(Op::Return, 2);
4125 let func = b.finish();
4126
4127 let mut vm = Vm::new();
4128 match vm.execute(&func).unwrap() {
4129 Value::Number(n) => assert_eq!(n, 3.0),
4130 v => panic!("expected 3, got {v:?}"),
4131 }
4132 }
4133
4134 #[test]
4135 fn test_throw_uncaught() {
4136 let mut b = BytecodeBuilder::new("<test>".into(), 0);
4137 b.func.register_count = 1;
4138 let ci = b.add_constant(Constant::String("oops".into()));
4139 b.emit_reg_u16(Op::LoadConst, 0, ci);
4140 b.emit_reg(Op::Throw, 0);
4141 let func = b.finish();
4142
4143 let mut vm = Vm::new();
4144 let err = vm.execute(&func).unwrap_err();
4145 assert_eq!(err.message, "oops");
4146 }
4147
4148 #[test]
4149 fn test_loop_counting() {
4150 let mut b = BytecodeBuilder::new("<test>".into(), 0);
4151 b.func.register_count = 3;
4152 b.emit_load_int8(0, 0);
4153 b.emit_load_int8(1, 5);
4154 let loop_start = b.offset();
4155 b.emit_reg3(Op::LessThan, 2, 0, 1);
4156 let exit_patch = b.emit_cond_jump(Op::JumpIfFalse, 2);
4157 b.emit_load_int8(2, 1);
4158 b.emit_reg3(Op::Add, 0, 0, 2);
4159 b.emit_jump_to(loop_start);
4160 b.patch_jump(exit_patch);
4161 b.emit_reg(Op::Return, 0);
4162 let func = b.finish();
4163
4164 let mut vm = Vm::new();
4165 match vm.execute(&func).unwrap() {
4166 Value::Number(n) => assert_eq!(n, 5.0),
4167 v => panic!("expected 5, got {v:?}"),
4168 }
4169 }
4170
4171 // ── End-to-end (compile + execute) tests ────────────────
4172
4173 #[test]
4174 fn test_e2e_arithmetic() {
4175 match eval("2 + 3 * 4").unwrap() {
4176 Value::Number(n) => assert_eq!(n, 14.0),
4177 v => panic!("expected 14, got {v:?}"),
4178 }
4179 }
4180
4181 #[test]
4182 fn test_e2e_variables() {
4183 match eval("var x = 10; var y = 20; x + y").unwrap() {
4184 Value::Number(n) => assert_eq!(n, 30.0),
4185 v => panic!("expected 30, got {v:?}"),
4186 }
4187 }
4188
4189 #[test]
4190 fn test_e2e_if_else() {
4191 match eval("var x = 5; if (x > 3) { x = 100; } x").unwrap() {
4192 Value::Number(n) => assert_eq!(n, 100.0),
4193 v => panic!("expected 100, got {v:?}"),
4194 }
4195 }
4196
4197 #[test]
4198 fn test_e2e_while_loop() {
4199 match eval("var i = 0; var sum = 0; while (i < 10) { sum = sum + i; i = i + 1; } sum")
4200 .unwrap()
4201 {
4202 Value::Number(n) => assert_eq!(n, 45.0),
4203 v => panic!("expected 45, got {v:?}"),
4204 }
4205 }
4206
4207 #[test]
4208 fn test_e2e_function_call() {
4209 match eval("function add(a, b) { return a + b; } add(3, 4)").unwrap() {
4210 Value::Number(n) => assert_eq!(n, 7.0),
4211 v => panic!("expected 7, got {v:?}"),
4212 }
4213 }
4214
4215 #[test]
4216 fn test_e2e_recursive_factorial() {
4217 let src = r#"
4218 function fact(n) {
4219 if (n <= 1) return 1;
4220 return n * fact(n - 1);
4221 }
4222 fact(6)
4223 "#;
4224 match eval(src).unwrap() {
4225 Value::Number(n) => assert_eq!(n, 720.0),
4226 v => panic!("expected 720, got {v:?}"),
4227 }
4228 }
4229
4230 #[test]
4231 fn test_e2e_string_concat() {
4232 match eval("'hello' + ' ' + 'world'").unwrap() {
4233 Value::String(s) => assert_eq!(s, "hello world"),
4234 v => panic!("expected 'hello world', got {v:?}"),
4235 }
4236 }
4237
4238 #[test]
4239 fn test_e2e_comparison_coercion() {
4240 match eval("1 == '1'").unwrap() {
4241 Value::Boolean(b) => assert!(b),
4242 v => panic!("expected true, got {v:?}"),
4243 }
4244 match eval("1 === '1'").unwrap() {
4245 Value::Boolean(b) => assert!(!b),
4246 v => panic!("expected false, got {v:?}"),
4247 }
4248 }
4249
4250 #[test]
4251 fn test_e2e_typeof() {
4252 match eval("typeof 42").unwrap() {
4253 Value::String(s) => assert_eq!(s, "number"),
4254 v => panic!("expected 'number', got {v:?}"),
4255 }
4256 match eval("typeof 'hello'").unwrap() {
4257 Value::String(s) => assert_eq!(s, "string"),
4258 v => panic!("expected 'string', got {v:?}"),
4259 }
4260 }
4261
4262 #[test]
4263 fn test_e2e_object_literal() {
4264 match eval("var o = { x: 10, y: 20 }; o.x + o.y").unwrap() {
4265 Value::Number(n) => assert_eq!(n, 30.0),
4266 v => panic!("expected 30, got {v:?}"),
4267 }
4268 }
4269
4270 #[test]
4271 fn test_e2e_for_loop() {
4272 match eval("var s = 0; for (var i = 0; i < 5; i = i + 1) { s = s + i; } s").unwrap() {
4273 Value::Number(n) => assert_eq!(n, 10.0),
4274 v => panic!("expected 10, got {v:?}"),
4275 }
4276 }
4277
4278 #[test]
4279 fn test_e2e_nested_functions() {
4280 // Note: closures (capturing parent scope vars) not yet supported.
4281 // This test verifies nested function declarations and calls work.
4282 let src = r#"
4283 function outer(x) {
4284 function inner(y) {
4285 return y + 1;
4286 }
4287 return inner(x);
4288 }
4289 outer(5)
4290 "#;
4291 match eval(src).unwrap() {
4292 Value::Number(n) => assert_eq!(n, 6.0),
4293 v => panic!("expected 6, got {v:?}"),
4294 }
4295 }
4296
4297 #[test]
4298 fn test_e2e_logical_operators() {
4299 match eval("true && false").unwrap() {
4300 Value::Boolean(b) => assert!(!b),
4301 v => panic!("expected false, got {v:?}"),
4302 }
4303 match eval("false || true").unwrap() {
4304 Value::Boolean(b) => assert!(b),
4305 v => panic!("expected true, got {v:?}"),
4306 }
4307 }
4308
4309 #[test]
4310 fn test_e2e_unary_neg() {
4311 match eval("-(5 + 3)").unwrap() {
4312 Value::Number(n) => assert_eq!(n, -8.0),
4313 v => panic!("expected -8, got {v:?}"),
4314 }
4315 }
4316
4317 #[test]
4318 fn test_e2e_ternary() {
4319 match eval("true ? 1 : 2").unwrap() {
4320 Value::Number(n) => assert_eq!(n, 1.0),
4321 v => panic!("expected 1, got {v:?}"),
4322 }
4323 match eval("false ? 1 : 2").unwrap() {
4324 Value::Number(n) => assert_eq!(n, 2.0),
4325 v => panic!("expected 2, got {v:?}"),
4326 }
4327 }
4328
4329 #[test]
4330 fn test_e2e_fibonacci() {
4331 let src = r#"
4332 function fib(n) {
4333 if (n <= 1) return n;
4334 return fib(n - 1) + fib(n - 2);
4335 }
4336 fib(10)
4337 "#;
4338 match eval(src).unwrap() {
4339 Value::Number(n) => assert_eq!(n, 55.0),
4340 v => panic!("expected 55, got {v:?}"),
4341 }
4342 }
4343
4344 // ── GC integration tests ────────────────────────────────
4345
4346 #[test]
4347 fn test_gc_object_survives_collection() {
4348 let src = r#"
4349 var o = { x: 42 };
4350 o.x
4351 "#;
4352 match eval(src).unwrap() {
4353 Value::Number(n) => assert_eq!(n, 42.0),
4354 v => panic!("expected 42, got {v:?}"),
4355 }
4356 }
4357
4358 #[test]
4359 fn test_gc_many_objects() {
4360 // Allocate many objects to trigger GC threshold.
4361 let src = r#"
4362 var sum = 0;
4363 var i = 0;
4364 while (i < 100) {
4365 var o = { val: i };
4366 sum = sum + o.val;
4367 i = i + 1;
4368 }
4369 sum
4370 "#;
4371 match eval(src).unwrap() {
4372 Value::Number(n) => assert_eq!(n, 4950.0),
4373 v => panic!("expected 4950, got {v:?}"),
4374 }
4375 }
4376
4377 #[test]
4378 fn test_gc_reference_identity() {
4379 // With GC, object assignment is by reference.
4380 let mut gc: Gc<HeapObject> = Gc::new();
4381 let r = gc.alloc(HeapObject::Object(ObjectData::new()));
4382 let a = Value::Object(r);
4383 let b = a.clone();
4384 assert!(strict_eq(&a, &b)); // Same GcRef → strict equal.
4385 }
4386
4387 // ── Object model tests ──────────────────────────────────
4388
4389 #[test]
4390 fn test_prototype_chain_lookup() {
4391 // Property lookup walks the prototype chain.
4392 let src = r#"
4393 function Animal() {}
4394 var a = {};
4395 a.sound = "woof";
4396 a.sound
4397 "#;
4398 match eval(src).unwrap() {
4399 Value::String(s) => assert_eq!(s, "woof"),
4400 v => panic!("expected 'woof', got {v:?}"),
4401 }
4402 }
4403
4404 #[test]
4405 fn test_typeof_all_types() {
4406 // typeof returns correct strings for all types.
4407 let cases = [
4408 ("typeof undefined", "undefined"),
4409 ("typeof null", "object"),
4410 ("typeof true", "boolean"),
4411 ("typeof 42", "number"),
4412 ("typeof 'hello'", "string"),
4413 ("typeof {}", "object"),
4414 ("typeof function(){}", "function"),
4415 ];
4416 for (src, expected) in cases {
4417 match eval(src).unwrap() {
4418 Value::String(s) => assert_eq!(s, expected, "typeof failed for: {src}"),
4419 v => panic!("expected string for {src}, got {v:?}"),
4420 }
4421 }
4422 }
4423
4424 #[test]
4425 fn test_instanceof_basic() {
4426 let src = r#"
4427 function Foo() {}
4428 var f = {};
4429 f instanceof Foo
4430 "#;
4431 // Plain object without prototype link → false
4432 match eval(src).unwrap() {
4433 Value::Boolean(b) => assert!(!b),
4434 v => panic!("expected false, got {v:?}"),
4435 }
4436 }
4437
4438 #[test]
4439 fn test_in_operator() {
4440 let src = r#"
4441 var o = { x: 1, y: 2 };
4442 var r1 = "x" in o;
4443 var r2 = "z" in o;
4444 r1 === true && r2 === false
4445 "#;
4446 match eval(src).unwrap() {
4447 Value::Boolean(b) => assert!(b),
4448 v => panic!("expected true, got {v:?}"),
4449 }
4450 }
4451
4452 #[test]
4453 fn test_delete_property() {
4454 let src = r#"
4455 var o = { x: 1, y: 2 };
4456 delete o.x;
4457 typeof o.x === "undefined" && o.y === 2
4458 "#;
4459 match eval(src).unwrap() {
4460 Value::Boolean(b) => assert!(b),
4461 v => panic!("expected true, got {v:?}"),
4462 }
4463 }
4464
4465 #[test]
4466 fn test_delete_computed_property() {
4467 let src = r#"
4468 var o = { a: 10, b: 20 };
4469 var key = "a";
4470 delete o[key];
4471 typeof o.a === "undefined" && o.b === 20
4472 "#;
4473 match eval(src).unwrap() {
4474 Value::Boolean(b) => assert!(b),
4475 v => panic!("expected true, got {v:?}"),
4476 }
4477 }
4478
4479 #[test]
4480 fn test_delete_non_configurable() {
4481 // Array length is non-configurable, delete should return false.
4482 let mut gc: Gc<HeapObject> = Gc::new();
4483 let mut obj = ObjectData::new();
4484 obj.properties.insert(
4485 "x".to_string(),
4486 Property {
4487 value: Value::Number(1.0),
4488 writable: true,
4489 enumerable: true,
4490 configurable: false,
4491 },
4492 );
4493 let obj_ref = gc.alloc(HeapObject::Object(obj));
4494
4495 // Try to delete the non-configurable property.
4496 match gc.get_mut(obj_ref) {
4497 Some(HeapObject::Object(data)) => {
4498 let prop = data.properties.get("x").unwrap();
4499 assert!(!prop.configurable);
4500 // The property should still be there.
4501 assert!(data.properties.contains_key("x"));
4502 }
4503 _ => panic!("expected object"),
4504 }
4505 }
4506
4507 #[test]
4508 fn test_property_writable_flag() {
4509 // Setting a non-writable property should silently fail.
4510 let mut gc: Gc<HeapObject> = Gc::new();
4511 let mut obj = ObjectData::new();
4512 obj.properties.insert(
4513 "frozen".to_string(),
4514 Property {
4515 value: Value::Number(42.0),
4516 writable: false,
4517 enumerable: true,
4518 configurable: false,
4519 },
4520 );
4521 let obj_ref = gc.alloc(HeapObject::Object(obj));
4522
4523 // Verify the property value.
4524 match gc.get(obj_ref) {
4525 Some(HeapObject::Object(data)) => {
4526 assert_eq!(
4527 data.properties.get("frozen").unwrap().value.to_number(),
4528 42.0
4529 );
4530 }
4531 _ => panic!("expected object"),
4532 }
4533 }
4534
4535 #[test]
4536 fn test_for_in_basic() {
4537 let src = r#"
4538 var o = { a: 1, b: 2, c: 3 };
4539 var sum = 0;
4540 for (var key in o) {
4541 sum = sum + o[key];
4542 }
4543 sum
4544 "#;
4545 match eval(src).unwrap() {
4546 Value::Number(n) => assert_eq!(n, 6.0),
4547 v => panic!("expected 6, got {v:?}"),
4548 }
4549 }
4550
4551 #[test]
4552 fn test_for_in_collects_keys() {
4553 let src = r#"
4554 var o = { x: 10, y: 20 };
4555 var keys = "";
4556 for (var k in o) {
4557 keys = keys + k + ",";
4558 }
4559 keys
4560 "#;
4561 match eval(src).unwrap() {
4562 Value::String(s) => {
4563 // Both keys should appear (order may vary with HashMap).
4564 assert!(s.contains("x,"));
4565 assert!(s.contains("y,"));
4566 }
4567 v => panic!("expected string, got {v:?}"),
4568 }
4569 }
4570
4571 #[test]
4572 fn test_for_in_empty_object() {
4573 let src = r#"
4574 var o = {};
4575 var count = 0;
4576 for (var k in o) {
4577 count = count + 1;
4578 }
4579 count
4580 "#;
4581 match eval(src).unwrap() {
4582 Value::Number(n) => assert_eq!(n, 0.0),
4583 v => panic!("expected 0, got {v:?}"),
4584 }
4585 }
4586
4587 #[test]
4588 fn test_property_enumerable_flag() {
4589 // Array "length" is non-enumerable, should not appear in for-in.
4590 let src = r#"
4591 var arr = [10, 20, 30];
4592 var keys = "";
4593 for (var k in arr) {
4594 keys = keys + k + ",";
4595 }
4596 keys
4597 "#;
4598 match eval(src).unwrap() {
4599 Value::String(s) => {
4600 // "length" should NOT be in the keys (it's non-enumerable).
4601 assert!(!s.contains("length"));
4602 // Array indices should be present.
4603 assert!(s.contains("0,"));
4604 assert!(s.contains("1,"));
4605 assert!(s.contains("2,"));
4606 }
4607 v => panic!("expected string, got {v:?}"),
4608 }
4609 }
4610
4611 #[test]
4612 fn test_object_reference_semantics() {
4613 // Objects have reference semantics (shared via GcRef).
4614 let src = r#"
4615 var a = { x: 1 };
4616 var b = a;
4617 b.x = 42;
4618 a.x
4619 "#;
4620 match eval(src).unwrap() {
4621 Value::Number(n) => assert_eq!(n, 42.0),
4622 v => panic!("expected 42, got {v:?}"),
4623 }
4624 }
4625
4626 #[test]
4627 fn test_gc_enumerate_keys_order() {
4628 // Integer keys should come first, sorted numerically.
4629 let mut gc: Gc<HeapObject> = Gc::new();
4630 let mut obj = ObjectData::new();
4631 obj.properties
4632 .insert("b".to_string(), Property::data(Value::Number(2.0)));
4633 obj.properties
4634 .insert("2".to_string(), Property::data(Value::Number(3.0)));
4635 obj.properties
4636 .insert("a".to_string(), Property::data(Value::Number(1.0)));
4637 obj.properties
4638 .insert("0".to_string(), Property::data(Value::Number(0.0)));
4639 let obj_ref = gc.alloc(HeapObject::Object(obj));
4640
4641 let keys = gc_enumerate_keys(&gc, obj_ref);
4642 // Integer keys first (sorted), then string keys.
4643 let int_part: Vec<&str> = keys.iter().take(2).map(|s| s.as_str()).collect();
4644 assert_eq!(int_part, vec!["0", "2"]);
4645 // Remaining keys are string keys (order depends on HashMap iteration).
4646 let str_part: Vec<&str> = keys.iter().skip(2).map(|s| s.as_str()).collect();
4647 assert!(str_part.contains(&"a"));
4648 assert!(str_part.contains(&"b"));
4649 }
4650
4651 #[test]
4652 fn test_instanceof_with_gc() {
4653 // Direct test of gc_instanceof.
4654 let mut gc: Gc<HeapObject> = Gc::new();
4655
4656 // Create a constructor function with a .prototype object.
4657 let proto = gc.alloc(HeapObject::Object(ObjectData::new()));
4658 let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData {
4659 name: "Foo".to_string(),
4660 kind: FunctionKind::Native(NativeFunc {
4661 callback: |_, _ctx| Ok(Value::Undefined),
4662 }),
4663 prototype_obj: Some(proto),
4664 properties: HashMap::new(),
4665 upvalues: Vec::new(),
4666 })));
4667
4668 // Create an object whose [[Prototype]] is the constructor's .prototype.
4669 let mut obj_data = ObjectData::new();
4670 obj_data.prototype = Some(proto);
4671 let obj = gc.alloc(HeapObject::Object(obj_data));
4672
4673 assert!(gc_instanceof(&gc, obj, ctor));
4674
4675 // An unrelated object should not match.
4676 let other = gc.alloc(HeapObject::Object(ObjectData::new()));
4677 assert!(!gc_instanceof(&gc, other, ctor));
4678 }
4679
4680 #[test]
4681 fn test_try_catch_basic() {
4682 // Simple try/catch should catch a thrown value.
4683 let src = r#"
4684 var caught = false;
4685 try { throw "err"; } catch (e) { caught = true; }
4686 caught
4687 "#;
4688 match eval(src).unwrap() {
4689 Value::Boolean(true) => {}
4690 v => panic!("expected true, got {v:?}"),
4691 }
4692 }
4693
4694 #[test]
4695 fn test_try_catch_nested_call() {
4696 // try/catch should catch errors thrown from called functions.
4697 let src = r#"
4698 function thrower() { throw "err"; }
4699 var caught = false;
4700 try { thrower(); } catch (e) { caught = true; }
4701 caught
4702 "#;
4703 match eval(src).unwrap() {
4704 Value::Boolean(true) => {}
4705 v => panic!("expected true, got {v:?}"),
4706 }
4707 }
4708
4709 // ── Closure tests ────────────────────────────────────────
4710
4711 #[test]
4712 fn test_closure_basic() {
4713 // Basic closure: inner function reads outer variable.
4714 let src = r#"
4715 function outer() {
4716 var x = 10;
4717 function inner() {
4718 return x;
4719 }
4720 return inner();
4721 }
4722 outer()
4723 "#;
4724 match eval(src).unwrap() {
4725 Value::Number(n) => assert_eq!(n, 10.0),
4726 v => panic!("expected 10, got {v:?}"),
4727 }
4728 }
4729
4730 #[test]
4731 fn test_closure_return_function() {
4732 // Closure survives the outer function's return.
4733 let src = r#"
4734 function makeAdder(x) {
4735 return function(y) { return x + y; };
4736 }
4737 var add5 = makeAdder(5);
4738 add5(3)
4739 "#;
4740 match eval(src).unwrap() {
4741 Value::Number(n) => assert_eq!(n, 8.0),
4742 v => panic!("expected 8, got {v:?}"),
4743 }
4744 }
4745
4746 #[test]
4747 fn test_closure_mutation() {
4748 // Closures share live references — mutation is visible.
4749 let src = r#"
4750 function counter() {
4751 var n = 0;
4752 return function() { n = n + 1; return n; };
4753 }
4754 var c = counter();
4755 c();
4756 c();
4757 c()
4758 "#;
4759 match eval(src).unwrap() {
4760 Value::Number(n) => assert_eq!(n, 3.0),
4761 v => panic!("expected 3, got {v:?}"),
4762 }
4763 }
4764
4765 #[test]
4766 fn test_closure_shared_variable() {
4767 // Two closures from the same scope share the same variable.
4768 let src = r#"
4769 function make() {
4770 var x = 0;
4771 function inc() { x = x + 1; }
4772 function get() { return x; }
4773 inc();
4774 inc();
4775 return get();
4776 }
4777 make()
4778 "#;
4779 match eval(src).unwrap() {
4780 Value::Number(n) => assert_eq!(n, 2.0),
4781 v => panic!("expected 2, got {v:?}"),
4782 }
4783 }
4784
4785 #[test]
4786 fn test_closure_arrow() {
4787 // Arrow function captures outer variable.
4788 let src = r#"
4789 function outer() {
4790 var x = 42;
4791 var f = () => x;
4792 return f();
4793 }
4794 outer()
4795 "#;
4796 match eval(src).unwrap() {
4797 Value::Number(n) => assert_eq!(n, 42.0),
4798 v => panic!("expected 42, got {v:?}"),
4799 }
4800 }
4801
4802 #[test]
4803 fn test_closure_nested() {
4804 // Transitive capture: grandchild function reads grandparent variable.
4805 let src = r#"
4806 function outer() {
4807 var x = 100;
4808 function middle() {
4809 function inner() {
4810 return x;
4811 }
4812 return inner();
4813 }
4814 return middle();
4815 }
4816 outer()
4817 "#;
4818 match eval(src).unwrap() {
4819 Value::Number(n) => assert_eq!(n, 100.0),
4820 v => panic!("expected 100, got {v:?}"),
4821 }
4822 }
4823
4824 #[test]
4825 fn test_closure_param_capture() {
4826 // Closure captures a function parameter.
4827 let src = r#"
4828 function multiply(factor) {
4829 return function(x) { return x * factor; };
4830 }
4831 var double = multiply(2);
4832 double(7)
4833 "#;
4834 match eval(src).unwrap() {
4835 Value::Number(n) => assert_eq!(n, 14.0),
4836 v => panic!("expected 14, got {v:?}"),
4837 }
4838 }
4839
4840 // ── const tests ──────────────────────────────────────────
4841
4842 #[test]
4843 fn test_const_basic() {
4844 let src = "const x = 42; x";
4845 match eval(src).unwrap() {
4846 Value::Number(n) => assert_eq!(n, 42.0),
4847 v => panic!("expected 42, got {v:?}"),
4848 }
4849 }
4850
4851 #[test]
4852 fn test_const_reassignment_error() {
4853 let src = "const x = 1; x = 2;";
4854 let program = crate::parser::Parser::parse(src).expect("parse ok");
4855 let result = crate::compiler::compile(&program);
4856 assert!(
4857 result.is_err(),
4858 "const reassignment should be a compile error"
4859 );
4860 }
4861
4862 #[test]
4863 fn test_const_missing_init_error() {
4864 let src = "const x;";
4865 let program = crate::parser::Parser::parse(src).expect("parse ok");
4866 let result = crate::compiler::compile(&program);
4867 assert!(
4868 result.is_err(),
4869 "const without initializer should be a compile error"
4870 );
4871 }
4872
4873 // ── this binding tests ───────────────────────────────────
4874
4875 #[test]
4876 fn test_method_call_this() {
4877 let src = r#"
4878 var obj = {};
4879 obj.x = 10;
4880 obj.getX = function() { return this.x; };
4881 obj.getX()
4882 "#;
4883 match eval(src).unwrap() {
4884 Value::Number(n) => assert_eq!(n, 10.0),
4885 v => panic!("expected 10, got {v:?}"),
4886 }
4887 }
4888
4889 // ── Object built-in tests ────────────────────────────────
4890
4891 #[test]
4892 fn test_object_keys() {
4893 let src = r#"
4894 var obj = {};
4895 obj.a = 1;
4896 obj.b = 2;
4897 obj.c = 3;
4898 var k = Object.keys(obj);
4899 k.length
4900 "#;
4901 match eval(src).unwrap() {
4902 Value::Number(n) => assert_eq!(n, 3.0),
4903 v => panic!("expected 3, got {v:?}"),
4904 }
4905 }
4906
4907 #[test]
4908 fn test_object_values() {
4909 let src = r#"
4910 var obj = {};
4911 obj.x = 10;
4912 var v = Object.values(obj);
4913 v[0]
4914 "#;
4915 match eval(src).unwrap() {
4916 Value::Number(n) => assert_eq!(n, 10.0),
4917 v => panic!("expected 10, got {v:?}"),
4918 }
4919 }
4920
4921 #[test]
4922 fn test_object_entries() {
4923 let src = r#"
4924 var obj = {};
4925 obj.x = 42;
4926 var e = Object.entries(obj);
4927 e[0][1]
4928 "#;
4929 match eval(src).unwrap() {
4930 Value::Number(n) => assert_eq!(n, 42.0),
4931 v => panic!("expected 42, got {v:?}"),
4932 }
4933 }
4934
4935 #[test]
4936 fn test_object_assign() {
4937 let src = r#"
4938 var a = {};
4939 a.x = 1;
4940 var b = {};
4941 b.y = 2;
4942 var c = Object.assign(a, b);
4943 c.y
4944 "#;
4945 match eval(src).unwrap() {
4946 Value::Number(n) => assert_eq!(n, 2.0),
4947 v => panic!("expected 2, got {v:?}"),
4948 }
4949 }
4950
4951 #[test]
4952 fn test_object_create() {
4953 let src = r#"
4954 var proto = {};
4955 proto.greet = "hello";
4956 var child = Object.create(proto);
4957 child.greet
4958 "#;
4959 match eval(src).unwrap() {
4960 Value::String(s) => assert_eq!(s, "hello"),
4961 v => panic!("expected 'hello', got {v:?}"),
4962 }
4963 }
4964
4965 #[test]
4966 fn test_object_is() {
4967 let src = "Object.is(NaN, NaN)";
4968 match eval(src).unwrap() {
4969 Value::Boolean(b) => assert!(b),
4970 v => panic!("expected true, got {v:?}"),
4971 }
4972 }
4973
4974 #[test]
4975 fn test_object_freeze() {
4976 let src = r#"
4977 var obj = {};
4978 obj.x = 1;
4979 Object.freeze(obj);
4980 Object.isFrozen(obj)
4981 "#;
4982 match eval(src).unwrap() {
4983 Value::Boolean(b) => assert!(b),
4984 v => panic!("expected true, got {v:?}"),
4985 }
4986 }
4987
4988 #[test]
4989 fn test_object_has_own_property() {
4990 let src = r#"
4991 var obj = {};
4992 obj.x = 1;
4993 obj.hasOwnProperty("x")
4994 "#;
4995 match eval(src).unwrap() {
4996 Value::Boolean(b) => assert!(b),
4997 v => panic!("expected true, got {v:?}"),
4998 }
4999 }
5000
5001 // ── Array built-in tests ─────────────────────────────────
5002
5003 #[test]
5004 fn test_array_push_pop() {
5005 let src = r#"
5006 var arr = [1, 2, 3];
5007 arr.push(4);
5008 arr.pop()
5009 "#;
5010 match eval(src).unwrap() {
5011 Value::Number(n) => assert_eq!(n, 4.0),
5012 v => panic!("expected 4, got {v:?}"),
5013 }
5014 }
5015
5016 #[test]
5017 fn test_array_push_length() {
5018 let src = r#"
5019 var arr = [];
5020 arr.push(10);
5021 arr.push(20);
5022 arr.length
5023 "#;
5024 match eval(src).unwrap() {
5025 Value::Number(n) => assert_eq!(n, 2.0),
5026 v => panic!("expected 2, got {v:?}"),
5027 }
5028 }
5029
5030 #[test]
5031 fn test_array_shift_unshift() {
5032 let src = r#"
5033 var arr = [1, 2, 3];
5034 arr.unshift(0);
5035 arr.shift()
5036 "#;
5037 match eval(src).unwrap() {
5038 Value::Number(n) => assert_eq!(n, 0.0),
5039 v => panic!("expected 0, got {v:?}"),
5040 }
5041 }
5042
5043 #[test]
5044 fn test_array_index_of() {
5045 let src = r#"
5046 var arr = [10, 20, 30];
5047 arr.indexOf(20)
5048 "#;
5049 match eval(src).unwrap() {
5050 Value::Number(n) => assert_eq!(n, 1.0),
5051 v => panic!("expected 1, got {v:?}"),
5052 }
5053 }
5054
5055 #[test]
5056 fn test_array_includes() {
5057 let src = r#"
5058 var arr = [1, 2, 3];
5059 arr.includes(2)
5060 "#;
5061 match eval(src).unwrap() {
5062 Value::Boolean(b) => assert!(b),
5063 v => panic!("expected true, got {v:?}"),
5064 }
5065 }
5066
5067 #[test]
5068 fn test_array_join() {
5069 let src = r#"
5070 var arr = [1, 2, 3];
5071 arr.join("-")
5072 "#;
5073 match eval(src).unwrap() {
5074 Value::String(s) => assert_eq!(s, "1-2-3"),
5075 v => panic!("expected '1-2-3', got {v:?}"),
5076 }
5077 }
5078
5079 #[test]
5080 fn test_array_slice() {
5081 let src = r#"
5082 var arr = [1, 2, 3, 4, 5];
5083 var s = arr.slice(1, 3);
5084 s.length
5085 "#;
5086 match eval(src).unwrap() {
5087 Value::Number(n) => assert_eq!(n, 2.0),
5088 v => panic!("expected 2, got {v:?}"),
5089 }
5090 }
5091
5092 #[test]
5093 fn test_array_concat() {
5094 let src = r#"
5095 var a = [1, 2];
5096 var b = [3, 4];
5097 var c = a.concat(b);
5098 c.length
5099 "#;
5100 match eval(src).unwrap() {
5101 Value::Number(n) => assert_eq!(n, 4.0),
5102 v => panic!("expected 4, got {v:?}"),
5103 }
5104 }
5105
5106 #[test]
5107 fn test_array_reverse() {
5108 let src = r#"
5109 var arr = [1, 2, 3];
5110 arr.reverse();
5111 arr[0]
5112 "#;
5113 match eval(src).unwrap() {
5114 Value::Number(n) => assert_eq!(n, 3.0),
5115 v => panic!("expected 3, got {v:?}"),
5116 }
5117 }
5118
5119 #[test]
5120 fn test_array_splice() {
5121 let src = r#"
5122 var arr = [1, 2, 3, 4, 5];
5123 var removed = arr.splice(1, 2);
5124 removed.length
5125 "#;
5126 match eval(src).unwrap() {
5127 Value::Number(n) => assert_eq!(n, 2.0),
5128 v => panic!("expected 2, got {v:?}"),
5129 }
5130 }
5131
5132 #[test]
5133 fn test_array_is_array() {
5134 let src = "Array.isArray([1, 2, 3])";
5135 match eval(src).unwrap() {
5136 Value::Boolean(b) => assert!(b),
5137 v => panic!("expected true, got {v:?}"),
5138 }
5139 }
5140
5141 #[test]
5142 fn test_array_map() {
5143 let src = r#"
5144 var arr = [1, 2, 3];
5145 var doubled = arr.map(function(x) { return x * 2; });
5146 doubled[1]
5147 "#;
5148 match eval(src).unwrap() {
5149 Value::Number(n) => assert_eq!(n, 4.0),
5150 v => panic!("expected 4, got {v:?}"),
5151 }
5152 }
5153
5154 #[test]
5155 fn test_array_filter() {
5156 let src = r#"
5157 var arr = [1, 2, 3, 4, 5];
5158 var evens = arr.filter(function(x) { return x % 2 === 0; });
5159 evens.length
5160 "#;
5161 match eval(src).unwrap() {
5162 Value::Number(n) => assert_eq!(n, 2.0),
5163 v => panic!("expected 2, got {v:?}"),
5164 }
5165 }
5166
5167 #[test]
5168 fn test_array_reduce() {
5169 let src = r#"
5170 var arr = [1, 2, 3, 4];
5171 arr.reduce(function(acc, x) { return acc + x; }, 0)
5172 "#;
5173 match eval(src).unwrap() {
5174 Value::Number(n) => assert_eq!(n, 10.0),
5175 v => panic!("expected 10, got {v:?}"),
5176 }
5177 }
5178
5179 #[test]
5180 fn test_array_foreach() {
5181 let src = r#"
5182 var arr = [1, 2, 3];
5183 var sum = 0;
5184 arr.forEach(function(x) { sum = sum + x; });
5185 sum
5186 "#;
5187 match eval(src).unwrap() {
5188 Value::Number(n) => assert_eq!(n, 6.0),
5189 v => panic!("expected 6, got {v:?}"),
5190 }
5191 }
5192
5193 #[test]
5194 fn test_array_find() {
5195 let src = r#"
5196 var arr = [1, 2, 3, 4];
5197 arr.find(function(x) { return x > 2; })
5198 "#;
5199 match eval(src).unwrap() {
5200 Value::Number(n) => assert_eq!(n, 3.0),
5201 v => panic!("expected 3, got {v:?}"),
5202 }
5203 }
5204
5205 #[test]
5206 fn test_array_find_index() {
5207 let src = r#"
5208 var arr = [1, 2, 3, 4];
5209 arr.findIndex(function(x) { return x > 2; })
5210 "#;
5211 match eval(src).unwrap() {
5212 Value::Number(n) => assert_eq!(n, 2.0),
5213 v => panic!("expected 2, got {v:?}"),
5214 }
5215 }
5216
5217 #[test]
5218 fn test_array_some_every() {
5219 let src = r#"
5220 var arr = [2, 4, 6];
5221 var all_even = arr.every(function(x) { return x % 2 === 0; });
5222 var has_big = arr.some(function(x) { return x > 10; });
5223 all_even && !has_big
5224 "#;
5225 match eval(src).unwrap() {
5226 Value::Boolean(b) => assert!(b),
5227 v => panic!("expected true, got {v:?}"),
5228 }
5229 }
5230
5231 #[test]
5232 fn test_array_sort() {
5233 let src = r#"
5234 var arr = [3, 1, 2];
5235 arr.sort();
5236 arr[0]
5237 "#;
5238 match eval(src).unwrap() {
5239 Value::Number(n) => assert_eq!(n, 1.0),
5240 v => panic!("expected 1, got {v:?}"),
5241 }
5242 }
5243
5244 #[test]
5245 fn test_array_sort_custom() {
5246 let src = r#"
5247 var arr = [3, 1, 2];
5248 arr.sort(function(a, b) { return a - b; });
5249 arr[2]
5250 "#;
5251 match eval(src).unwrap() {
5252 Value::Number(n) => assert_eq!(n, 3.0),
5253 v => panic!("expected 3, got {v:?}"),
5254 }
5255 }
5256
5257 #[test]
5258 fn test_array_from() {
5259 let src = r#"
5260 var arr = Array.from("abc");
5261 arr.length
5262 "#;
5263 match eval(src).unwrap() {
5264 Value::Number(n) => assert_eq!(n, 3.0),
5265 v => panic!("expected 3, got {v:?}"),
5266 }
5267 }
5268
5269 #[test]
5270 fn test_array_from_array() {
5271 let src = r#"
5272 var orig = [10, 20, 30];
5273 var copy = Array.from(orig);
5274 copy[2]
5275 "#;
5276 match eval(src).unwrap() {
5277 Value::Number(n) => assert_eq!(n, 30.0),
5278 v => panic!("expected 30, got {v:?}"),
5279 }
5280 }
5281
5282 #[test]
5283 fn test_array_flat() {
5284 let src = r#"
5285 var arr = [[1, 2], [3, 4]];
5286 var flat = arr.flat();
5287 flat.length
5288 "#;
5289 match eval(src).unwrap() {
5290 Value::Number(n) => assert_eq!(n, 4.0),
5291 v => panic!("expected 4, got {v:?}"),
5292 }
5293 }
5294
5295 // ── Error built-in tests ─────────────────────────────────
5296
5297 #[test]
5298 fn test_error_constructor() {
5299 let src = r#"
5300 var e = new Error("oops");
5301 e.message
5302 "#;
5303 match eval(src).unwrap() {
5304 Value::String(s) => assert_eq!(s, "oops"),
5305 v => panic!("expected 'oops', got {v:?}"),
5306 }
5307 }
5308
5309 #[test]
5310 fn test_type_error_constructor() {
5311 let src = r#"
5312 var e = new TypeError("bad type");
5313 e.message
5314 "#;
5315 match eval(src).unwrap() {
5316 Value::String(s) => assert_eq!(s, "bad type"),
5317 v => panic!("expected 'bad type', got {v:?}"),
5318 }
5319 }
5320
5321 // ── Global function tests ────────────────────────────────
5322
5323 #[test]
5324 fn test_parse_int() {
5325 let src = "parseInt('42')";
5326 match eval(src).unwrap() {
5327 Value::Number(n) => assert_eq!(n, 42.0),
5328 v => panic!("expected 42, got {v:?}"),
5329 }
5330 }
5331
5332 #[test]
5333 fn test_parse_int_hex() {
5334 let src = "parseInt('0xFF', 16)";
5335 match eval(src).unwrap() {
5336 Value::Number(n) => assert_eq!(n, 255.0),
5337 v => panic!("expected 255, got {v:?}"),
5338 }
5339 }
5340
5341 #[test]
5342 fn test_is_nan() {
5343 let src = "isNaN(NaN)";
5344 match eval(src).unwrap() {
5345 Value::Boolean(b) => assert!(b),
5346 v => panic!("expected true, got {v:?}"),
5347 }
5348 }
5349
5350 #[test]
5351 fn test_is_finite() {
5352 let src = "isFinite(42)";
5353 match eval(src).unwrap() {
5354 Value::Boolean(b) => assert!(b),
5355 v => panic!("expected true, got {v:?}"),
5356 }
5357 }
5358
5359 // ── String built-in tests ─────────────────────────────────
5360
5361 #[test]
5362 fn test_string_constructor() {
5363 match eval("String(42)").unwrap() {
5364 Value::String(s) => assert_eq!(s, "42"),
5365 v => panic!("expected '42', got {v:?}"),
5366 }
5367 match eval("String(true)").unwrap() {
5368 Value::String(s) => assert_eq!(s, "true"),
5369 v => panic!("expected 'true', got {v:?}"),
5370 }
5371 match eval("String()").unwrap() {
5372 Value::String(s) => assert_eq!(s, ""),
5373 v => panic!("expected '', got {v:?}"),
5374 }
5375 }
5376
5377 #[test]
5378 fn test_string_length() {
5379 match eval("'hello'.length").unwrap() {
5380 Value::Number(n) => assert_eq!(n, 5.0),
5381 v => panic!("expected 5, got {v:?}"),
5382 }
5383 }
5384
5385 #[test]
5386 fn test_string_char_at() {
5387 match eval("'hello'.charAt(1)").unwrap() {
5388 Value::String(s) => assert_eq!(s, "e"),
5389 v => panic!("expected 'e', got {v:?}"),
5390 }
5391 match eval("'hello'.charAt(10)").unwrap() {
5392 Value::String(s) => assert_eq!(s, ""),
5393 v => panic!("expected '', got {v:?}"),
5394 }
5395 }
5396
5397 #[test]
5398 fn test_string_char_code_at() {
5399 match eval("'A'.charCodeAt(0)").unwrap() {
5400 Value::Number(n) => assert_eq!(n, 65.0),
5401 v => panic!("expected 65, got {v:?}"),
5402 }
5403 }
5404
5405 #[test]
5406 fn test_string_proto_concat() {
5407 match eval("'hello'.concat(' ', 'world')").unwrap() {
5408 Value::String(s) => assert_eq!(s, "hello world"),
5409 v => panic!("expected 'hello world', got {v:?}"),
5410 }
5411 }
5412
5413 #[test]
5414 fn test_string_slice() {
5415 match eval("'hello world'.slice(6)").unwrap() {
5416 Value::String(s) => assert_eq!(s, "world"),
5417 v => panic!("expected 'world', got {v:?}"),
5418 }
5419 match eval("'hello'.slice(1, 3)").unwrap() {
5420 Value::String(s) => assert_eq!(s, "el"),
5421 v => panic!("expected 'el', got {v:?}"),
5422 }
5423 match eval("'hello'.slice(-3)").unwrap() {
5424 Value::String(s) => assert_eq!(s, "llo"),
5425 v => panic!("expected 'llo', got {v:?}"),
5426 }
5427 }
5428
5429 #[test]
5430 fn test_string_substring() {
5431 match eval("'hello'.substring(1, 3)").unwrap() {
5432 Value::String(s) => assert_eq!(s, "el"),
5433 v => panic!("expected 'el', got {v:?}"),
5434 }
5435 // substring swaps args if start > end
5436 match eval("'hello'.substring(3, 1)").unwrap() {
5437 Value::String(s) => assert_eq!(s, "el"),
5438 v => panic!("expected 'el', got {v:?}"),
5439 }
5440 }
5441
5442 #[test]
5443 fn test_string_index_of() {
5444 match eval("'hello world'.indexOf('world')").unwrap() {
5445 Value::Number(n) => assert_eq!(n, 6.0),
5446 v => panic!("expected 6, got {v:?}"),
5447 }
5448 match eval("'hello'.indexOf('xyz')").unwrap() {
5449 Value::Number(n) => assert_eq!(n, -1.0),
5450 v => panic!("expected -1, got {v:?}"),
5451 }
5452 }
5453
5454 #[test]
5455 fn test_string_last_index_of() {
5456 match eval("'abcabc'.lastIndexOf('abc')").unwrap() {
5457 Value::Number(n) => assert_eq!(n, 3.0),
5458 v => panic!("expected 3, got {v:?}"),
5459 }
5460 }
5461
5462 #[test]
5463 fn test_string_includes() {
5464 match eval("'hello world'.includes('world')").unwrap() {
5465 Value::Boolean(b) => assert!(b),
5466 v => panic!("expected true, got {v:?}"),
5467 }
5468 match eval("'hello'.includes('xyz')").unwrap() {
5469 Value::Boolean(b) => assert!(!b),
5470 v => panic!("expected false, got {v:?}"),
5471 }
5472 }
5473
5474 #[test]
5475 fn test_string_starts_ends_with() {
5476 match eval("'hello'.startsWith('hel')").unwrap() {
5477 Value::Boolean(b) => assert!(b),
5478 v => panic!("expected true, got {v:?}"),
5479 }
5480 match eval("'hello'.endsWith('llo')").unwrap() {
5481 Value::Boolean(b) => assert!(b),
5482 v => panic!("expected true, got {v:?}"),
5483 }
5484 }
5485
5486 #[test]
5487 fn test_string_trim() {
5488 match eval("' hello '.trim()").unwrap() {
5489 Value::String(s) => assert_eq!(s, "hello"),
5490 v => panic!("expected 'hello', got {v:?}"),
5491 }
5492 match eval("' hello '.trimStart()").unwrap() {
5493 Value::String(s) => assert_eq!(s, "hello "),
5494 v => panic!("expected 'hello ', got {v:?}"),
5495 }
5496 match eval("' hello '.trimEnd()").unwrap() {
5497 Value::String(s) => assert_eq!(s, " hello"),
5498 v => panic!("expected ' hello', got {v:?}"),
5499 }
5500 }
5501
5502 #[test]
5503 fn test_string_pad() {
5504 match eval("'5'.padStart(3, '0')").unwrap() {
5505 Value::String(s) => assert_eq!(s, "005"),
5506 v => panic!("expected '005', got {v:?}"),
5507 }
5508 match eval("'5'.padEnd(3, '0')").unwrap() {
5509 Value::String(s) => assert_eq!(s, "500"),
5510 v => panic!("expected '500', got {v:?}"),
5511 }
5512 }
5513
5514 #[test]
5515 fn test_string_repeat() {
5516 match eval("'ab'.repeat(3)").unwrap() {
5517 Value::String(s) => assert_eq!(s, "ababab"),
5518 v => panic!("expected 'ababab', got {v:?}"),
5519 }
5520 }
5521
5522 #[test]
5523 fn test_string_split() {
5524 // split returns an array; verify length and elements.
5525 match eval("'a,b,c'.split(',').length").unwrap() {
5526 Value::Number(n) => assert_eq!(n, 3.0),
5527 v => panic!("expected 3, got {v:?}"),
5528 }
5529 match eval("'a,b,c'.split(',')[0]").unwrap() {
5530 Value::String(s) => assert_eq!(s, "a"),
5531 v => panic!("expected 'a', got {v:?}"),
5532 }
5533 match eval("'a,b,c'.split(',')[2]").unwrap() {
5534 Value::String(s) => assert_eq!(s, "c"),
5535 v => panic!("expected 'c', got {v:?}"),
5536 }
5537 }
5538
5539 #[test]
5540 fn test_string_replace() {
5541 match eval("'hello world'.replace('world', 'there')").unwrap() {
5542 Value::String(s) => assert_eq!(s, "hello there"),
5543 v => panic!("expected 'hello there', got {v:?}"),
5544 }
5545 }
5546
5547 #[test]
5548 fn test_string_replace_all() {
5549 match eval("'aabbcc'.replaceAll('b', 'x')").unwrap() {
5550 Value::String(s) => assert_eq!(s, "aaxxcc"),
5551 v => panic!("expected 'aaxxcc', got {v:?}"),
5552 }
5553 }
5554
5555 #[test]
5556 fn test_string_case() {
5557 match eval("'Hello'.toLowerCase()").unwrap() {
5558 Value::String(s) => assert_eq!(s, "hello"),
5559 v => panic!("expected 'hello', got {v:?}"),
5560 }
5561 match eval("'Hello'.toUpperCase()").unwrap() {
5562 Value::String(s) => assert_eq!(s, "HELLO"),
5563 v => panic!("expected 'HELLO', got {v:?}"),
5564 }
5565 }
5566
5567 #[test]
5568 fn test_string_at() {
5569 match eval("'hello'.at(0)").unwrap() {
5570 Value::String(s) => assert_eq!(s, "h"),
5571 v => panic!("expected 'h', got {v:?}"),
5572 }
5573 match eval("'hello'.at(-1)").unwrap() {
5574 Value::String(s) => assert_eq!(s, "o"),
5575 v => panic!("expected 'o', got {v:?}"),
5576 }
5577 }
5578
5579 #[test]
5580 fn test_string_from_char_code() {
5581 match eval("String.fromCharCode(72, 101, 108)").unwrap() {
5582 Value::String(s) => assert_eq!(s, "Hel"),
5583 v => panic!("expected 'Hel', got {v:?}"),
5584 }
5585 }
5586
5587 #[test]
5588 fn test_string_from_code_point() {
5589 match eval("String.fromCodePoint(65, 66, 67)").unwrap() {
5590 Value::String(s) => assert_eq!(s, "ABC"),
5591 v => panic!("expected 'ABC', got {v:?}"),
5592 }
5593 }
5594
5595 // ── Number built-in tests ─────────────────────────────────
5596
5597 #[test]
5598 fn test_number_constructor() {
5599 match eval("Number('42')").unwrap() {
5600 Value::Number(n) => assert_eq!(n, 42.0),
5601 v => panic!("expected 42, got {v:?}"),
5602 }
5603 match eval("Number(true)").unwrap() {
5604 Value::Number(n) => assert_eq!(n, 1.0),
5605 v => panic!("expected 1, got {v:?}"),
5606 }
5607 match eval("Number()").unwrap() {
5608 Value::Number(n) => assert_eq!(n, 0.0),
5609 v => panic!("expected 0, got {v:?}"),
5610 }
5611 }
5612
5613 #[test]
5614 fn test_number_is_nan() {
5615 match eval("Number.isNaN(NaN)").unwrap() {
5616 Value::Boolean(b) => assert!(b),
5617 v => panic!("expected true, got {v:?}"),
5618 }
5619 match eval("Number.isNaN(42)").unwrap() {
5620 Value::Boolean(b) => assert!(!b),
5621 v => panic!("expected false, got {v:?}"),
5622 }
5623 // Number.isNaN doesn't coerce — string "NaN" is not NaN.
5624 match eval("Number.isNaN('NaN')").unwrap() {
5625 Value::Boolean(b) => assert!(!b),
5626 v => panic!("expected false, got {v:?}"),
5627 }
5628 }
5629
5630 #[test]
5631 fn test_number_is_finite() {
5632 match eval("Number.isFinite(42)").unwrap() {
5633 Value::Boolean(b) => assert!(b),
5634 v => panic!("expected true, got {v:?}"),
5635 }
5636 match eval("Number.isFinite(Infinity)").unwrap() {
5637 Value::Boolean(b) => assert!(!b),
5638 v => panic!("expected false, got {v:?}"),
5639 }
5640 }
5641
5642 #[test]
5643 fn test_number_is_integer() {
5644 match eval("Number.isInteger(42)").unwrap() {
5645 Value::Boolean(b) => assert!(b),
5646 v => panic!("expected true, got {v:?}"),
5647 }
5648 match eval("Number.isInteger(42.5)").unwrap() {
5649 Value::Boolean(b) => assert!(!b),
5650 v => panic!("expected false, got {v:?}"),
5651 }
5652 }
5653
5654 #[test]
5655 fn test_number_is_safe_integer() {
5656 match eval("Number.isSafeInteger(42)").unwrap() {
5657 Value::Boolean(b) => assert!(b),
5658 v => panic!("expected true, got {v:?}"),
5659 }
5660 match eval("Number.isSafeInteger(9007199254740992)").unwrap() {
5661 Value::Boolean(b) => assert!(!b),
5662 v => panic!("expected false, got {v:?}"),
5663 }
5664 }
5665
5666 #[test]
5667 fn test_number_constants() {
5668 match eval("Number.MAX_SAFE_INTEGER").unwrap() {
5669 Value::Number(n) => assert_eq!(n, 9007199254740991.0),
5670 v => panic!("expected MAX_SAFE_INTEGER, got {v:?}"),
5671 }
5672 match eval("Number.EPSILON").unwrap() {
5673 Value::Number(n) => assert_eq!(n, f64::EPSILON),
5674 v => panic!("expected EPSILON, got {v:?}"),
5675 }
5676 }
5677
5678 #[test]
5679 fn test_number_to_fixed() {
5680 match eval("var n = 3.14159; n.toFixed(2)").unwrap() {
5681 Value::String(s) => assert_eq!(s, "3.14"),
5682 v => panic!("expected '3.14', got {v:?}"),
5683 }
5684 }
5685
5686 #[test]
5687 fn test_number_to_string_radix() {
5688 match eval("var n = 255; n.toString(16)").unwrap() {
5689 Value::String(s) => assert_eq!(s, "ff"),
5690 v => panic!("expected 'ff', got {v:?}"),
5691 }
5692 match eval("var n = 10; n.toString(2)").unwrap() {
5693 Value::String(s) => assert_eq!(s, "1010"),
5694 v => panic!("expected '1010', got {v:?}"),
5695 }
5696 }
5697
5698 #[test]
5699 fn test_number_parse_int() {
5700 match eval("Number.parseInt('42')").unwrap() {
5701 Value::Number(n) => assert_eq!(n, 42.0),
5702 v => panic!("expected 42, got {v:?}"),
5703 }
5704 }
5705
5706 // ── Boolean built-in tests ────────────────────────────────
5707
5708 #[test]
5709 fn test_boolean_constructor() {
5710 match eval("Boolean(1)").unwrap() {
5711 Value::Boolean(b) => assert!(b),
5712 v => panic!("expected true, got {v:?}"),
5713 }
5714 match eval("Boolean(0)").unwrap() {
5715 Value::Boolean(b) => assert!(!b),
5716 v => panic!("expected false, got {v:?}"),
5717 }
5718 match eval("Boolean('')").unwrap() {
5719 Value::Boolean(b) => assert!(!b),
5720 v => panic!("expected false, got {v:?}"),
5721 }
5722 match eval("Boolean('hello')").unwrap() {
5723 Value::Boolean(b) => assert!(b),
5724 v => panic!("expected true, got {v:?}"),
5725 }
5726 }
5727
5728 #[test]
5729 fn test_boolean_to_string() {
5730 match eval("true.toString()").unwrap() {
5731 Value::String(s) => assert_eq!(s, "true"),
5732 v => panic!("expected 'true', got {v:?}"),
5733 }
5734 match eval("false.toString()").unwrap() {
5735 Value::String(s) => assert_eq!(s, "false"),
5736 v => panic!("expected 'false', got {v:?}"),
5737 }
5738 }
5739
5740 // ── Symbol built-in tests ─────────────────────────────────
5741
5742 #[test]
5743 fn test_symbol_uniqueness() {
5744 // Each Symbol() call should produce a unique value.
5745 match eval("var a = Symbol('x'); var b = Symbol('x'); a === b").unwrap() {
5746 Value::Boolean(b) => assert!(!b),
5747 v => panic!("expected false, got {v:?}"),
5748 }
5749 }
5750
5751 #[test]
5752 fn test_symbol_well_known() {
5753 match eval("typeof Symbol.iterator").unwrap() {
5754 Value::String(s) => assert_eq!(s, "string"),
5755 v => panic!("expected 'string', got {v:?}"),
5756 }
5757 match eval("Symbol.iterator").unwrap() {
5758 Value::String(s) => assert_eq!(s, "@@iterator"),
5759 v => panic!("expected '@@iterator', got {v:?}"),
5760 }
5761 }
5762
5763 #[test]
5764 fn test_symbol_for_and_key_for() {
5765 // "for" is a keyword, so use bracket notation: Symbol["for"](...).
5766 match eval("Symbol['for']('test') === Symbol['for']('test')").unwrap() {
5767 Value::Boolean(b) => assert!(b),
5768 v => panic!("expected true, got {v:?}"),
5769 }
5770 match eval("Symbol.keyFor(Symbol['for']('mykey'))").unwrap() {
5771 Value::String(s) => assert_eq!(s, "mykey"),
5772 v => panic!("expected 'mykey', got {v:?}"),
5773 }
5774 }
5775
5776 // ── Primitive auto-boxing tests ───────────────────────────
5777
5778 #[test]
5779 fn test_string_method_chaining() {
5780 match eval("' Hello World '.trim().toLowerCase()").unwrap() {
5781 Value::String(s) => assert_eq!(s, "hello world"),
5782 v => panic!("expected 'hello world', got {v:?}"),
5783 }
5784 }
5785
5786 #[test]
5787 fn test_string_substr() {
5788 match eval("'hello world'.substr(6, 5)").unwrap() {
5789 Value::String(s) => assert_eq!(s, "world"),
5790 v => panic!("expected 'world', got {v:?}"),
5791 }
5792 }
5793
5794 // ── Math built-in ─────────────────────────────────────────
5795
5796 #[test]
5797 fn test_math_constants() {
5798 let r = eval("Math.PI").unwrap();
5799 match r {
5800 Value::Number(n) => assert!((n - std::f64::consts::PI).abs() < 1e-10),
5801 _ => panic!("Expected number"),
5802 }
5803 let r = eval("Math.E").unwrap();
5804 match r {
5805 Value::Number(n) => assert!((n - std::f64::consts::E).abs() < 1e-10),
5806 _ => panic!("Expected number"),
5807 }
5808 let r = eval("Math.SQRT2").unwrap();
5809 match r {
5810 Value::Number(n) => assert!((n - std::f64::consts::SQRT_2).abs() < 1e-10),
5811 _ => panic!("Expected number"),
5812 }
5813 }
5814
5815 #[test]
5816 fn test_math_abs() {
5817 assert_eq!(eval("Math.abs(-5)").unwrap().to_number(), 5.0);
5818 assert_eq!(eval("Math.abs(3)").unwrap().to_number(), 3.0);
5819 assert_eq!(eval("Math.abs(0)").unwrap().to_number(), 0.0);
5820 }
5821
5822 #[test]
5823 fn test_math_floor_ceil_round_trunc() {
5824 assert_eq!(eval("Math.floor(4.7)").unwrap().to_number(), 4.0);
5825 assert_eq!(eval("Math.ceil(4.2)").unwrap().to_number(), 5.0);
5826 assert_eq!(eval("Math.round(4.5)").unwrap().to_number(), 5.0);
5827 assert_eq!(eval("Math.round(4.4)").unwrap().to_number(), 4.0);
5828 assert_eq!(eval("Math.trunc(4.7)").unwrap().to_number(), 4.0);
5829 assert_eq!(eval("Math.trunc(-4.7)").unwrap().to_number(), -4.0);
5830 }
5831
5832 #[test]
5833 fn test_math_max_min() {
5834 assert_eq!(eval("Math.max(1, 3, 2)").unwrap().to_number(), 3.0);
5835 assert_eq!(eval("Math.min(1, 3, 2)").unwrap().to_number(), 1.0);
5836 assert_eq!(eval("Math.max()").unwrap().to_number(), f64::NEG_INFINITY);
5837 assert_eq!(eval("Math.min()").unwrap().to_number(), f64::INFINITY);
5838 }
5839
5840 #[test]
5841 fn test_math_pow_sqrt() {
5842 assert_eq!(eval("Math.pow(2, 10)").unwrap().to_number(), 1024.0);
5843 assert_eq!(eval("Math.sqrt(9)").unwrap().to_number(), 3.0);
5844 let cbrt = eval("Math.cbrt(27)").unwrap().to_number();
5845 assert!((cbrt - 3.0).abs() < 1e-10);
5846 }
5847
5848 #[test]
5849 fn test_math_trig() {
5850 let sin = eval("Math.sin(0)").unwrap().to_number();
5851 assert!(sin.abs() < 1e-10);
5852 let cos = eval("Math.cos(0)").unwrap().to_number();
5853 assert!((cos - 1.0).abs() < 1e-10);
5854 let atan2 = eval("Math.atan2(1, 1)").unwrap().to_number();
5855 assert!((atan2 - std::f64::consts::FRAC_PI_4).abs() < 1e-10);
5856 }
5857
5858 #[test]
5859 fn test_math_log_exp() {
5860 let exp = eval("Math.exp(1)").unwrap().to_number();
5861 assert!((exp - std::f64::consts::E).abs() < 1e-10);
5862 let log = eval("Math.log(Math.E)").unwrap().to_number();
5863 assert!((log - 1.0).abs() < 1e-10);
5864 let log2 = eval("Math.log2(8)").unwrap().to_number();
5865 assert!((log2 - 3.0).abs() < 1e-10);
5866 let log10 = eval("Math.log10(1000)").unwrap().to_number();
5867 assert!((log10 - 3.0).abs() < 1e-10);
5868 }
5869
5870 #[test]
5871 fn test_math_sign() {
5872 assert_eq!(eval("Math.sign(5)").unwrap().to_number(), 1.0);
5873 assert_eq!(eval("Math.sign(-5)").unwrap().to_number(), -1.0);
5874 assert_eq!(eval("Math.sign(0)").unwrap().to_number(), 0.0);
5875 }
5876
5877 #[test]
5878 fn test_math_clz32() {
5879 assert_eq!(eval("Math.clz32(1)").unwrap().to_number(), 31.0);
5880 assert_eq!(eval("Math.clz32(0)").unwrap().to_number(), 32.0);
5881 }
5882
5883 #[test]
5884 fn test_math_imul() {
5885 assert_eq!(eval("Math.imul(3, 4)").unwrap().to_number(), 12.0);
5886 assert_eq!(eval("Math.imul(0xffffffff, 5)").unwrap().to_number(), -5.0);
5887 }
5888
5889 #[test]
5890 fn test_math_hypot() {
5891 assert_eq!(eval("Math.hypot(3, 4)").unwrap().to_number(), 5.0);
5892 assert_eq!(eval("Math.hypot()").unwrap().to_number(), 0.0);
5893 }
5894
5895 #[test]
5896 fn test_math_random() {
5897 match eval("var r = Math.random(); r >= 0 && r < 1").unwrap() {
5898 Value::Boolean(b) => assert!(b),
5899 v => panic!("expected true, got {v:?}"),
5900 }
5901 }
5902
5903 #[test]
5904 fn test_math_fround() {
5905 let r = eval("Math.fround(5.5)").unwrap().to_number();
5906 assert_eq!(r, 5.5f32 as f64);
5907 }
5908
5909 // ── Date built-in ─────────────────────────────────────────
5910
5911 #[test]
5912 fn test_date_now() {
5913 let r = eval("Date.now()").unwrap().to_number();
5914 assert!(r > 1_577_836_800_000.0);
5915 }
5916
5917 #[test]
5918 fn test_date_utc() {
5919 let r = eval("Date.UTC(2020, 0, 1)").unwrap().to_number();
5920 assert_eq!(r, 1_577_836_800_000.0);
5921 }
5922
5923 #[test]
5924 fn test_date_parse() {
5925 let r = eval("Date.parse('2020-01-01T00:00:00.000Z')")
5926 .unwrap()
5927 .to_number();
5928 assert_eq!(r, 1_577_836_800_000.0);
5929 }
5930
5931 #[test]
5932 fn test_date_constructor_ms() {
5933 let r = eval("var d = new Date(1577836800000); d.getFullYear()")
5934 .unwrap()
5935 .to_number();
5936 assert_eq!(r, 2020.0);
5937 }
5938
5939 #[test]
5940 fn test_date_constructor_components() {
5941 let r = eval("var d = new Date(2020, 0, 1, 0, 0, 0, 0); d.getTime()")
5942 .unwrap()
5943 .to_number();
5944 assert_eq!(r, 1_577_836_800_000.0);
5945 }
5946
5947 #[test]
5948 fn test_date_getters() {
5949 let src = r#"
5950 var d = new Date(1577836800000);
5951 var results = [
5952 d.getFullYear(),
5953 d.getMonth(),
5954 d.getDate(),
5955 d.getHours(),
5956 d.getMinutes(),
5957 d.getSeconds(),
5958 d.getMilliseconds(),
5959 d.getDay()
5960 ];
5961 results[0] === 2020 && results[1] === 0 && results[2] === 1 &&
5962 results[3] === 0 && results[4] === 0 && results[5] === 0 &&
5963 results[6] === 0 && results[7] === 3
5964 "#;
5965 match eval(src).unwrap() {
5966 Value::Boolean(b) => assert!(b),
5967 v => panic!("expected true, got {v:?}"),
5968 }
5969 }
5970
5971 #[test]
5972 fn test_date_setters() {
5973 let src = r#"
5974 var d = new Date(1577836800000);
5975 d.setFullYear(2025);
5976 d.getFullYear()
5977 "#;
5978 assert_eq!(eval(src).unwrap().to_number(), 2025.0);
5979 }
5980
5981 #[test]
5982 fn test_date_to_iso_string() {
5983 match eval("var d = new Date(1577836800000); d.toISOString()").unwrap() {
5984 Value::String(s) => assert_eq!(s, "2020-01-01T00:00:00.000Z"),
5985 v => panic!("expected ISO string, got {v:?}"),
5986 }
5987 }
5988
5989 #[test]
5990 fn test_date_value_of() {
5991 let r = eval("var d = new Date(1577836800000); d.valueOf()")
5992 .unwrap()
5993 .to_number();
5994 assert_eq!(r, 1_577_836_800_000.0);
5995 }
5996
5997 #[test]
5998 fn test_date_to_string() {
5999 let r = eval("var d = new Date(1577836800000); d.toString()").unwrap();
6000 match r {
6001 Value::String(s) => assert!(s.contains("2020") && s.contains("GMT")),
6002 _ => panic!("Expected string"),
6003 }
6004 }
6005
6006 #[test]
6007 fn test_date_to_json() {
6008 match eval("var d = new Date(1577836800000); d.toJSON()").unwrap() {
6009 Value::String(s) => assert_eq!(s, "2020-01-01T00:00:00.000Z"),
6010 v => panic!("expected ISO string, got {v:?}"),
6011 }
6012 }
6013
6014 #[test]
6015 fn test_date_constructor_string() {
6016 let r = eval("var d = new Date('2020-06-15T12:30:00Z'); d.getMonth()")
6017 .unwrap()
6018 .to_number();
6019 assert_eq!(r, 5.0);
6020 }
6021
6022 // ── JSON built-in ─────────────────────────────────────────
6023
6024 #[test]
6025 fn test_json_parse_primitives() {
6026 assert!(matches!(eval("JSON.parse('null')").unwrap(), Value::Null));
6027 match eval("JSON.parse('true')").unwrap() {
6028 Value::Boolean(b) => assert!(b),
6029 v => panic!("expected true, got {v:?}"),
6030 }
6031 match eval("JSON.parse('false')").unwrap() {
6032 Value::Boolean(b) => assert!(!b),
6033 v => panic!("expected false, got {v:?}"),
6034 }
6035 assert_eq!(eval("JSON.parse('42')").unwrap().to_number(), 42.0);
6036 match eval(r#"JSON.parse('"hello"')"#).unwrap() {
6037 Value::String(s) => assert_eq!(s, "hello"),
6038 v => panic!("expected 'hello', got {v:?}"),
6039 }
6040 }
6041
6042 #[test]
6043 fn test_json_parse_array() {
6044 let src = r#"
6045 var a = JSON.parse('[1, 2, 3]');
6046 a.length === 3 && a[0] === 1 && a[1] === 2 && a[2] === 3
6047 "#;
6048 match eval(src).unwrap() {
6049 Value::Boolean(b) => assert!(b),
6050 v => panic!("expected true, got {v:?}"),
6051 }
6052 }
6053
6054 #[test]
6055 fn test_json_parse_object() {
6056 let src = r#"
6057 var o = JSON.parse('{"name":"test","value":42}');
6058 o.name === "test" && o.value === 42
6059 "#;
6060 match eval(src).unwrap() {
6061 Value::Boolean(b) => assert!(b),
6062 v => panic!("expected true, got {v:?}"),
6063 }
6064 }
6065
6066 #[test]
6067 fn test_json_parse_nested() {
6068 let src = r#"
6069 var o = JSON.parse('{"a":[1,{"b":2}]}');
6070 o.a[1].b === 2
6071 "#;
6072 match eval(src).unwrap() {
6073 Value::Boolean(b) => assert!(b),
6074 v => panic!("expected true, got {v:?}"),
6075 }
6076 }
6077
6078 #[test]
6079 fn test_json_parse_invalid() {
6080 assert!(eval("JSON.parse('{invalid}')").is_err());
6081 assert!(eval("JSON.parse('')").is_err());
6082 }
6083
6084 #[test]
6085 fn test_json_stringify_primitives() {
6086 match eval("JSON.stringify(null)").unwrap() {
6087 Value::String(s) => assert_eq!(s, "null"),
6088 v => panic!("expected 'null', got {v:?}"),
6089 }
6090 match eval("JSON.stringify(true)").unwrap() {
6091 Value::String(s) => assert_eq!(s, "true"),
6092 v => panic!("expected 'true', got {v:?}"),
6093 }
6094 match eval("JSON.stringify(42)").unwrap() {
6095 Value::String(s) => assert_eq!(s, "42"),
6096 v => panic!("expected '42', got {v:?}"),
6097 }
6098 match eval(r#"JSON.stringify("hello")"#).unwrap() {
6099 Value::String(s) => assert_eq!(s, "\"hello\""),
6100 v => panic!("expected quoted hello, got {v:?}"),
6101 }
6102 }
6103
6104 #[test]
6105 fn test_json_stringify_array() {
6106 match eval("JSON.stringify([1, 2, 3])").unwrap() {
6107 Value::String(s) => assert_eq!(s, "[1,2,3]"),
6108 v => panic!("expected '[1,2,3]', got {v:?}"),
6109 }
6110 }
6111
6112 #[test]
6113 fn test_json_stringify_object() {
6114 let src = r#"
6115 var o = {a: 1, b: "hello"};
6116 JSON.stringify(o)
6117 "#;
6118 let r = eval(src).unwrap();
6119 match r {
6120 Value::String(s) => {
6121 assert!(s.contains("\"a\"") && s.contains("\"b\""));
6122 assert!(s.contains('1') && s.contains("\"hello\""));
6123 }
6124 _ => panic!("Expected string"),
6125 }
6126 }
6127
6128 #[test]
6129 fn test_json_stringify_nested() {
6130 let src = r#"
6131 JSON.stringify({a: [1, 2], b: {c: 3}})
6132 "#;
6133 let r = eval(src).unwrap();
6134 match r {
6135 Value::String(s) => {
6136 assert!(s.contains("[1,2]"));
6137 assert!(s.contains("\"c\":3") || s.contains("\"c\": 3"));
6138 }
6139 _ => panic!("Expected string"),
6140 }
6141 }
6142
6143 #[test]
6144 fn test_json_stringify_special_values() {
6145 match eval("JSON.stringify(NaN)").unwrap() {
6146 Value::String(s) => assert_eq!(s, "null"),
6147 v => panic!("expected 'null', got {v:?}"),
6148 }
6149 match eval("JSON.stringify(Infinity)").unwrap() {
6150 Value::String(s) => assert_eq!(s, "null"),
6151 v => panic!("expected 'null', got {v:?}"),
6152 }
6153 assert!(matches!(
6154 eval("JSON.stringify(undefined)").unwrap(),
6155 Value::Undefined
6156 ));
6157 }
6158
6159 #[test]
6160 fn test_json_stringify_with_indent() {
6161 let src = r#"JSON.stringify([1, 2], null, 2)"#;
6162 let r = eval(src).unwrap();
6163 match r {
6164 Value::String(s) => {
6165 assert!(s.contains('\n'));
6166 assert!(s.contains(" 1"));
6167 }
6168 _ => panic!("Expected string"),
6169 }
6170 }
6171
6172 #[test]
6173 fn test_json_parse_escape_sequences() {
6174 let src = r#"JSON.parse('"hello\\nworld"')"#;
6175 match eval(src).unwrap() {
6176 Value::String(s) => assert_eq!(s, "hello\nworld"),
6177 v => panic!("expected escaped string, got {v:?}"),
6178 }
6179 }
6180
6181 #[test]
6182 fn test_json_roundtrip() {
6183 let src = r#"
6184 var original = {name: "test", values: [1, 2, 3], nested: {ok: true}};
6185 var json = JSON.stringify(original);
6186 var parsed = JSON.parse(json);
6187 parsed.name === "test" && parsed.values[1] === 2 && parsed.nested.ok === true
6188 "#;
6189 match eval(src).unwrap() {
6190 Value::Boolean(b) => assert!(b),
6191 v => panic!("expected true, got {v:?}"),
6192 }
6193 }
6194
6195 #[test]
6196 fn test_json_stringify_circular_detection() {
6197 let src = r#"
6198 var obj = {};
6199 obj.self = obj;
6200 try {
6201 JSON.stringify(obj);
6202 false;
6203 } catch (e) {
6204 e.message.indexOf("circular") !== -1;
6205 }
6206 "#;
6207 match eval(src).unwrap() {
6208 Value::Boolean(b) => assert!(b),
6209 v => panic!("expected true, got {v:?}"),
6210 }
6211 }
6212
6213 #[test]
6214 fn test_json_parse_unicode_escape() {
6215 let src = r#"JSON.parse('"\\u0041"')"#;
6216 match eval(src).unwrap() {
6217 Value::String(s) => assert_eq!(s, "A"),
6218 v => panic!("expected 'A', got {v:?}"),
6219 }
6220 }
6221
6222 #[test]
6223 fn test_json_stringify_empty() {
6224 match eval("JSON.stringify([])").unwrap() {
6225 Value::String(s) => assert_eq!(s, "[]"),
6226 v => panic!("expected '[]', got {v:?}"),
6227 }
6228 match eval("JSON.stringify({})").unwrap() {
6229 Value::String(s) => assert_eq!(s, "{}"),
6230 v => panic!("expected '{{}}', got {v:?}"),
6231 }
6232 }
6233
6234 // ── RegExp tests ────────────────────────────────────────
6235
6236 #[test]
6237 fn test_regexp_constructor() {
6238 match eval("var r = new RegExp('abc', 'g'); r.source").unwrap() {
6239 Value::String(s) => assert_eq!(s, "abc"),
6240 v => panic!("expected 'abc', got {v:?}"),
6241 }
6242 match eval("var r = new RegExp('abc', 'gi'); r.flags").unwrap() {
6243 Value::String(s) => assert_eq!(s, "gi"),
6244 v => panic!("expected 'gi', got {v:?}"),
6245 }
6246 match eval("var r = new RegExp('abc'); r.global").unwrap() {
6247 Value::Boolean(b) => assert!(!b),
6248 v => panic!("expected false, got {v:?}"),
6249 }
6250 match eval("var r = new RegExp('abc', 'g'); r.global").unwrap() {
6251 Value::Boolean(b) => assert!(b),
6252 v => panic!("expected true, got {v:?}"),
6253 }
6254 }
6255
6256 #[test]
6257 fn test_regexp_test() {
6258 match eval("var r = new RegExp('abc'); r.test('xabcx')").unwrap() {
6259 Value::Boolean(b) => assert!(b),
6260 v => panic!("expected true, got {v:?}"),
6261 }
6262 match eval("var r = new RegExp('abc'); r.test('xyz')").unwrap() {
6263 Value::Boolean(b) => assert!(!b),
6264 v => panic!("expected false, got {v:?}"),
6265 }
6266 match eval("var r = new RegExp('\\\\d+'); r.test('abc123')").unwrap() {
6267 Value::Boolean(b) => assert!(b),
6268 v => panic!("expected true, got {v:?}"),
6269 }
6270 }
6271
6272 #[test]
6273 fn test_regexp_exec() {
6274 match eval("var r = new RegExp('(a)(b)(c)'); var m = r.exec('abc'); m[0]").unwrap() {
6275 Value::String(s) => assert_eq!(s, "abc"),
6276 v => panic!("expected 'abc', got {v:?}"),
6277 }
6278 match eval("var r = new RegExp('(a)(b)(c)'); var m = r.exec('abc'); m[1]").unwrap() {
6279 Value::String(s) => assert_eq!(s, "a"),
6280 v => panic!("expected 'a', got {v:?}"),
6281 }
6282 match eval("var r = new RegExp('b+'); var m = r.exec('aabbc'); m[0]").unwrap() {
6283 Value::String(s) => assert_eq!(s, "bb"),
6284 v => panic!("expected 'bb', got {v:?}"),
6285 }
6286 match eval("var r = new RegExp('xyz'); r.exec('abc')").unwrap() {
6287 Value::Null => {}
6288 v => panic!("expected null, got {v:?}"),
6289 }
6290 }
6291
6292 #[test]
6293 fn test_regexp_exec_global() {
6294 let src = "var r = new RegExp('a', 'g'); r.exec('aba')[0]";
6295 match eval(src).unwrap() {
6296 Value::String(s) => assert_eq!(s, "a"),
6297 v => panic!("expected 'a', got {v:?}"),
6298 }
6299 let src = r#"
6300 var r = new RegExp('a', 'g');
6301 r.exec('aba');
6302 var m = r.exec('aba');
6303 m[0] + ',' + m.index
6304 "#;
6305 match eval(src).unwrap() {
6306 Value::String(s) => assert_eq!(s, "a,2"),
6307 v => panic!("expected 'a,2', got {v:?}"),
6308 }
6309 }
6310
6311 #[test]
6312 fn test_regexp_to_string() {
6313 match eval("var r = new RegExp('abc', 'gi'); r.toString()").unwrap() {
6314 Value::String(s) => assert_eq!(s, "/abc/gi"),
6315 v => panic!("expected '/abc/gi', got {v:?}"),
6316 }
6317 match eval("/hello\\d+/.toString()").unwrap() {
6318 Value::String(s) => assert_eq!(s, "/hello\\d+/"),
6319 v => panic!("expected '/hello\\d+/', got {v:?}"),
6320 }
6321 }
6322
6323 #[test]
6324 fn test_regexp_literal() {
6325 match eval("/abc/.test('abc')").unwrap() {
6326 Value::Boolean(b) => assert!(b),
6327 v => panic!("expected true, got {v:?}"),
6328 }
6329 match eval("/abc/.test('xyz')").unwrap() {
6330 Value::Boolean(b) => assert!(!b),
6331 v => panic!("expected false, got {v:?}"),
6332 }
6333 match eval("/\\d+/.test('123')").unwrap() {
6334 Value::Boolean(b) => assert!(b),
6335 v => panic!("expected true, got {v:?}"),
6336 }
6337 match eval("/abc/i.test('ABC')").unwrap() {
6338 Value::Boolean(b) => assert!(b),
6339 v => panic!("expected true, got {v:?}"),
6340 }
6341 }
6342
6343 #[test]
6344 fn test_regexp_literal_exec() {
6345 match eval("var m = /([a-z]+)(\\d+)/.exec('abc123'); m[0]").unwrap() {
6346 Value::String(s) => assert_eq!(s, "abc123"),
6347 v => panic!("expected 'abc123', got {v:?}"),
6348 }
6349 match eval("var m = /([a-z]+)(\\d+)/.exec('abc123'); m[1]").unwrap() {
6350 Value::String(s) => assert_eq!(s, "abc"),
6351 v => panic!("expected 'abc', got {v:?}"),
6352 }
6353 match eval("var m = /([a-z]+)(\\d+)/.exec('abc123'); m[2]").unwrap() {
6354 Value::String(s) => assert_eq!(s, "123"),
6355 v => panic!("expected '123', got {v:?}"),
6356 }
6357 }
6358
6359 #[test]
6360 fn test_string_match_regexp() {
6361 match eval("'hello world'.match(/world/)[0]").unwrap() {
6362 Value::String(s) => assert_eq!(s, "world"),
6363 v => panic!("expected 'world', got {v:?}"),
6364 }
6365 match eval("'aaa'.match(/a/g).length").unwrap() {
6366 Value::Number(n) => assert_eq!(n, 3.0),
6367 v => panic!("expected 3, got {v:?}"),
6368 }
6369 match eval("'abc'.match(/xyz/)").unwrap() {
6370 Value::Null => {}
6371 v => panic!("expected null, got {v:?}"),
6372 }
6373 }
6374
6375 #[test]
6376 fn test_string_search_regexp() {
6377 match eval("'hello world'.search(/world/)").unwrap() {
6378 Value::Number(n) => assert_eq!(n, 6.0),
6379 v => panic!("expected 6, got {v:?}"),
6380 }
6381 match eval("'abc'.search(/xyz/)").unwrap() {
6382 Value::Number(n) => assert_eq!(n, -1.0),
6383 v => panic!("expected -1, got {v:?}"),
6384 }
6385 match eval("'abc123'.search(/\\d/)").unwrap() {
6386 Value::Number(n) => assert_eq!(n, 3.0),
6387 v => panic!("expected 3, got {v:?}"),
6388 }
6389 }
6390
6391 #[test]
6392 fn test_string_replace_regexp() {
6393 match eval("'hello world'.replace(/world/, 'rust')").unwrap() {
6394 Value::String(s) => assert_eq!(s, "hello rust"),
6395 v => panic!("expected 'hello rust', got {v:?}"),
6396 }
6397 match eval("'aaa'.replace(/a/, 'b')").unwrap() {
6398 Value::String(s) => assert_eq!(s, "baa"),
6399 v => panic!("expected 'baa', got {v:?}"),
6400 }
6401 match eval("'aaa'.replace(/a/g, 'b')").unwrap() {
6402 Value::String(s) => assert_eq!(s, "bbb"),
6403 v => panic!("expected 'bbb', got {v:?}"),
6404 }
6405 }
6406
6407 #[test]
6408 fn test_string_replace_capture_groups() {
6409 let src = r#"'John Smith'.replace(/(\w+) (\w+)/, '$2, $1')"#;
6410 match eval(src).unwrap() {
6411 Value::String(s) => assert_eq!(s, "Smith, John"),
6412 v => panic!("expected 'Smith, John', got {v:?}"),
6413 }
6414 match eval("'abc'.replace(/(b)/, '[$1]')").unwrap() {
6415 Value::String(s) => assert_eq!(s, "a[b]c"),
6416 v => panic!("expected 'a[b]c', got {v:?}"),
6417 }
6418 }
6419
6420 #[test]
6421 fn test_string_split_regexp() {
6422 match eval("'a1b2c3'.split(/\\d/).length").unwrap() {
6423 Value::Number(n) => assert_eq!(n, 4.0),
6424 v => panic!("expected 4, got {v:?}"),
6425 }
6426 match eval("'a1b2c3'.split(/\\d/)[0]").unwrap() {
6427 Value::String(s) => assert_eq!(s, "a"),
6428 v => panic!("expected 'a', got {v:?}"),
6429 }
6430 }
6431
6432 #[test]
6433 fn test_regexp_ignore_case() {
6434 match eval("/abc/i.exec('XAbCx')[0]").unwrap() {
6435 Value::String(s) => assert_eq!(s, "AbC"),
6436 v => panic!("expected 'AbC', got {v:?}"),
6437 }
6438 }
6439
6440 #[test]
6441 fn test_regexp_multiline() {
6442 match eval("/^b/m.test('a\\nb')").unwrap() {
6443 Value::Boolean(b) => assert!(b),
6444 v => panic!("expected true, got {v:?}"),
6445 }
6446 match eval("/^b/.test('a\\nb')").unwrap() {
6447 Value::Boolean(b) => assert!(!b),
6448 v => panic!("expected false, got {v:?}"),
6449 }
6450 }
6451
6452 #[test]
6453 fn test_regexp_dot_all() {
6454 match eval("/a.b/s.test('a\\nb')").unwrap() {
6455 Value::Boolean(b) => assert!(b),
6456 v => panic!("expected true, got {v:?}"),
6457 }
6458 match eval("/a.b/.test('a\\nb')").unwrap() {
6459 Value::Boolean(b) => assert!(!b),
6460 v => panic!("expected false, got {v:?}"),
6461 }
6462 }
6463
6464 #[test]
6465 fn test_regexp_word_boundary() {
6466 match eval("/\\bfoo\\b/.test('a foo b')").unwrap() {
6467 Value::Boolean(b) => assert!(b),
6468 v => panic!("expected true, got {v:?}"),
6469 }
6470 match eval("/\\bfoo\\b/.test('foobar')").unwrap() {
6471 Value::Boolean(b) => assert!(!b),
6472 v => panic!("expected false, got {v:?}"),
6473 }
6474 }
6475
6476 #[test]
6477 fn test_regexp_quantifiers_vm() {
6478 match eval("/a{3}/.test('aaa')").unwrap() {
6479 Value::Boolean(b) => assert!(b),
6480 v => panic!("expected true, got {v:?}"),
6481 }
6482 match eval("/a{3}/.test('aa')").unwrap() {
6483 Value::Boolean(b) => assert!(!b),
6484 v => panic!("expected false, got {v:?}"),
6485 }
6486 match eval("/a+?/.exec('aaa')[0]").unwrap() {
6487 Value::String(s) => assert_eq!(s, "a"),
6488 v => panic!("expected 'a', got {v:?}"),
6489 }
6490 }
6491
6492 #[test]
6493 fn test_regexp_alternation_vm() {
6494 match eval("/cat|dog/.exec('I have a dog')[0]").unwrap() {
6495 Value::String(s) => assert_eq!(s, "dog"),
6496 v => panic!("expected 'dog', got {v:?}"),
6497 }
6498 }
6499
6500 #[test]
6501 fn test_regexp_lookahead_vm() {
6502 match eval("/a(?=b)/.test('ab')").unwrap() {
6503 Value::Boolean(b) => assert!(b),
6504 v => panic!("expected true, got {v:?}"),
6505 }
6506 match eval("/a(?=b)/.test('ac')").unwrap() {
6507 Value::Boolean(b) => assert!(!b),
6508 v => panic!("expected false, got {v:?}"),
6509 }
6510 match eval("/a(?!b)/.test('ac')").unwrap() {
6511 Value::Boolean(b) => assert!(b),
6512 v => panic!("expected true, got {v:?}"),
6513 }
6514 }
6515
6516 #[test]
6517 fn test_regexp_char_class_vm() {
6518 match eval("/[abc]/.test('b')").unwrap() {
6519 Value::Boolean(b) => assert!(b),
6520 v => panic!("expected true, got {v:?}"),
6521 }
6522 match eval("/[a-z]+/.exec('Hello')[0]").unwrap() {
6523 Value::String(s) => assert_eq!(s, "ello"),
6524 v => panic!("expected 'ello', got {v:?}"),
6525 }
6526 }
6527
6528 #[test]
6529 fn test_regexp_backreference_vm() {
6530 match eval("/(a)\\1/.test('aa')").unwrap() {
6531 Value::Boolean(b) => assert!(b),
6532 v => panic!("expected true, got {v:?}"),
6533 }
6534 match eval("/(a)\\1/.test('ab')").unwrap() {
6535 Value::Boolean(b) => assert!(!b),
6536 v => panic!("expected false, got {v:?}"),
6537 }
6538 }
6539
6540 #[test]
6541 fn test_regexp_properties() {
6542 match eval("var r = /abc/gim; r.global").unwrap() {
6543 Value::Boolean(b) => assert!(b),
6544 v => panic!("expected true, got {v:?}"),
6545 }
6546 match eval("/abc/.lastIndex").unwrap() {
6547 Value::Number(n) => assert_eq!(n, 0.0),
6548 v => panic!("expected 0, got {v:?}"),
6549 }
6550 }
6551
6552 #[test]
6553 fn test_string_replace_all_regexp() {
6554 match eval("'aba'.replaceAll(/a/g, 'x')").unwrap() {
6555 Value::String(s) => assert_eq!(s, "xbx"),
6556 v => panic!("expected 'xbx', got {v:?}"),
6557 }
6558 }
6559
6560 // ── Map tests ─────────────────────────────────────────────
6561
6562 #[test]
6563 fn test_map_basic() {
6564 match eval("var m = new Map(); m.set('a', 1); m.get('a')").unwrap() {
6565 Value::Number(n) => assert_eq!(n, 1.0),
6566 v => panic!("expected 1, got {v:?}"),
6567 }
6568 }
6569
6570 #[test]
6571 fn test_map_size() {
6572 match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.size").unwrap() {
6573 Value::Number(n) => assert_eq!(n, 2.0),
6574 v => panic!("expected 2, got {v:?}"),
6575 }
6576 }
6577
6578 #[test]
6579 fn test_map_has() {
6580 match eval("var m = new Map(); m.set('x', 10); m.has('x')").unwrap() {
6581 Value::Boolean(b) => assert!(b),
6582 v => panic!("expected true, got {v:?}"),
6583 }
6584 match eval("var m = new Map(); m.has('x')").unwrap() {
6585 Value::Boolean(b) => assert!(!b),
6586 v => panic!("expected false, got {v:?}"),
6587 }
6588 }
6589
6590 #[test]
6591 fn test_map_delete() {
6592 match eval("var m = new Map(); m.set('a', 1); m['delete']('a'); m.has('a')").unwrap() {
6593 Value::Boolean(b) => assert!(!b),
6594 v => panic!("expected false, got {v:?}"),
6595 }
6596 match eval("var m = new Map(); m.set('a', 1); m['delete']('a'); m.size").unwrap() {
6597 Value::Number(n) => assert_eq!(n, 0.0),
6598 v => panic!("expected 0, got {v:?}"),
6599 }
6600 }
6601
6602 #[test]
6603 fn test_map_clear() {
6604 match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.clear(); m.size").unwrap() {
6605 Value::Number(n) => assert_eq!(n, 0.0),
6606 v => panic!("expected 0, got {v:?}"),
6607 }
6608 }
6609
6610 #[test]
6611 fn test_map_overwrite() {
6612 match eval("var m = new Map(); m.set('a', 1); m.set('a', 2); m.get('a')").unwrap() {
6613 Value::Number(n) => assert_eq!(n, 2.0),
6614 v => panic!("expected 2, got {v:?}"),
6615 }
6616 // Size should still be 1 after overwriting.
6617 match eval("var m = new Map(); m.set('a', 1); m.set('a', 2); m.size").unwrap() {
6618 Value::Number(n) => assert_eq!(n, 1.0),
6619 v => panic!("expected 1, got {v:?}"),
6620 }
6621 }
6622
6623 #[test]
6624 fn test_map_get_missing() {
6625 match eval("var m = new Map(); m.get('missing')").unwrap() {
6626 Value::Undefined => {}
6627 v => panic!("expected undefined, got {v:?}"),
6628 }
6629 }
6630
6631 #[test]
6632 fn test_map_chaining() {
6633 // set() returns the Map for chaining.
6634 match eval("var m = new Map(); m.set('a', 1).set('b', 2); m.size").unwrap() {
6635 Value::Number(n) => assert_eq!(n, 2.0),
6636 v => panic!("expected 2, got {v:?}"),
6637 }
6638 }
6639
6640 #[test]
6641 fn test_map_nan_key() {
6642 // NaN === NaN for Map keys (SameValueZero).
6643 match eval("var m = new Map(); m.set(NaN, 'nan'); m.get(NaN)").unwrap() {
6644 Value::String(s) => assert_eq!(s, "nan"),
6645 v => panic!("expected 'nan', got {v:?}"),
6646 }
6647 }
6648
6649 #[test]
6650 fn test_map_object_key() {
6651 match eval("var m = new Map(); var o = {}; m.set(o, 'val'); m.get(o)").unwrap() {
6652 Value::String(s) => assert_eq!(s, "val"),
6653 v => panic!("expected 'val', got {v:?}"),
6654 }
6655 }
6656
6657 #[test]
6658 fn test_map_constructor_with_pairs() {
6659 match eval("var m = new Map([['a', 1], ['b', 2]]); m.get('b')").unwrap() {
6660 Value::Number(n) => assert_eq!(n, 2.0),
6661 v => panic!("expected 2, got {v:?}"),
6662 }
6663 }
6664
6665 #[test]
6666 fn test_map_keys_values_entries() {
6667 match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.keys().length").unwrap() {
6668 Value::Number(n) => assert_eq!(n, 2.0),
6669 v => panic!("expected 2, got {v:?}"),
6670 }
6671 match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.values()[1]").unwrap() {
6672 Value::Number(n) => assert_eq!(n, 2.0),
6673 v => panic!("expected 2, got {v:?}"),
6674 }
6675 match eval("var m = new Map(); m.set('a', 1); m.entries()[0][0]").unwrap() {
6676 Value::String(s) => assert_eq!(s, "a"),
6677 v => panic!("expected 'a', got {v:?}"),
6678 }
6679 }
6680
6681 #[test]
6682 fn test_map_insertion_order() {
6683 match eval("var m = new Map(); m.set('c', 3); m.set('a', 1); m.set('b', 2); m.keys()[0]")
6684 .unwrap()
6685 {
6686 Value::String(s) => assert_eq!(s, "c"),
6687 v => panic!("expected 'c', got {v:?}"),
6688 }
6689 }
6690
6691 // ── Set tests ─────────────────────────────────────────────
6692
6693 #[test]
6694 fn test_set_basic() {
6695 match eval("var s = new Set(); s.add(1); s.add(2); s.size").unwrap() {
6696 Value::Number(n) => assert_eq!(n, 2.0),
6697 v => panic!("expected 2, got {v:?}"),
6698 }
6699 }
6700
6701 #[test]
6702 fn test_set_has() {
6703 match eval("var s = new Set(); s.add(42); s.has(42)").unwrap() {
6704 Value::Boolean(b) => assert!(b),
6705 v => panic!("expected true, got {v:?}"),
6706 }
6707 match eval("var s = new Set(); s.has(42)").unwrap() {
6708 Value::Boolean(b) => assert!(!b),
6709 v => panic!("expected false, got {v:?}"),
6710 }
6711 }
6712
6713 #[test]
6714 fn test_set_delete() {
6715 match eval("var s = new Set(); s.add(1); s['delete'](1); s.has(1)").unwrap() {
6716 Value::Boolean(b) => assert!(!b),
6717 v => panic!("expected false, got {v:?}"),
6718 }
6719 match eval("var s = new Set(); s.add(1); s['delete'](1); s.size").unwrap() {
6720 Value::Number(n) => assert_eq!(n, 0.0),
6721 v => panic!("expected 0, got {v:?}"),
6722 }
6723 }
6724
6725 #[test]
6726 fn test_set_clear() {
6727 match eval("var s = new Set(); s.add(1); s.add(2); s.clear(); s.size").unwrap() {
6728 Value::Number(n) => assert_eq!(n, 0.0),
6729 v => panic!("expected 0, got {v:?}"),
6730 }
6731 }
6732
6733 #[test]
6734 fn test_set_uniqueness() {
6735 match eval("var s = new Set(); s.add(1); s.add(1); s.add(1); s.size").unwrap() {
6736 Value::Number(n) => assert_eq!(n, 1.0),
6737 v => panic!("expected 1, got {v:?}"),
6738 }
6739 }
6740
6741 #[test]
6742 fn test_set_chaining() {
6743 match eval("var s = new Set(); s.add(1).add(2).add(3); s.size").unwrap() {
6744 Value::Number(n) => assert_eq!(n, 3.0),
6745 v => panic!("expected 3, got {v:?}"),
6746 }
6747 }
6748
6749 #[test]
6750 fn test_set_nan() {
6751 match eval("var s = new Set(); s.add(NaN); s.add(NaN); s.size").unwrap() {
6752 Value::Number(n) => assert_eq!(n, 1.0),
6753 v => panic!("expected 1, got {v:?}"),
6754 }
6755 match eval("var s = new Set(); s.add(NaN); s.has(NaN)").unwrap() {
6756 Value::Boolean(b) => assert!(b),
6757 v => panic!("expected true, got {v:?}"),
6758 }
6759 }
6760
6761 #[test]
6762 fn test_set_constructor_from_array() {
6763 match eval("var s = new Set([1, 2, 3, 2, 1]); s.size").unwrap() {
6764 Value::Number(n) => assert_eq!(n, 3.0),
6765 v => panic!("expected 3, got {v:?}"),
6766 }
6767 }
6768
6769 #[test]
6770 fn test_set_values() {
6771 match eval("var s = new Set(); s.add('a'); s.add('b'); s.values().length").unwrap() {
6772 Value::Number(n) => assert_eq!(n, 2.0),
6773 v => panic!("expected 2, got {v:?}"),
6774 }
6775 }
6776
6777 #[test]
6778 fn test_set_entries() {
6779 // Set.entries() returns [value, value] pairs.
6780 match eval("var s = new Set(); s.add('x'); s.entries()[0][0]").unwrap() {
6781 Value::String(s) => assert_eq!(s, "x"),
6782 v => panic!("expected 'x', got {v:?}"),
6783 }
6784 match eval("var s = new Set(); s.add('x'); s.entries()[0][1]").unwrap() {
6785 Value::String(s) => assert_eq!(s, "x"),
6786 v => panic!("expected 'x', got {v:?}"),
6787 }
6788 }
6789
6790 #[test]
6791 fn test_set_insertion_order() {
6792 match eval("var s = new Set(); s.add('c'); s.add('a'); s.add('b'); s.values()[0]").unwrap()
6793 {
6794 Value::String(s) => assert_eq!(s, "c"),
6795 v => panic!("expected 'c', got {v:?}"),
6796 }
6797 }
6798
6799 // ── WeakMap tests ─────────────────────────────────────────
6800
6801 #[test]
6802 fn test_weakmap_basic() {
6803 match eval("var wm = new WeakMap(); var o = {}; wm.set(o, 'val'); wm.get(o)").unwrap() {
6804 Value::String(s) => assert_eq!(s, "val"),
6805 v => panic!("expected 'val', got {v:?}"),
6806 }
6807 }
6808
6809 #[test]
6810 fn test_weakmap_has_delete() {
6811 match eval("var wm = new WeakMap(); var o = {}; wm.set(o, 1); wm.has(o)").unwrap() {
6812 Value::Boolean(b) => assert!(b),
6813 v => panic!("expected true, got {v:?}"),
6814 }
6815 match eval("var wm = new WeakMap(); var o = {}; wm.set(o, 1); wm['delete'](o); wm.has(o)")
6816 .unwrap()
6817 {
6818 Value::Boolean(b) => assert!(!b),
6819 v => panic!("expected false, got {v:?}"),
6820 }
6821 }
6822
6823 #[test]
6824 fn test_weakmap_rejects_primitive_key() {
6825 assert!(eval("var wm = new WeakMap(); wm.set('str', 1)").is_err());
6826 assert!(eval("var wm = new WeakMap(); wm.set(42, 1)").is_err());
6827 }
6828
6829 // ── WeakSet tests ─────────────────────────────────────────
6830
6831 #[test]
6832 fn test_weakset_basic() {
6833 match eval("var ws = new WeakSet(); var o = {}; ws.add(o); ws.has(o)").unwrap() {
6834 Value::Boolean(b) => assert!(b),
6835 v => panic!("expected true, got {v:?}"),
6836 }
6837 }
6838
6839 #[test]
6840 fn test_weakset_delete() {
6841 match eval("var ws = new WeakSet(); var o = {}; ws.add(o); ws['delete'](o); ws.has(o)")
6842 .unwrap()
6843 {
6844 Value::Boolean(b) => assert!(!b),
6845 v => panic!("expected false, got {v:?}"),
6846 }
6847 }
6848
6849 #[test]
6850 fn test_weakset_rejects_primitive() {
6851 assert!(eval("var ws = new WeakSet(); ws.add('str')").is_err());
6852 assert!(eval("var ws = new WeakSet(); ws.add(42)").is_err());
6853 }
6854
6855 // ── Promise tests ────────────────────────────────────────────
6856
6857 #[test]
6858 fn test_promise_typeof() {
6859 match eval("typeof Promise").unwrap() {
6860 Value::String(s) => assert_eq!(s, "function"),
6861 v => panic!("expected 'function', got {v:?}"),
6862 }
6863 }
6864
6865 #[test]
6866 fn test_promise_static_resolve_exists() {
6867 match eval("typeof Promise.resolve").unwrap() {
6868 Value::String(s) => assert_eq!(s, "function"),
6869 v => panic!("expected 'function', got {v:?}"),
6870 }
6871 }
6872
6873 #[test]
6874 fn test_promise_resolve_returns_object() {
6875 match eval("typeof Promise.resolve(42)").unwrap() {
6876 Value::String(s) => assert_eq!(s, "object"),
6877 v => panic!("expected 'object', got {v:?}"),
6878 }
6879 }
6880
6881 #[test]
6882 fn test_promise_resolve_then_exists() {
6883 match eval("typeof Promise.resolve(42).then").unwrap() {
6884 Value::String(s) => assert_eq!(s, "function"),
6885 v => panic!("expected 'function', got {v:?}"),
6886 }
6887 }
6888
6889 /// Helper: eval JS, then read an undeclared global set by callbacks.
6890 /// NOTE: Variables set inside closures MUST NOT be declared with `var`
6891 /// in the same scope that creates the closure, because the compiler
6892 /// would put them in Cells instead of globals.
6893 fn eval_global(source: &str, name: &str) -> Result<Value, RuntimeError> {
6894 let program = Parser::parse(source).expect("parse failed");
6895 let func = compiler::compile(&program).expect("compile failed");
6896 let mut vm = Vm::new();
6897 vm.execute(&func)?;
6898 Ok(vm.globals.get(name).cloned().unwrap_or(Value::Undefined))
6899 }
6900
6901 #[test]
6902 fn test_promise_resolve_then() {
6903 // Don't use `var result` — it would be captured. Use implicit global.
6904 match eval_global(
6905 "Promise.resolve(42).then(function(v) { result = v; });",
6906 "result",
6907 )
6908 .unwrap()
6909 {
6910 Value::Number(n) => assert_eq!(n, 42.0),
6911 v => panic!("expected 42, got {v:?}"),
6912 }
6913 }
6914
6915 #[test]
6916 fn test_promise_reject_catch() {
6917 // Use bracket notation for catch (it's a keyword in the parser).
6918 match eval_global(
6919 "Promise.reject('err')['catch'](function(e) { result = e; });",
6920 "result",
6921 )
6922 .unwrap()
6923 {
6924 Value::String(s) => assert_eq!(s, "err"),
6925 v => panic!("expected 'err', got {v:?}"),
6926 }
6927 }
6928
6929 #[test]
6930 fn test_promise_constructor_resolve() {
6931 match eval_global(
6932 "var p = Promise(function(resolve) { resolve(10); }); p.then(function(v) { result = v; });",
6933 "result",
6934 )
6935 .unwrap()
6936 {
6937 Value::Number(n) => assert_eq!(n, 10.0),
6938 v => panic!("expected 10, got {v:?}"),
6939 }
6940 }
6941
6942 #[test]
6943 fn test_promise_constructor_reject() {
6944 match eval_global(
6945 "var p = Promise(function(resolve, reject) { reject('fail'); }); p['catch'](function(e) { result = e; });",
6946 "result",
6947 )
6948 .unwrap()
6949 {
6950 Value::String(s) => assert_eq!(s, "fail"),
6951 v => panic!("expected 'fail', got {v:?}"),
6952 }
6953 }
6954
6955 #[test]
6956 fn test_promise_executor_runs_synchronously() {
6957 // Executor runs synchronously. Use eval to check completion value.
6958 match eval("var x = 'before'; Promise(function(resolve) { x = 'during'; resolve(); }); x")
6959 .unwrap()
6960 {
6961 Value::String(s) => assert_eq!(s, "during"),
6962 v => panic!("expected 'during', got {v:?}"),
6963 }
6964 }
6965
6966 #[test]
6967 fn test_promise_then_chaining() {
6968 match eval_global(
6969 "Promise.resolve(1).then(function(v) { return v + 1; }).then(function(v) { result = v; });",
6970 "result",
6971 )
6972 .unwrap()
6973 {
6974 Value::Number(n) => assert_eq!(n, 2.0),
6975 v => panic!("expected 2, got {v:?}"),
6976 }
6977 }
6978
6979 #[test]
6980 fn test_promise_catch_returns_to_then() {
6981 match eval_global(
6982 "Promise.reject('err')['catch'](function(e) { return 'recovered'; }).then(function(v) { result = v; });",
6983 "result",
6984 )
6985 .unwrap()
6986 {
6987 Value::String(s) => assert_eq!(s, "recovered"),
6988 v => panic!("expected 'recovered', got {v:?}"),
6989 }
6990 }
6991
6992 #[test]
6993 fn test_promise_then_error_goes_to_catch() {
6994 // When a then handler throws, the rejection reason is the thrown value
6995 // wrapped in an Error object by the VM. Check it's an object.
6996 match eval_global(
6997 "Promise.resolve(1).then(function(v) { throw 'oops'; })['catch'](function(e) { result = typeof e; });",
6998 "result",
6999 )
7000 .unwrap()
7001 {
7002 // The thrown string gets wrapped in an error object by RuntimeError::to_value.
7003 Value::String(s) => assert!(
7004 s == "object" || s == "string",
7005 "expected 'object' or 'string', got '{s}'"
7006 ),
7007 v => panic!("expected string type, got {v:?}"),
7008 }
7009 }
7010
7011 #[test]
7012 fn test_promise_resolve_with_promise() {
7013 match eval_global(
7014 "var p = Promise.resolve(99); p.then(function(v) { result = v; });",
7015 "result",
7016 )
7017 .unwrap()
7018 {
7019 Value::Number(n) => assert_eq!(n, 99.0),
7020 v => panic!("expected 99, got {v:?}"),
7021 }
7022 }
7023
7024 #[test]
7025 fn test_promise_multiple_then() {
7026 match eval_global(
7027 "var p = Promise.resolve(5); p.then(function(v) { a = v; }); p.then(function(v) { b = v * 2; });",
7028 "a",
7029 )
7030 .unwrap()
7031 {
7032 Value::Number(n) => assert_eq!(n, 5.0),
7033 v => panic!("expected 5, got {v:?}"),
7034 }
7035 }
7036
7037 #[test]
7038 fn test_promise_double_resolve_ignored() {
7039 match eval_global(
7040 "var p = Promise(function(resolve) { resolve(1); resolve(2); }); p.then(function(v) { result = v; });",
7041 "result",
7042 )
7043 .unwrap()
7044 {
7045 Value::Number(n) => assert_eq!(n, 1.0),
7046 v => panic!("expected 1, got {v:?}"),
7047 }
7048 }
7049
7050 #[test]
7051 fn test_promise_executor_throw_rejects() {
7052 match eval_global(
7053 "var p = Promise(function() { throw 'boom'; }); p['catch'](function(e) { result = e; });",
7054 "result",
7055 )
7056 .unwrap()
7057 {
7058 Value::String(s) => assert_eq!(s, "boom"),
7059 v => panic!("expected 'boom', got {v:?}"),
7060 }
7061 }
7062
7063 #[test]
7064 fn test_promise_finally_fulfilled() {
7065 match eval_global(
7066 "Promise.resolve(42)['finally'](function() { result = 'done'; });",
7067 "result",
7068 )
7069 .unwrap()
7070 {
7071 Value::String(s) => assert_eq!(s, "done"),
7072 v => panic!("expected 'done', got {v:?}"),
7073 }
7074 }
7075
7076 #[test]
7077 fn test_promise_finally_rejected() {
7078 match eval_global(
7079 "Promise.reject('err')['finally'](function() { result = 'done'; });",
7080 "result",
7081 )
7082 .unwrap()
7083 {
7084 Value::String(s) => assert_eq!(s, "done"),
7085 v => panic!("expected 'done', got {v:?}"),
7086 }
7087 }
7088
7089 #[test]
7090 fn test_promise_all_empty() {
7091 match eval_global(
7092 "Promise.all([]).then(function(v) { result = v.length; });",
7093 "result",
7094 )
7095 .unwrap()
7096 {
7097 Value::Number(n) => assert_eq!(n, 0.0),
7098 v => panic!("expected 0, got {v:?}"),
7099 }
7100 }
7101
7102 #[test]
7103 fn test_promise_all_resolved() {
7104 match eval_global(
7105 "Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]).then(function(v) { result = v[0] + v[1] + v[2]; });",
7106 "result",
7107 )
7108 .unwrap()
7109 {
7110 Value::Number(n) => assert_eq!(n, 6.0),
7111 v => panic!("expected 6, got {v:?}"),
7112 }
7113 }
7114
7115 #[test]
7116 fn test_promise_all_rejects_on_first() {
7117 match eval_global(
7118 "Promise.all([Promise.resolve(1), Promise.reject('fail')])['catch'](function(e) { result = e; });",
7119 "result",
7120 )
7121 .unwrap()
7122 {
7123 Value::String(s) => assert_eq!(s, "fail"),
7124 v => panic!("expected 'fail', got {v:?}"),
7125 }
7126 }
7127
7128 #[test]
7129 fn test_promise_race_first_wins() {
7130 match eval_global(
7131 "Promise.race([Promise.resolve('first'), Promise.resolve('second')]).then(function(v) { result = v; });",
7132 "result",
7133 )
7134 .unwrap()
7135 {
7136 Value::String(s) => assert_eq!(s, "first"),
7137 v => panic!("expected 'first', got {v:?}"),
7138 }
7139 }
7140
7141 #[test]
7142 fn test_promise_race_reject_wins() {
7143 match eval_global(
7144 "Promise.race([Promise.reject('err')])['catch'](function(e) { result = e; });",
7145 "result",
7146 )
7147 .unwrap()
7148 {
7149 Value::String(s) => assert_eq!(s, "err"),
7150 v => panic!("expected 'err', got {v:?}"),
7151 }
7152 }
7153
7154 #[test]
7155 fn test_promise_any_first_fulfilled() {
7156 match eval_global(
7157 "Promise.any([Promise.reject('a'), Promise.resolve('b')]).then(function(v) { result = v; });",
7158 "result",
7159 )
7160 .unwrap()
7161 {
7162 Value::String(s) => assert_eq!(s, "b"),
7163 v => panic!("expected 'b', got {v:?}"),
7164 }
7165 }
7166
7167 #[test]
7168 fn test_promise_any_all_rejected() {
7169 match eval_global(
7170 "Promise.any([Promise.reject('a'), Promise.reject('b')])['catch'](function(e) { result = e; });",
7171 "result",
7172 )
7173 .unwrap()
7174 {
7175 Value::String(ref s) if s.contains("rejected") => {}
7176 v => panic!("expected AggregateError string, got {v:?}"),
7177 }
7178 }
7179
7180 #[test]
7181 fn test_promise_all_with_non_promises() {
7182 match eval_global(
7183 "Promise.all([1, 2, 3]).then(function(v) { result = v[0] + v[1] + v[2]; });",
7184 "result",
7185 )
7186 .unwrap()
7187 {
7188 Value::Number(n) => assert_eq!(n, 6.0),
7189 v => panic!("expected 6, got {v:?}"),
7190 }
7191 }
7192
7193 #[test]
7194 fn test_promise_race_with_non_promise() {
7195 match eval_global(
7196 "Promise.race([42]).then(function(v) { result = v; });",
7197 "result",
7198 )
7199 .unwrap()
7200 {
7201 Value::Number(n) => assert_eq!(n, 42.0),
7202 v => panic!("expected 42, got {v:?}"),
7203 }
7204 }
7205
7206 #[test]
7207 fn test_promise_then_identity_passthrough() {
7208 match eval_global(
7209 "Promise.resolve(7).then().then(function(v) { result = v; });",
7210 "result",
7211 )
7212 .unwrap()
7213 {
7214 Value::Number(n) => assert_eq!(n, 7.0),
7215 v => panic!("expected 7, got {v:?}"),
7216 }
7217 }
7218
7219 #[test]
7220 fn test_promise_catch_passthrough() {
7221 match eval_global(
7222 "Promise.resolve(99)['catch'](function(e) { result = 'bad'; }).then(function(v) { result = v; });",
7223 "result",
7224 )
7225 .unwrap()
7226 {
7227 Value::Number(n) => assert_eq!(n, 99.0),
7228 v => panic!("expected 99, got {v:?}"),
7229 }
7230 }
7231
7232 #[test]
7233 fn test_promise_all_settled() {
7234 match eval_global(
7235 "Promise.allSettled([Promise.resolve(1), Promise.reject('err')]).then(function(v) { result = v[0].status + ',' + v[1].status; });",
7236 "result",
7237 )
7238 .unwrap()
7239 {
7240 Value::String(s) => assert_eq!(s, "fulfilled,rejected"),
7241 v => panic!("expected 'fulfilled,rejected', got {v:?}"),
7242 }
7243 }
7244
7245 // ── Iterator and for...of tests ────────────────────────
7246
7247 #[test]
7248 fn test_for_of_array() {
7249 match eval(
7250 "var result = ''; var arr = [10, 20, 30]; for (var x of arr) { result = result + x + ','; } result",
7251 )
7252 .unwrap()
7253 {
7254 Value::String(s) => assert_eq!(s, "10,20,30,"),
7255 v => panic!("expected '10,20,30,', got {v:?}"),
7256 }
7257 }
7258
7259 #[test]
7260 fn test_for_of_string() {
7261 match eval("var result = ''; for (var ch of 'abc') { result = result + ch; } result")
7262 .unwrap()
7263 {
7264 Value::String(s) => assert_eq!(s, "abc"),
7265 v => panic!("expected 'abc', got {v:?}"),
7266 }
7267 }
7268
7269 #[test]
7270 fn test_for_of_with_break() {
7271 match eval(
7272 "var result = 0; for (var x of [1, 2, 3, 4, 5]) { if (x === 3) break; result = result + x; } result",
7273 )
7274 .unwrap()
7275 {
7276 Value::Number(n) => assert_eq!(n, 3.0),
7277 v => panic!("expected 3, got {v:?}"),
7278 }
7279 }
7280
7281 #[test]
7282 fn test_for_of_with_continue() {
7283 match eval(
7284 "var result = 0; for (var x of [1, 2, 3, 4, 5]) { if (x === 3) continue; result = result + x; } result",
7285 )
7286 .unwrap()
7287 {
7288 Value::Number(n) => assert_eq!(n, 12.0),
7289 v => panic!("expected 12, got {v:?}"),
7290 }
7291 }
7292
7293 // ── Generator tests ────────────────────────────────────
7294
7295 #[test]
7296 fn test_generator_typeof() {
7297 // First test: does gen() return an object?
7298 match eval("function* gen() { yield 1; } typeof gen()").unwrap() {
7299 Value::String(s) => assert_eq!(s, "object"),
7300 v => panic!("expected 'object', got {v:?}"),
7301 }
7302 }
7303
7304 #[test]
7305 fn test_generator_has_next() {
7306 // Test: does the generator have a next method?
7307 match eval("function* gen() { yield 1; } var g = gen(); typeof g.next").unwrap() {
7308 Value::String(s) => assert_eq!(s, "function"),
7309 v => panic!("expected 'function', got {v:?}"),
7310 }
7311 }
7312
7313 #[test]
7314 fn test_basic_generator() {
7315 match eval(
7316 "function* gen() { yield 1; yield 2; yield 3; }
7317 var g = gen();
7318 var a = g.next();
7319 a.value",
7320 )
7321 .unwrap()
7322 {
7323 Value::Number(n) => assert_eq!(n, 1.0),
7324 v => panic!("expected 1, got {v:?}"),
7325 }
7326 }
7327
7328 #[test]
7329 fn test_generator_multiple_yields() {
7330 match eval(
7331 "function* gen() { yield 10; yield 20; yield 30; }
7332 var g = gen();
7333 var r1 = g.next(); var r2 = g.next(); var r3 = g.next(); var r4 = g.next();
7334 '' + r1.value + ',' + r2.value + ',' + r3.value + ',' + r4.done",
7335 )
7336 .unwrap()
7337 {
7338 Value::String(s) => assert_eq!(s, "10,20,30,true"),
7339 v => panic!("expected '10,20,30,true', got {v:?}"),
7340 }
7341 }
7342
7343 #[test]
7344 fn test_generator_send_value() {
7345 match eval(
7346 "function* gen() { var x = yield 'hello'; yield x + ' world'; }
7347 var g = gen();
7348 g.next();
7349 var r = g.next('beautiful');
7350 r.value",
7351 )
7352 .unwrap()
7353 {
7354 Value::String(s) => assert_eq!(s, "beautiful world"),
7355 v => panic!("expected 'beautiful world', got {v:?}"),
7356 }
7357 }
7358
7359 #[test]
7360 fn test_generator_return() {
7361 match eval(
7362 "function* gen() { yield 1; yield 2; yield 3; }
7363 var g = gen();
7364 g.next();
7365 var r = g['return'](42);
7366 '' + r.value + ',' + r.done",
7367 )
7368 .unwrap()
7369 {
7370 Value::String(s) => assert_eq!(s, "42,true"),
7371 v => panic!("expected '42,true', got {v:?}"),
7372 }
7373 }
7374
7375 #[test]
7376 fn test_generator_in_for_of() {
7377 match eval(
7378 "function* range(start, end) {
7379 for (var i = start; i < end; i = i + 1) { yield i; }
7380 }
7381 var result = 0;
7382 for (var n of range(1, 5)) { result = result + n; }
7383 result",
7384 )
7385 .unwrap()
7386 {
7387 Value::Number(n) => assert_eq!(n, 10.0),
7388 v => panic!("expected 10, got {v:?}"),
7389 }
7390 }
7391
7392 #[test]
7393 fn test_generator_with_return_value() {
7394 match eval(
7395 "function* gen() { yield 1; return 'done'; }
7396 var g = gen();
7397 var a = g.next();
7398 var b = g.next();
7399 '' + a.value + ',' + a.done + ',' + b.value + ',' + b.done",
7400 )
7401 .unwrap()
7402 {
7403 Value::String(s) => assert_eq!(s, "1,false,done,true"),
7404 v => panic!("expected '1,false,done,true', got {v:?}"),
7405 }
7406 }
7407
7408 // ── Destructuring tests ────────────────────────────────
7409
7410 #[test]
7411 fn test_array_destructuring_basic() {
7412 match eval("var [a, b, c] = [1, 2, 3]; a + b + c").unwrap() {
7413 Value::Number(n) => assert_eq!(n, 6.0),
7414 v => panic!("expected 6, got {v:?}"),
7415 }
7416 }
7417
7418 #[test]
7419 fn test_array_destructuring_rest() {
7420 match eval("var [first, ...rest] = [1, 2, 3, 4]; '' + first + ',' + rest.length").unwrap() {
7421 Value::String(s) => assert_eq!(s, "1,3"),
7422 v => panic!("expected '1,3', got {v:?}"),
7423 }
7424 }
7425
7426 #[test]
7427 fn test_array_destructuring_default() {
7428 match eval("var [a = 10, b = 20] = [1]; '' + a + ',' + b").unwrap() {
7429 Value::String(s) => assert_eq!(s, "1,20"),
7430 v => panic!("expected '1,20', got {v:?}"),
7431 }
7432 }
7433
7434 #[test]
7435 fn test_object_destructuring_basic() {
7436 match eval("var {x, y} = {x: 1, y: 2}; x + y").unwrap() {
7437 Value::Number(n) => assert_eq!(n, 3.0),
7438 v => panic!("expected 3, got {v:?}"),
7439 }
7440 }
7441
7442 #[test]
7443 fn test_object_destructuring_alias() {
7444 match eval("var {x: a, y: b} = {x: 10, y: 20}; a + b").unwrap() {
7445 Value::Number(n) => assert_eq!(n, 30.0),
7446 v => panic!("expected 30, got {v:?}"),
7447 }
7448 }
7449
7450 #[test]
7451 fn test_nested_destructuring() {
7452 match eval("var {a: {b}} = {a: {b: 42}}; b").unwrap() {
7453 Value::Number(n) => assert_eq!(n, 42.0),
7454 v => panic!("expected 42, got {v:?}"),
7455 }
7456 }
7457
7458 #[test]
7459 fn test_destructuring_in_for_of() {
7460 match eval(
7461 "var result = 0; var pairs = [[1, 2], [3, 4], [5, 6]]; for (var [a, b] of pairs) { result = result + a + b; } result",
7462 )
7463 .unwrap()
7464 {
7465 Value::Number(n) => assert_eq!(n, 21.0),
7466 v => panic!("expected 21, got {v:?}"),
7467 }
7468 }
7469
7470 // ── Spread tests ───────────────────────────────────────
7471
7472 #[test]
7473 fn test_spread_in_array() {
7474 match eval("var a = [1, 2, 3]; var b = [0, ...a, 4]; b.length").unwrap() {
7475 Value::Number(n) => assert_eq!(n, 5.0),
7476 v => panic!("expected 5, got {v:?}"),
7477 }
7478 }
7479
7480 #[test]
7481 fn test_spread_in_array_values() {
7482 match eval(
7483 "var a = [1, 2, 3]; var b = [0, ...a, 4]; '' + b[0] + ',' + b[1] + ',' + b[2] + ',' + b[3] + ',' + b[4]",
7484 )
7485 .unwrap()
7486 {
7487 Value::String(s) => assert_eq!(s, "0,1,2,3,4"),
7488 v => panic!("expected '0,1,2,3,4', got {v:?}"),
7489 }
7490 }
7491
7492 // ── Custom iterable tests ──────────────────────────────
7493
7494 #[test]
7495 fn test_custom_iterable() {
7496 match eval(
7497 "var obj = {};
7498 obj['@@iterator'] = function() {
7499 var i = 0;
7500 return {
7501 next: function() {
7502 i = i + 1;
7503 if (i <= 3) return {value: i, done: false};
7504 return {value: undefined, done: true};
7505 }
7506 };
7507 };
7508 var result = 0;
7509 for (var v of obj) { result = result + v; }
7510 result",
7511 )
7512 .unwrap()
7513 {
7514 Value::Number(n) => assert_eq!(n, 6.0),
7515 v => panic!("expected 6, got {v:?}"),
7516 }
7517 }
7518
7519 // ── Array.from with iterables ──────────────────────────
7520
7521 #[test]
7522 fn test_array_keys_values_entries() {
7523 match eval(
7524 "var arr = [10, 20, 30]; var r = ''; for (var v of arr.values()) { r = r + v + ','; } r",
7525 )
7526 .unwrap()
7527 {
7528 Value::String(s) => assert_eq!(s, "10,20,30,"),
7529 v => panic!("expected '10,20,30,', got {v:?}"),
7530 }
7531 }
7532
7533 // ── Async/await tests ────────────────────────────────────
7534
7535 #[test]
7536 fn test_async_function_returns_promise() {
7537 match eval("async function f() { return 42; } typeof f()").unwrap() {
7538 Value::String(s) => assert_eq!(s, "object"),
7539 v => panic!("expected 'object', got {v:?}"),
7540 }
7541 }
7542
7543 #[test]
7544 fn test_async_function_resolves_value() {
7545 match eval_global(
7546 "async function f() { return 42; } f().then(function(v) { result = v; });",
7547 "result",
7548 )
7549 .unwrap()
7550 {
7551 Value::Number(n) => assert_eq!(n, 42.0),
7552 v => panic!("expected 42, got {v:?}"),
7553 }
7554 }
7555
7556 #[test]
7557 fn test_await_resolved_promise() {
7558 match eval_global(
7559 "async function f() { var x = await Promise.resolve(10); return x + 5; }
7560 f().then(function(v) { result = v; });",
7561 "result",
7562 )
7563 .unwrap()
7564 {
7565 Value::Number(n) => assert_eq!(n, 15.0),
7566 v => panic!("expected 15, got {v:?}"),
7567 }
7568 }
7569
7570 #[test]
7571 fn test_await_non_promise_value() {
7572 match eval_global(
7573 "async function f() { var x = await 7; return x * 3; }
7574 f().then(function(v) { result = v; });",
7575 "result",
7576 )
7577 .unwrap()
7578 {
7579 Value::Number(n) => assert_eq!(n, 21.0),
7580 v => panic!("expected 21, got {v:?}"),
7581 }
7582 }
7583
7584 #[test]
7585 fn test_multiple_awaits_in_sequence() {
7586 match eval_global(
7587 "async function f() {
7588 var a = await Promise.resolve(1);
7589 var b = await Promise.resolve(2);
7590 var c = await Promise.resolve(3);
7591 return a + b + c;
7592 }
7593 f().then(function(v) { result = v; });",
7594 "result",
7595 )
7596 .unwrap()
7597 {
7598 Value::Number(n) => assert_eq!(n, 6.0),
7599 v => panic!("expected 6, got {v:?}"),
7600 }
7601 }
7602
7603 #[test]
7604 fn test_await_rejected_promise_throws() {
7605 match eval_global(
7606 "async function f() {
7607 try { await Promise.reject('oops'); } catch(e) { return 'caught: ' + e; }
7608 }
7609 f().then(function(v) { result = v; });",
7610 "result",
7611 )
7612 .unwrap()
7613 {
7614 Value::String(s) => assert_eq!(s, "caught: oops"),
7615 v => panic!("expected 'caught: oops', got {v:?}"),
7616 }
7617 }
7618
7619 #[test]
7620 fn test_async_function_throw_rejects() {
7621 match eval_global(
7622 "async function f() { throw 'error!'; }
7623 f()['catch'](function(e) { result = 'got: ' + e; });",
7624 "result",
7625 )
7626 .unwrap()
7627 {
7628 Value::String(s) => assert_eq!(s, "got: error!"),
7629 v => panic!("expected 'got: error!', got {v:?}"),
7630 }
7631 }
7632
7633 #[test]
7634 fn test_async_function_expression() {
7635 match eval_global(
7636 "var f = async function() { var x = await Promise.resolve(99); return x; };
7637 f().then(function(v) { result = v; });",
7638 "result",
7639 )
7640 .unwrap()
7641 {
7642 Value::Number(n) => assert_eq!(n, 99.0),
7643 v => panic!("expected 99, got {v:?}"),
7644 }
7645 }
7646
7647 #[test]
7648 fn test_async_function_expression_with_args() {
7649 match eval_global(
7650 "var f = async function(x) { return x * 2; };
7651 f(21).then(function(v) { result = v; });",
7652 "result",
7653 )
7654 .unwrap()
7655 {
7656 Value::Number(n) => assert_eq!(n, 42.0),
7657 v => panic!("expected 42, got {v:?}"),
7658 }
7659 }
7660
7661 #[test]
7662 fn test_async_arrow_function() {
7663 match eval_global(
7664 "var f = async x => { var y = await Promise.resolve(x); return y + 1; };
7665 f(10).then(function(v) { result = v; });",
7666 "result",
7667 )
7668 .unwrap()
7669 {
7670 Value::Number(n) => assert_eq!(n, 11.0),
7671 v => panic!("expected 11, got {v:?}"),
7672 }
7673 }
7674
7675 #[test]
7676 fn test_async_arrow_concise_body() {
7677 match eval_global(
7678 "var f = async x => x * 3;
7679 f(7).then(function(v) { result = v; });",
7680 "result",
7681 )
7682 .unwrap()
7683 {
7684 Value::Number(n) => assert_eq!(n, 21.0),
7685 v => panic!("expected 21, got {v:?}"),
7686 }
7687 }
7688
7689 #[test]
7690 fn test_await_chained_promises() {
7691 match eval_global(
7692 "async function f() {
7693 var p = Promise.resolve(5).then(function(v) { return v * 10; });
7694 return await p;
7695 }
7696 f().then(function(v) { result = v; });",
7697 "result",
7698 )
7699 .unwrap()
7700 {
7701 Value::Number(n) => assert_eq!(n, 50.0),
7702 v => panic!("expected 50, got {v:?}"),
7703 }
7704 }
7705
7706 #[test]
7707 fn test_async_function_with_closure() {
7708 match eval_global(
7709 "function make() {
7710 var x = 100;
7711 return async function() { var y = await Promise.resolve(23); return x + y; };
7712 }
7713 make()().then(function(v) { result = v; });",
7714 "result",
7715 )
7716 .unwrap()
7717 {
7718 Value::Number(n) => assert_eq!(n, 123.0),
7719 v => panic!("expected 123, got {v:?}"),
7720 }
7721 }
7722
7723 #[test]
7724 fn test_async_method_in_object() {
7725 match eval_global(
7726 "var obj = { async getValue() { return await Promise.resolve(42); } };
7727 obj.getValue().then(function(v) { result = v; });",
7728 "result",
7729 )
7730 .unwrap()
7731 {
7732 Value::Number(n) => assert_eq!(n, 42.0),
7733 v => panic!("expected 42, got {v:?}"),
7734 }
7735 }
7736
7737 #[test]
7738 fn test_async_generator_basic() {
7739 match eval_global(
7740 "async function* gen() { yield 1; yield 2; yield 3; }
7741 var it = gen();
7742 it.next().then(function(r) { result = r.value; });",
7743 "result",
7744 )
7745 .unwrap()
7746 {
7747 Value::Number(n) => assert_eq!(n, 1.0),
7748 v => panic!("expected 1, got {v:?}"),
7749 }
7750 }
7751
7752 #[test]
7753 fn test_for_await_of_parsing() {
7754 let prog =
7755 crate::parser::Parser::parse("async function f() { for await (let x of iter) { } }");
7756 assert!(prog.is_ok());
7757 }
7758
7759 // ── Console API tests ─────────────────────────────────────
7760
7761 use std::cell::RefCell;
7762 use std::rc::Rc;
7763
7764 /// A console output sink that captures messages for testing.
7765 struct CapturedConsole {
7766 log_messages: RefCell<Vec<String>>,
7767 error_messages: RefCell<Vec<String>>,
7768 warn_messages: RefCell<Vec<String>>,
7769 }
7770
7771 impl CapturedConsole {
7772 fn new() -> Self {
7773 Self {
7774 log_messages: RefCell::new(Vec::new()),
7775 error_messages: RefCell::new(Vec::new()),
7776 warn_messages: RefCell::new(Vec::new()),
7777 }
7778 }
7779 }
7780
7781 impl ConsoleOutput for CapturedConsole {
7782 fn log(&self, message: &str) {
7783 self.log_messages.borrow_mut().push(message.to_string());
7784 }
7785 fn error(&self, message: &str) {
7786 self.error_messages.borrow_mut().push(message.to_string());
7787 }
7788 fn warn(&self, message: &str) {
7789 self.warn_messages.borrow_mut().push(message.to_string());
7790 }
7791 }
7792
7793 /// Helper: compile and execute JS, capturing console output.
7794 fn eval_with_console(source: &str) -> (Result<Value, RuntimeError>, Rc<CapturedConsole>) {
7795 let console = Rc::new(CapturedConsole::new());
7796 let program = Parser::parse(source).expect("parse failed");
7797 let func = compiler::compile(&program).expect("compile failed");
7798 let mut vm = Vm::new();
7799 vm.set_console_output(Box::new(RcConsole(console.clone())));
7800 let result = vm.execute(&func);
7801 (result, console)
7802 }
7803
7804 /// Wrapper to use Rc<CapturedConsole> as Box<dyn ConsoleOutput>.
7805 struct RcConsole(Rc<CapturedConsole>);
7806
7807 impl ConsoleOutput for RcConsole {
7808 fn log(&self, message: &str) {
7809 self.0.log(message);
7810 }
7811 fn error(&self, message: &str) {
7812 self.0.error(message);
7813 }
7814 fn warn(&self, message: &str) {
7815 self.0.warn(message);
7816 }
7817 }
7818
7819 #[test]
7820 fn test_console_log_string() {
7821 let (result, console) = eval_with_console("console.log('hello')");
7822 assert!(result.is_ok());
7823 assert!(matches!(result.unwrap(), Value::Undefined));
7824 let logs = console.log_messages.borrow();
7825 assert_eq!(logs.len(), 1);
7826 assert_eq!(logs[0], "hello");
7827 }
7828
7829 #[test]
7830 fn test_console_log_multiple_args() {
7831 let (_, console) = eval_with_console("console.log('a', 1, true)");
7832 let logs = console.log_messages.borrow();
7833 assert_eq!(logs[0], "a 1 true");
7834 }
7835
7836 #[test]
7837 fn test_console_error_to_error_channel() {
7838 let (_, console) = eval_with_console("console.error('oops')");
7839 assert!(console.log_messages.borrow().is_empty());
7840 let errors = console.error_messages.borrow();
7841 assert_eq!(errors.len(), 1);
7842 assert_eq!(errors[0], "oops");
7843 }
7844
7845 #[test]
7846 fn test_console_warn_to_warn_channel() {
7847 let (_, console) = eval_with_console("console.warn('warning')");
7848 assert!(console.log_messages.borrow().is_empty());
7849 let warns = console.warn_messages.borrow();
7850 assert_eq!(warns.len(), 1);
7851 assert_eq!(warns[0], "warning");
7852 }
7853
7854 #[test]
7855 fn test_console_info_aliases_log() {
7856 let (_, console) = eval_with_console("console.info('info msg')");
7857 let logs = console.log_messages.borrow();
7858 assert_eq!(logs.len(), 1);
7859 assert_eq!(logs[0], "info msg");
7860 }
7861
7862 #[test]
7863 fn test_console_debug_aliases_log() {
7864 let (_, console) = eval_with_console("console.debug('debug msg')");
7865 let logs = console.log_messages.borrow();
7866 assert_eq!(logs.len(), 1);
7867 assert_eq!(logs[0], "debug msg");
7868 }
7869
7870 #[test]
7871 fn test_console_log_returns_undefined() {
7872 let result = eval("console.log('test')").unwrap();
7873 assert!(matches!(result, Value::Undefined));
7874 }
7875
7876 #[test]
7877 fn test_console_log_no_args() {
7878 let (_, console) = eval_with_console("console.log()");
7879 let logs = console.log_messages.borrow();
7880 assert_eq!(logs.len(), 1);
7881 assert_eq!(logs[0], "");
7882 }
7883
7884 #[test]
7885 fn test_console_log_primitives() {
7886 let (_, console) = eval_with_console(
7887 "console.log(undefined); console.log(null); console.log(42); console.log(true);",
7888 );
7889 let logs = console.log_messages.borrow();
7890 assert_eq!(logs.len(), 4);
7891 assert_eq!(logs[0], "undefined");
7892 assert_eq!(logs[1], "null");
7893 assert_eq!(logs[2], "42");
7894 assert_eq!(logs[3], "true");
7895 }
7896
7897 #[test]
7898 fn test_console_log_array() {
7899 let (_, console) = eval_with_console("console.log([1, 2, 3])");
7900 let logs = console.log_messages.borrow();
7901 assert_eq!(logs[0], "[ 1, 2, 3 ]");
7902 }
7903
7904 #[test]
7905 fn test_console_log_object() {
7906 let (_, console) = eval_with_console("console.log({x: 1})");
7907 let logs = console.log_messages.borrow();
7908 assert_eq!(logs[0], "{ x: 1 }");
7909 }
7910
7911 #[test]
7912 fn test_console_typeof() {
7913 match eval("typeof console.log").unwrap() {
7914 Value::String(s) => assert_eq!(s, "function"),
7915 v => panic!("expected 'function', got {v:?}"),
7916 }
7917 }
7918
7919 #[test]
7920 fn test_console_is_object() {
7921 match eval("typeof console").unwrap() {
7922 Value::String(s) => assert_eq!(s, "object"),
7923 v => panic!("expected 'object', got {v:?}"),
7924 }
7925 }
7926
7927 #[test]
7928 fn test_console_never_throws() {
7929 let result = eval("console.log({x: 1}); console.log([1,2]); console.log(undefined); 'ok'");
7930 assert!(result.is_ok());
7931 match result.unwrap() {
7932 Value::String(s) => assert_eq!(s, "ok"),
7933 v => panic!("expected 'ok', got {v:?}"),
7934 }
7935 }
7936}