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