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