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: either a plain object or a function.
16pub enum HeapObject {
17 Object(ObjectData),
18 Function(FunctionData),
19}
20
21impl Traceable for HeapObject {
22 fn trace(&self, visitor: &mut dyn FnMut(GcRef)) {
23 match self {
24 HeapObject::Object(data) => {
25 for prop in data.properties.values() {
26 if let Some(r) = prop.value.gc_ref() {
27 visitor(r);
28 }
29 }
30 if let Some(proto) = data.prototype {
31 visitor(proto);
32 }
33 }
34 HeapObject::Function(fdata) => {
35 if let Some(proto) = fdata.prototype_obj {
36 visitor(proto);
37 }
38 }
39 }
40 }
41}
42
43/// A property descriptor stored in an object's property map.
44#[derive(Clone)]
45pub struct Property {
46 /// The property's value (for data properties).
47 pub value: Value,
48 /// Whether the value can be changed via assignment.
49 pub writable: bool,
50 /// Whether the property shows up in `for...in` and `Object.keys`.
51 pub enumerable: bool,
52 /// Whether the property can be deleted or its attributes changed.
53 pub configurable: bool,
54}
55
56impl Property {
57 /// Create a new data property with all flags set to true (the JS default for
58 /// properties created by assignment).
59 pub fn data(value: Value) -> Self {
60 Self {
61 value,
62 writable: true,
63 enumerable: true,
64 configurable: true,
65 }
66 }
67
68 /// Create a non-enumerable, non-configurable property (e.g. built-in methods).
69 pub fn builtin(value: Value) -> Self {
70 Self {
71 value,
72 writable: true,
73 enumerable: false,
74 configurable: false,
75 }
76 }
77}
78
79/// A JS plain object (properties stored as a HashMap with descriptors).
80pub struct ObjectData {
81 pub properties: HashMap<String, Property>,
82 pub prototype: Option<GcRef>,
83 /// Whether new properties can be added (Object.preventExtensions).
84 pub extensible: bool,
85}
86
87impl ObjectData {
88 pub fn new() -> Self {
89 Self {
90 properties: HashMap::new(),
91 prototype: None,
92 extensible: true,
93 }
94 }
95}
96
97impl Default for ObjectData {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103/// A runtime function value: either bytecode or native.
104pub struct FunctionData {
105 pub name: String,
106 pub kind: FunctionKind,
107 /// The `.prototype` property object (for use as a constructor with `instanceof`).
108 pub prototype_obj: Option<GcRef>,
109}
110
111#[derive(Clone)]
112pub enum FunctionKind {
113 /// Bytecode function.
114 Bytecode(BytecodeFunc),
115 /// Native (Rust) function.
116 Native(NativeFunc),
117}
118
119#[derive(Clone)]
120pub struct BytecodeFunc {
121 pub func: Function,
122}
123
124/// A native function callable from JS.
125#[derive(Clone)]
126pub struct NativeFunc {
127 pub callback: fn(&[Value]) -> Result<Value, RuntimeError>,
128}
129
130// ── JS Value ──────────────────────────────────────────────────
131
132/// A JavaScript runtime value.
133///
134/// Primitive types (Undefined, Null, Boolean, Number, String) are stored
135/// inline. Objects and Functions are heap-allocated via the GC and referenced
136/// by a [`GcRef`] handle.
137#[derive(Clone)]
138pub enum Value {
139 Undefined,
140 Null,
141 Boolean(bool),
142 Number(f64),
143 String(String),
144 /// A GC-managed plain object.
145 Object(GcRef),
146 /// A GC-managed function.
147 Function(GcRef),
148}
149
150impl fmt::Debug for Value {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 match self {
153 Value::Undefined => write!(f, "undefined"),
154 Value::Null => write!(f, "null"),
155 Value::Boolean(b) => write!(f, "{b}"),
156 Value::Number(n) => write!(f, "{n}"),
157 Value::String(s) => write!(f, "\"{}\"", s),
158 Value::Object(_) => write!(f, "[object Object]"),
159 Value::Function(_) => write!(f, "[Function]"),
160 }
161 }
162}
163
164impl fmt::Display for Value {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 match self {
167 Value::Undefined => write!(f, "undefined"),
168 Value::Null => write!(f, "null"),
169 Value::Boolean(b) => write!(f, "{b}"),
170 Value::Number(n) => format_number(*n, f),
171 Value::String(s) => write!(f, "{s}"),
172 Value::Object(_) => write!(f, "[object Object]"),
173 Value::Function(_) => write!(f, "function() {{ [native code] }}"),
174 }
175 }
176}
177
178/// Format a number following JS conventions (no trailing .0 for integers).
179fn format_number(n: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 if n.is_nan() {
181 write!(f, "NaN")
182 } else if n.is_infinite() {
183 if n.is_sign_positive() {
184 write!(f, "Infinity")
185 } else {
186 write!(f, "-Infinity")
187 }
188 } else if n == 0.0 {
189 write!(f, "0")
190 } else if n.fract() == 0.0 && n.abs() < 1e20 {
191 write!(f, "{}", n as i64)
192 } else {
193 write!(f, "{n}")
194 }
195}
196
197impl Value {
198 /// Abstract `ToBoolean` (ECMA-262 §7.1.2).
199 pub fn to_boolean(&self) -> bool {
200 match self {
201 Value::Undefined | Value::Null => false,
202 Value::Boolean(b) => *b,
203 Value::Number(n) => *n != 0.0 && !n.is_nan(),
204 Value::String(s) => !s.is_empty(),
205 Value::Object(_) | Value::Function(_) => true,
206 }
207 }
208
209 /// Abstract `ToNumber` (ECMA-262 §7.1.3).
210 pub fn to_number(&self) -> f64 {
211 match self {
212 Value::Undefined => f64::NAN,
213 Value::Null => 0.0,
214 Value::Boolean(true) => 1.0,
215 Value::Boolean(false) => 0.0,
216 Value::Number(n) => *n,
217 Value::String(s) => {
218 let s = s.trim();
219 if s.is_empty() {
220 0.0
221 } else if s == "Infinity" || s == "+Infinity" {
222 f64::INFINITY
223 } else if s == "-Infinity" {
224 f64::NEG_INFINITY
225 } else {
226 s.parse::<f64>().unwrap_or(f64::NAN)
227 }
228 }
229 Value::Object(_) | Value::Function(_) => f64::NAN,
230 }
231 }
232
233 /// Abstract `ToString` (ECMA-262 §7.1.12).
234 ///
235 /// Requires `&Gc` to look up function names for `Value::Function`.
236 pub fn to_js_string(&self, gc: &Gc<HeapObject>) -> String {
237 match self {
238 Value::Undefined => "undefined".to_string(),
239 Value::Null => "null".to_string(),
240 Value::Boolean(true) => "true".to_string(),
241 Value::Boolean(false) => "false".to_string(),
242 Value::Number(n) => js_number_to_string(*n),
243 Value::String(s) => s.clone(),
244 Value::Object(_) => "[object Object]".to_string(),
245 Value::Function(gc_ref) => gc
246 .get(*gc_ref)
247 .and_then(|obj| match obj {
248 HeapObject::Function(f) => {
249 Some(format!("function {}() {{ [native code] }}", f.name))
250 }
251 _ => None,
252 })
253 .unwrap_or_else(|| "function() { [native code] }".to_string()),
254 }
255 }
256
257 /// `typeof` operator result.
258 pub fn type_of(&self) -> &'static str {
259 match self {
260 Value::Undefined => "undefined",
261 Value::Null => "object", // yes, this is the spec
262 Value::Boolean(_) => "boolean",
263 Value::Number(_) => "number",
264 Value::String(_) => "string",
265 Value::Object(_) => "object",
266 Value::Function(_) => "function",
267 }
268 }
269
270 /// Is this value nullish (null or undefined)?
271 pub fn is_nullish(&self) -> bool {
272 matches!(self, Value::Undefined | Value::Null)
273 }
274
275 /// Extract the `GcRef` if this value is an Object or Function.
276 pub fn gc_ref(&self) -> Option<GcRef> {
277 match self {
278 Value::Object(r) | Value::Function(r) => Some(*r),
279 _ => None,
280 }
281 }
282}
283
284/// Format a number as JS would.
285fn js_number_to_string(n: f64) -> String {
286 if n.is_nan() {
287 "NaN".to_string()
288 } else if n.is_infinite() {
289 if n.is_sign_positive() {
290 "Infinity".to_string()
291 } else {
292 "-Infinity".to_string()
293 }
294 } else if n == 0.0 {
295 "0".to_string()
296 } else if n.fract() == 0.0 && n.abs() < 1e20 {
297 format!("{}", n as i64)
298 } else {
299 format!("{n}")
300 }
301}
302
303// ── Runtime errors ────────────────────────────────────────────
304
305/// JavaScript runtime error types.
306#[derive(Debug, Clone)]
307pub struct RuntimeError {
308 pub kind: ErrorKind,
309 pub message: String,
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq)]
313pub enum ErrorKind {
314 TypeError,
315 ReferenceError,
316 RangeError,
317 SyntaxError,
318 Error,
319}
320
321impl fmt::Display for RuntimeError {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 let name = match self.kind {
324 ErrorKind::TypeError => "TypeError",
325 ErrorKind::ReferenceError => "ReferenceError",
326 ErrorKind::RangeError => "RangeError",
327 ErrorKind::SyntaxError => "SyntaxError",
328 ErrorKind::Error => "Error",
329 };
330 write!(f, "{name}: {}", self.message)
331 }
332}
333
334impl RuntimeError {
335 pub fn type_error(msg: impl Into<String>) -> Self {
336 Self {
337 kind: ErrorKind::TypeError,
338 message: msg.into(),
339 }
340 }
341
342 pub fn reference_error(msg: impl Into<String>) -> Self {
343 Self {
344 kind: ErrorKind::ReferenceError,
345 message: msg.into(),
346 }
347 }
348
349 pub fn range_error(msg: impl Into<String>) -> Self {
350 Self {
351 kind: ErrorKind::RangeError,
352 message: msg.into(),
353 }
354 }
355
356 /// Convert to a JS Value (an error object). Allocates through the GC.
357 pub fn to_value(&self, gc: &mut Gc<HeapObject>) -> Value {
358 let mut obj = ObjectData::new();
359 obj.properties.insert(
360 "message".to_string(),
361 Property::data(Value::String(self.message.clone())),
362 );
363 let name = match self.kind {
364 ErrorKind::TypeError => "TypeError",
365 ErrorKind::ReferenceError => "ReferenceError",
366 ErrorKind::RangeError => "RangeError",
367 ErrorKind::SyntaxError => "SyntaxError",
368 ErrorKind::Error => "Error",
369 };
370 obj.properties.insert(
371 "name".to_string(),
372 Property::data(Value::String(name.to_string())),
373 );
374 Value::Object(gc.alloc(HeapObject::Object(obj)))
375 }
376}
377
378// ── Property access helpers ──────────────────────────────────
379
380/// Get a property from an object, walking the prototype chain.
381fn gc_get_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str) -> Value {
382 let proto = {
383 match gc.get(obj_ref) {
384 Some(HeapObject::Object(data)) => {
385 if let Some(prop) = data.properties.get(key) {
386 return prop.value.clone();
387 }
388 data.prototype
389 }
390 Some(HeapObject::Function(fdata)) => {
391 // Functions have a `.prototype` property.
392 if key == "prototype" {
393 if let Some(proto_ref) = fdata.prototype_obj {
394 return Value::Object(proto_ref);
395 }
396 return Value::Undefined;
397 }
398 None
399 }
400 _ => return Value::Undefined,
401 }
402 };
403 if let Some(proto_ref) = proto {
404 gc_get_property(gc, proto_ref, key)
405 } else {
406 Value::Undefined
407 }
408}
409
410/// Check if an object has a property (own or inherited).
411fn gc_has_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str) -> bool {
412 let proto = {
413 match gc.get(obj_ref) {
414 Some(HeapObject::Object(data)) => {
415 if data.properties.contains_key(key) {
416 return true;
417 }
418 data.prototype
419 }
420 _ => return false,
421 }
422 };
423 if let Some(proto_ref) = proto {
424 gc_has_property(gc, proto_ref, key)
425 } else {
426 false
427 }
428}
429
430/// Collect all enumerable string keys of an object (own + inherited), in proper order.
431/// Integer indices first (sorted numerically), then string keys in insertion order.
432fn gc_enumerate_keys(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> {
433 let mut seen = std::collections::HashSet::new();
434 let mut integer_keys: Vec<(u32, String)> = Vec::new();
435 let mut string_keys: Vec<String> = Vec::new();
436 let mut current = Some(obj_ref);
437
438 while let Some(cur_ref) = current {
439 match gc.get(cur_ref) {
440 Some(HeapObject::Object(data)) => {
441 for (key, prop) in &data.properties {
442 if prop.enumerable && seen.insert(key.clone()) {
443 if let Ok(idx) = key.parse::<u32>() {
444 integer_keys.push((idx, key.clone()));
445 } else {
446 string_keys.push(key.clone());
447 }
448 }
449 }
450 current = data.prototype;
451 }
452 _ => break,
453 }
454 }
455
456 // Integer indices sorted numerically first, then string keys in collected order.
457 integer_keys.sort_by_key(|(idx, _)| *idx);
458 let mut result: Vec<String> = integer_keys.into_iter().map(|(_, k)| k).collect();
459 result.extend(string_keys);
460 result
461}
462
463/// Check if `obj_ref` is an instance of the constructor at `ctor_ref`.
464/// Walks the prototype chain of `obj_ref` looking for `ctor.prototype`.
465fn gc_instanceof(gc: &Gc<HeapObject>, obj_ref: GcRef, ctor_ref: GcRef) -> bool {
466 // Get the constructor's .prototype object.
467 let ctor_proto = match gc.get(ctor_ref) {
468 Some(HeapObject::Function(fdata)) => match fdata.prototype_obj {
469 Some(p) => p,
470 None => return false,
471 },
472 _ => return false,
473 };
474
475 // Walk the prototype chain of obj_ref.
476 let mut current = match gc.get(obj_ref) {
477 Some(HeapObject::Object(data)) => data.prototype,
478 _ => None,
479 };
480
481 while let Some(proto_ref) = current {
482 if proto_ref == ctor_proto {
483 return true;
484 }
485 current = match gc.get(proto_ref) {
486 Some(HeapObject::Object(data)) => data.prototype,
487 _ => None,
488 };
489 }
490 false
491}
492
493/// Get a string property (length, index access).
494fn string_get_property(s: &str, key: &str) -> Value {
495 if key == "length" {
496 Value::Number(s.len() as f64)
497 } else if let Ok(idx) = key.parse::<usize>() {
498 s.chars()
499 .nth(idx)
500 .map(|c| Value::String(c.to_string()))
501 .unwrap_or(Value::Undefined)
502 } else {
503 Value::Undefined
504 }
505}
506
507// ── Type conversion helpers ──────────────────────────────────
508
509/// ToInt32 (ECMA-262 §7.1.5).
510fn to_int32(val: &Value) -> i32 {
511 let n = val.to_number();
512 if n.is_nan() || n.is_infinite() || n == 0.0 {
513 return 0;
514 }
515 let i = n.trunc() as i64;
516 (i & 0xFFFF_FFFF) as i32
517}
518
519/// ToUint32 (ECMA-262 §7.1.6).
520fn to_uint32(val: &Value) -> u32 {
521 let n = val.to_number();
522 if n.is_nan() || n.is_infinite() || n == 0.0 {
523 return 0;
524 }
525 let i = n.trunc() as i64;
526 (i & 0xFFFF_FFFF) as u32
527}
528
529// ── Equality ─────────────────────────────────────────────────
530
531/// Abstract equality comparison (==) per ECMA-262 §7.2.14.
532fn abstract_eq(x: &Value, y: &Value) -> bool {
533 match (x, y) {
534 (Value::Undefined, Value::Undefined) => true,
535 (Value::Null, Value::Null) => true,
536 (Value::Undefined, Value::Null) | (Value::Null, Value::Undefined) => true,
537 (Value::Number(a), Value::Number(b)) => a == b,
538 (Value::String(a), Value::String(b)) => a == b,
539 (Value::Boolean(a), Value::Boolean(b)) => a == b,
540 // Number / String → convert String to Number.
541 (Value::Number(_), Value::String(_)) => abstract_eq(x, &Value::Number(y.to_number())),
542 (Value::String(_), Value::Number(_)) => abstract_eq(&Value::Number(x.to_number()), y),
543 // Boolean → Number.
544 (Value::Boolean(_), _) => abstract_eq(&Value::Number(x.to_number()), y),
545 (_, Value::Boolean(_)) => abstract_eq(x, &Value::Number(y.to_number())),
546 // Same GcRef → equal.
547 (Value::Object(a), Value::Object(b)) => a == b,
548 (Value::Function(a), Value::Function(b)) => a == b,
549 _ => false,
550 }
551}
552
553/// Strict equality comparison (===) per ECMA-262 §7.2.15.
554fn strict_eq(x: &Value, y: &Value) -> bool {
555 match (x, y) {
556 (Value::Undefined, Value::Undefined) => true,
557 (Value::Null, Value::Null) => true,
558 (Value::Number(a), Value::Number(b)) => a == b,
559 (Value::String(a), Value::String(b)) => a == b,
560 (Value::Boolean(a), Value::Boolean(b)) => a == b,
561 // Reference identity for heap objects.
562 (Value::Object(a), Value::Object(b)) => a == b,
563 (Value::Function(a), Value::Function(b)) => a == b,
564 _ => false,
565 }
566}
567
568// ── Relational comparison ────────────────────────────────────
569
570/// Abstract relational comparison. Returns false for NaN comparisons.
571fn abstract_relational(
572 lhs: &Value,
573 rhs: &Value,
574 predicate: fn(std::cmp::Ordering) -> bool,
575) -> bool {
576 // If both are strings, compare lexicographically.
577 if let (Value::String(a), Value::String(b)) = (lhs, rhs) {
578 return predicate(a.cmp(b));
579 }
580 // Otherwise, compare as numbers.
581 let a = lhs.to_number();
582 let b = rhs.to_number();
583 if a.is_nan() || b.is_nan() {
584 return false;
585 }
586 predicate(a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal))
587}
588
589// ── Addition ─────────────────────────────────────────────────
590
591/// The + operator: string concat if either operand is a string, else numeric add.
592fn add_values(lhs: &Value, rhs: &Value, gc: &Gc<HeapObject>) -> Value {
593 match (lhs, rhs) {
594 (Value::String(a), _) => Value::String(format!("{a}{}", rhs.to_js_string(gc))),
595 (_, Value::String(b)) => Value::String(format!("{}{b}", lhs.to_js_string(gc))),
596 _ => Value::Number(lhs.to_number() + rhs.to_number()),
597 }
598}
599
600// ── Call frame ────────────────────────────────────────────────
601
602/// A single call frame on the VM's call stack.
603struct CallFrame {
604 /// The function being executed.
605 func: Function,
606 /// Instruction pointer (byte offset into func.code).
607 ip: usize,
608 /// Base register index in the VM's register file.
609 base: usize,
610 /// Register to write the return value into (absolute index in register file).
611 return_reg: usize,
612 /// Exception handler stack for this frame.
613 exception_handlers: Vec<ExceptionHandler>,
614}
615
616/// An exception handler entry (for try/catch).
617struct ExceptionHandler {
618 /// IP to jump to on exception (the catch block start).
619 catch_ip: usize,
620 /// Register to store the caught exception value.
621 catch_reg: Reg,
622}
623
624// ── VM ───────────────────────────────────────────────────────
625
626/// The JavaScript virtual machine.
627pub struct Vm {
628 /// Register file (flat array shared across frames via base offsets).
629 registers: Vec<Value>,
630 /// Call stack.
631 frames: Vec<CallFrame>,
632 /// Global variables.
633 globals: HashMap<String, Value>,
634 /// Garbage collector managing heap objects.
635 pub gc: Gc<HeapObject>,
636}
637
638/// Maximum register file size.
639const MAX_REGISTERS: usize = 4096;
640/// Maximum call depth.
641const MAX_CALL_DEPTH: usize = 512;
642
643impl Vm {
644 pub fn new() -> Self {
645 Self {
646 registers: vec![Value::Undefined; 256],
647 frames: Vec::new(),
648 globals: HashMap::new(),
649 gc: Gc::new(),
650 }
651 }
652
653 /// Execute a compiled top-level function and return the completion value.
654 pub fn execute(&mut self, func: &Function) -> Result<Value, RuntimeError> {
655 let reg_count = func.register_count as usize;
656 self.ensure_registers(reg_count);
657
658 self.frames.push(CallFrame {
659 func: func.clone(),
660 ip: 0,
661 base: 0,
662 return_reg: 0,
663 exception_handlers: Vec::new(),
664 });
665
666 self.run()
667 }
668
669 /// Ensure the register file has at least `needed` slots.
670 fn ensure_registers(&mut self, needed: usize) {
671 if needed > self.registers.len() {
672 if needed > MAX_REGISTERS {
673 return;
674 }
675 self.registers.resize(needed, Value::Undefined);
676 }
677 }
678
679 /// Read a u8 from the current frame's bytecode and advance IP.
680 #[inline]
681 fn read_u8(frame: &mut CallFrame) -> u8 {
682 let b = frame.func.code[frame.ip];
683 frame.ip += 1;
684 b
685 }
686
687 /// Read a u16 (little-endian) from the current frame's bytecode and advance IP.
688 #[inline]
689 fn read_u16(frame: &mut CallFrame) -> u16 {
690 let lo = frame.func.code[frame.ip];
691 let hi = frame.func.code[frame.ip + 1];
692 frame.ip += 2;
693 u16::from_le_bytes([lo, hi])
694 }
695
696 /// Read an i32 (little-endian) from the current frame's bytecode and advance IP.
697 #[inline]
698 fn read_i32(frame: &mut CallFrame) -> i32 {
699 let bytes = [
700 frame.func.code[frame.ip],
701 frame.func.code[frame.ip + 1],
702 frame.func.code[frame.ip + 2],
703 frame.func.code[frame.ip + 3],
704 ];
705 frame.ip += 4;
706 i32::from_le_bytes(bytes)
707 }
708
709 /// Collect all GcRef values reachable from the mutator (roots for GC).
710 fn collect_roots(&self) -> Vec<GcRef> {
711 let mut roots = Vec::new();
712 for val in &self.registers {
713 if let Some(r) = val.gc_ref() {
714 roots.push(r);
715 }
716 }
717 for val in self.globals.values() {
718 if let Some(r) = val.gc_ref() {
719 roots.push(r);
720 }
721 }
722 roots
723 }
724
725 /// Main dispatch loop.
726 fn run(&mut self) -> Result<Value, RuntimeError> {
727 loop {
728 let fi = self.frames.len() - 1;
729
730 // Check if we've reached the end of bytecode.
731 if self.frames[fi].ip >= self.frames[fi].func.code.len() {
732 if self.frames.len() == 1 {
733 self.frames.pop();
734 return Ok(Value::Undefined);
735 }
736 let old = self.frames.pop().unwrap();
737 self.registers[old.return_reg] = Value::Undefined;
738 continue;
739 }
740
741 let opcode_byte = self.frames[fi].func.code[self.frames[fi].ip];
742 self.frames[fi].ip += 1;
743
744 let Some(op) = Op::from_byte(opcode_byte) else {
745 return Err(RuntimeError {
746 kind: ErrorKind::Error,
747 message: format!("unknown opcode: 0x{opcode_byte:02X}"),
748 });
749 };
750
751 match op {
752 // ── Register loads ──────────────────────────────
753 Op::LoadConst => {
754 let dst = Self::read_u8(&mut self.frames[fi]);
755 let idx = Self::read_u16(&mut self.frames[fi]) as usize;
756 let base = self.frames[fi].base;
757 let val = match &self.frames[fi].func.constants[idx] {
758 Constant::Number(n) => Value::Number(*n),
759 Constant::String(s) => Value::String(s.clone()),
760 };
761 self.registers[base + dst as usize] = val;
762 }
763 Op::LoadNull => {
764 let dst = Self::read_u8(&mut self.frames[fi]);
765 let base = self.frames[fi].base;
766 self.registers[base + dst as usize] = Value::Null;
767 }
768 Op::LoadUndefined => {
769 let dst = Self::read_u8(&mut self.frames[fi]);
770 let base = self.frames[fi].base;
771 self.registers[base + dst as usize] = Value::Undefined;
772 }
773 Op::LoadTrue => {
774 let dst = Self::read_u8(&mut self.frames[fi]);
775 let base = self.frames[fi].base;
776 self.registers[base + dst as usize] = Value::Boolean(true);
777 }
778 Op::LoadFalse => {
779 let dst = Self::read_u8(&mut self.frames[fi]);
780 let base = self.frames[fi].base;
781 self.registers[base + dst as usize] = Value::Boolean(false);
782 }
783 Op::LoadInt8 => {
784 let dst = Self::read_u8(&mut self.frames[fi]);
785 let val = Self::read_u8(&mut self.frames[fi]) as i8;
786 let base = self.frames[fi].base;
787 self.registers[base + dst as usize] = Value::Number(val as f64);
788 }
789 Op::Move => {
790 let dst = Self::read_u8(&mut self.frames[fi]);
791 let src = Self::read_u8(&mut self.frames[fi]);
792 let base = self.frames[fi].base;
793 let val = self.registers[base + src as usize].clone();
794 self.registers[base + dst as usize] = val;
795 }
796
797 // ── Global access ──────────────────────────────
798 Op::LoadGlobal => {
799 let dst = Self::read_u8(&mut self.frames[fi]);
800 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
801 let base = self.frames[fi].base;
802 let name = &self.frames[fi].func.names[name_idx];
803 let val = self.globals.get(name).cloned().unwrap_or(Value::Undefined);
804 self.registers[base + dst as usize] = val;
805 }
806 Op::StoreGlobal => {
807 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
808 let src = Self::read_u8(&mut self.frames[fi]);
809 let base = self.frames[fi].base;
810 let name = self.frames[fi].func.names[name_idx].clone();
811 let val = self.registers[base + src as usize].clone();
812 self.globals.insert(name, val);
813 }
814
815 // ── Arithmetic ─────────────────────────────────
816 Op::Add => {
817 let dst = Self::read_u8(&mut self.frames[fi]);
818 let lhs_r = Self::read_u8(&mut self.frames[fi]);
819 let rhs_r = Self::read_u8(&mut self.frames[fi]);
820 let base = self.frames[fi].base;
821 let result = add_values(
822 &self.registers[base + lhs_r as usize],
823 &self.registers[base + rhs_r as usize],
824 &self.gc,
825 );
826 self.registers[base + dst as usize] = result;
827 }
828 Op::Sub => {
829 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
830 let result = self.registers[base + lhs_r].to_number()
831 - self.registers[base + rhs_r].to_number();
832 self.registers[base + dst] = Value::Number(result);
833 }
834 Op::Mul => {
835 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
836 let result = self.registers[base + lhs_r].to_number()
837 * self.registers[base + rhs_r].to_number();
838 self.registers[base + dst] = Value::Number(result);
839 }
840 Op::Div => {
841 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
842 let result = self.registers[base + lhs_r].to_number()
843 / self.registers[base + rhs_r].to_number();
844 self.registers[base + dst] = Value::Number(result);
845 }
846 Op::Rem => {
847 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
848 let result = self.registers[base + lhs_r].to_number()
849 % self.registers[base + rhs_r].to_number();
850 self.registers[base + dst] = Value::Number(result);
851 }
852 Op::Exp => {
853 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
854 let result = self.registers[base + lhs_r]
855 .to_number()
856 .powf(self.registers[base + rhs_r].to_number());
857 self.registers[base + dst] = Value::Number(result);
858 }
859 Op::Neg => {
860 let dst = Self::read_u8(&mut self.frames[fi]);
861 let src = Self::read_u8(&mut self.frames[fi]);
862 let base = self.frames[fi].base;
863 let result = -self.registers[base + src as usize].to_number();
864 self.registers[base + dst as usize] = Value::Number(result);
865 }
866
867 // ── Bitwise ────────────────────────────────────
868 Op::BitAnd => {
869 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
870 let a = to_int32(&self.registers[base + lhs_r]);
871 let b = to_int32(&self.registers[base + rhs_r]);
872 self.registers[base + dst] = Value::Number((a & b) as f64);
873 }
874 Op::BitOr => {
875 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
876 let a = to_int32(&self.registers[base + lhs_r]);
877 let b = to_int32(&self.registers[base + rhs_r]);
878 self.registers[base + dst] = Value::Number((a | b) as f64);
879 }
880 Op::BitXor => {
881 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
882 let a = to_int32(&self.registers[base + lhs_r]);
883 let b = to_int32(&self.registers[base + rhs_r]);
884 self.registers[base + dst] = Value::Number((a ^ b) as f64);
885 }
886 Op::ShiftLeft => {
887 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
888 let a = to_int32(&self.registers[base + lhs_r]);
889 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
890 self.registers[base + dst] = Value::Number((a << b) as f64);
891 }
892 Op::ShiftRight => {
893 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
894 let a = to_int32(&self.registers[base + lhs_r]);
895 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
896 self.registers[base + dst] = Value::Number((a >> b) as f64);
897 }
898 Op::UShiftRight => {
899 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
900 let a = to_uint32(&self.registers[base + lhs_r]);
901 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
902 self.registers[base + dst] = Value::Number((a >> b) as f64);
903 }
904 Op::BitNot => {
905 let dst = Self::read_u8(&mut self.frames[fi]);
906 let src = Self::read_u8(&mut self.frames[fi]);
907 let base = self.frames[fi].base;
908 let result = !to_int32(&self.registers[base + src as usize]);
909 self.registers[base + dst as usize] = Value::Number(result as f64);
910 }
911
912 // ── Comparison ─────────────────────────────────
913 Op::Eq => {
914 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
915 let result =
916 abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
917 self.registers[base + dst] = Value::Boolean(result);
918 }
919 Op::StrictEq => {
920 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
921 let result =
922 strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
923 self.registers[base + dst] = Value::Boolean(result);
924 }
925 Op::NotEq => {
926 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
927 let result =
928 !abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
929 self.registers[base + dst] = Value::Boolean(result);
930 }
931 Op::StrictNotEq => {
932 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
933 let result =
934 !strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
935 self.registers[base + dst] = Value::Boolean(result);
936 }
937 Op::LessThan => {
938 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
939 let result = abstract_relational(
940 &self.registers[base + lhs_r],
941 &self.registers[base + rhs_r],
942 |ord| ord == std::cmp::Ordering::Less,
943 );
944 self.registers[base + dst] = Value::Boolean(result);
945 }
946 Op::LessEq => {
947 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
948 let result = abstract_relational(
949 &self.registers[base + lhs_r],
950 &self.registers[base + rhs_r],
951 |ord| ord != std::cmp::Ordering::Greater,
952 );
953 self.registers[base + dst] = Value::Boolean(result);
954 }
955 Op::GreaterThan => {
956 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
957 let result = abstract_relational(
958 &self.registers[base + lhs_r],
959 &self.registers[base + rhs_r],
960 |ord| ord == std::cmp::Ordering::Greater,
961 );
962 self.registers[base + dst] = Value::Boolean(result);
963 }
964 Op::GreaterEq => {
965 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
966 let result = abstract_relational(
967 &self.registers[base + lhs_r],
968 &self.registers[base + rhs_r],
969 |ord| ord != std::cmp::Ordering::Less,
970 );
971 self.registers[base + dst] = Value::Boolean(result);
972 }
973
974 // ── Logical / unary ────────────────────────────
975 Op::LogicalNot => {
976 let dst = Self::read_u8(&mut self.frames[fi]);
977 let src = Self::read_u8(&mut self.frames[fi]);
978 let base = self.frames[fi].base;
979 let result = !self.registers[base + src as usize].to_boolean();
980 self.registers[base + dst as usize] = Value::Boolean(result);
981 }
982 Op::TypeOf => {
983 let dst = Self::read_u8(&mut self.frames[fi]);
984 let src = Self::read_u8(&mut self.frames[fi]);
985 let base = self.frames[fi].base;
986 let t = self.registers[base + src as usize].type_of();
987 self.registers[base + dst as usize] = Value::String(t.to_string());
988 }
989 Op::InstanceOf => {
990 let dst = Self::read_u8(&mut self.frames[fi]);
991 let lhs_r = Self::read_u8(&mut self.frames[fi]);
992 let rhs_r = Self::read_u8(&mut self.frames[fi]);
993 let base = self.frames[fi].base;
994 let result = match (
995 &self.registers[base + lhs_r as usize],
996 &self.registers[base + rhs_r as usize],
997 ) {
998 (Value::Object(obj_ref), Value::Function(ctor_ref)) => {
999 gc_instanceof(&self.gc, *obj_ref, *ctor_ref)
1000 }
1001 (_, Value::Function(_)) => false,
1002 _ => {
1003 return Err(RuntimeError::type_error(
1004 "Right-hand side of instanceof is not callable",
1005 ));
1006 }
1007 };
1008 self.registers[base + dst as usize] = Value::Boolean(result);
1009 }
1010 Op::In => {
1011 let dst = Self::read_u8(&mut self.frames[fi]);
1012 let key_r = Self::read_u8(&mut self.frames[fi]);
1013 let obj_r = Self::read_u8(&mut self.frames[fi]);
1014 let base = self.frames[fi].base;
1015 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1016 let result = match self.registers[base + obj_r as usize] {
1017 Value::Object(gc_ref) => {
1018 Value::Boolean(gc_has_property(&self.gc, gc_ref, &key))
1019 }
1020 _ => {
1021 return Err(RuntimeError::type_error(
1022 "Cannot use 'in' operator to search for property in non-object",
1023 ));
1024 }
1025 };
1026 self.registers[base + dst as usize] = result;
1027 }
1028 Op::Void => {
1029 let dst = Self::read_u8(&mut self.frames[fi]);
1030 let _src = Self::read_u8(&mut self.frames[fi]);
1031 let base = self.frames[fi].base;
1032 self.registers[base + dst as usize] = Value::Undefined;
1033 }
1034
1035 // ── Control flow ───────────────────────────────
1036 Op::Jump => {
1037 let offset = Self::read_i32(&mut self.frames[fi]);
1038 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1039 }
1040 Op::JumpIfTrue => {
1041 let reg = Self::read_u8(&mut self.frames[fi]);
1042 let offset = Self::read_i32(&mut self.frames[fi]);
1043 let base = self.frames[fi].base;
1044 if self.registers[base + reg as usize].to_boolean() {
1045 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1046 }
1047 }
1048 Op::JumpIfFalse => {
1049 let reg = Self::read_u8(&mut self.frames[fi]);
1050 let offset = Self::read_i32(&mut self.frames[fi]);
1051 let base = self.frames[fi].base;
1052 if !self.registers[base + reg as usize].to_boolean() {
1053 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1054 }
1055 }
1056 Op::JumpIfNullish => {
1057 let reg = Self::read_u8(&mut self.frames[fi]);
1058 let offset = Self::read_i32(&mut self.frames[fi]);
1059 let base = self.frames[fi].base;
1060 if self.registers[base + reg as usize].is_nullish() {
1061 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1062 }
1063 }
1064
1065 // ── Functions / calls ──────────────────────────
1066 Op::Call => {
1067 let dst = Self::read_u8(&mut self.frames[fi]);
1068 let func_r = Self::read_u8(&mut self.frames[fi]);
1069 let args_start = Self::read_u8(&mut self.frames[fi]);
1070 let arg_count = Self::read_u8(&mut self.frames[fi]);
1071 let base = self.frames[fi].base;
1072
1073 // Extract function GcRef.
1074 let func_gc_ref = match self.registers[base + func_r as usize] {
1075 Value::Function(r) => r,
1076 _ => {
1077 let desc =
1078 self.registers[base + func_r as usize].to_js_string(&self.gc);
1079 let err = RuntimeError::type_error(format!("{desc} is not a function"));
1080 let err_val = err.to_value(&mut self.gc);
1081 if !self.handle_exception(err_val) {
1082 return Err(err);
1083 }
1084 continue;
1085 }
1086 };
1087
1088 // Collect arguments.
1089 let mut args = Vec::with_capacity(arg_count as usize);
1090 for i in 0..arg_count {
1091 args.push(self.registers[base + (args_start + i) as usize].clone());
1092 }
1093
1094 // Read function data from GC (scoped borrow).
1095 let call_info = {
1096 match self.gc.get(func_gc_ref) {
1097 Some(HeapObject::Function(fdata)) => match &fdata.kind {
1098 FunctionKind::Native(n) => CallInfo::Native(n.callback),
1099 FunctionKind::Bytecode(bc) => CallInfo::Bytecode(bc.func.clone()),
1100 },
1101 _ => {
1102 let err = RuntimeError::type_error("not a function");
1103 let err_val = err.to_value(&mut self.gc);
1104 if !self.handle_exception(err_val) {
1105 return Err(err);
1106 }
1107 continue;
1108 }
1109 }
1110 };
1111
1112 match call_info {
1113 CallInfo::Native(callback) => match callback(&args) {
1114 Ok(val) => {
1115 self.registers[base + dst as usize] = val;
1116 }
1117 Err(err) => {
1118 let err_val = err.to_value(&mut self.gc);
1119 if !self.handle_exception(err_val) {
1120 return Err(err);
1121 }
1122 }
1123 },
1124 CallInfo::Bytecode(callee_func) => {
1125 if self.frames.len() >= MAX_CALL_DEPTH {
1126 let err =
1127 RuntimeError::range_error("Maximum call stack size exceeded");
1128 let err_val = err.to_value(&mut self.gc);
1129 if !self.handle_exception(err_val) {
1130 return Err(err);
1131 }
1132 continue;
1133 }
1134
1135 let callee_base = base + self.frames[fi].func.register_count as usize;
1136 let callee_regs = callee_func.register_count as usize;
1137 self.ensure_registers(callee_base + callee_regs);
1138
1139 // Copy arguments into callee's registers.
1140 for i in 0..callee_func.param_count.min(arg_count) {
1141 self.registers[callee_base + i as usize] = args[i as usize].clone();
1142 }
1143 // Fill remaining params with undefined.
1144 for i in arg_count..callee_func.param_count {
1145 self.registers[callee_base + i as usize] = Value::Undefined;
1146 }
1147
1148 self.frames.push(CallFrame {
1149 func: callee_func,
1150 ip: 0,
1151 base: callee_base,
1152 return_reg: base + dst as usize,
1153 exception_handlers: Vec::new(),
1154 });
1155 }
1156 }
1157 }
1158 Op::Return => {
1159 let reg = Self::read_u8(&mut self.frames[fi]);
1160 let base = self.frames[fi].base;
1161 let val = self.registers[base + reg as usize].clone();
1162
1163 if self.frames.len() == 1 {
1164 self.frames.pop();
1165 return Ok(val);
1166 }
1167
1168 let old = self.frames.pop().unwrap();
1169 self.registers[old.return_reg] = val;
1170 }
1171 Op::Throw => {
1172 let reg = Self::read_u8(&mut self.frames[fi]);
1173 let base = self.frames[fi].base;
1174 let val = self.registers[base + reg as usize].clone();
1175
1176 if !self.handle_exception(val) {
1177 let msg = self.registers[base + reg as usize].to_js_string(&self.gc);
1178 return Err(RuntimeError {
1179 kind: ErrorKind::Error,
1180 message: msg,
1181 });
1182 }
1183 }
1184 Op::CreateClosure => {
1185 let dst = Self::read_u8(&mut self.frames[fi]);
1186 let func_idx = Self::read_u16(&mut self.frames[fi]) as usize;
1187 let base = self.frames[fi].base;
1188 let inner_func = self.frames[fi].func.functions[func_idx].clone();
1189 let name = inner_func.name.clone();
1190 // Create a .prototype object for the function (for instanceof).
1191 let proto_obj = self.gc.alloc(HeapObject::Object(ObjectData::new()));
1192 let gc_ref = self.gc.alloc(HeapObject::Function(FunctionData {
1193 name,
1194 kind: FunctionKind::Bytecode(BytecodeFunc { func: inner_func }),
1195 prototype_obj: Some(proto_obj),
1196 }));
1197 // Set .prototype.constructor = this function.
1198 if let Some(HeapObject::Object(data)) = self.gc.get_mut(proto_obj) {
1199 data.properties.insert(
1200 "constructor".to_string(),
1201 Property {
1202 value: Value::Function(gc_ref),
1203 writable: true,
1204 enumerable: false,
1205 configurable: true,
1206 },
1207 );
1208 }
1209 self.registers[base + dst as usize] = Value::Function(gc_ref);
1210
1211 // Trigger GC if needed.
1212 if self.gc.should_collect() {
1213 let roots = self.collect_roots();
1214 self.gc.collect(&roots);
1215 }
1216 }
1217
1218 // ── Object / property ──────────────────────────
1219 Op::GetProperty => {
1220 let dst = Self::read_u8(&mut self.frames[fi]);
1221 let obj_r = Self::read_u8(&mut self.frames[fi]);
1222 let key_r = Self::read_u8(&mut self.frames[fi]);
1223 let base = self.frames[fi].base;
1224 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1225 let val = match self.registers[base + obj_r as usize] {
1226 Value::Object(gc_ref) => gc_get_property(&self.gc, gc_ref, &key),
1227 Value::String(ref s) => string_get_property(s, &key),
1228 _ => Value::Undefined,
1229 };
1230 self.registers[base + dst as usize] = val;
1231 }
1232 Op::SetProperty => {
1233 let obj_r = Self::read_u8(&mut self.frames[fi]);
1234 let key_r = Self::read_u8(&mut self.frames[fi]);
1235 let val_r = Self::read_u8(&mut self.frames[fi]);
1236 let base = self.frames[fi].base;
1237 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1238 let val = self.registers[base + val_r as usize].clone();
1239 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] {
1240 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1241 if let Some(prop) = data.properties.get_mut(&key) {
1242 if prop.writable {
1243 prop.value = val;
1244 }
1245 } else {
1246 data.properties.insert(key, Property::data(val));
1247 }
1248 }
1249 }
1250 }
1251 Op::CreateObject => {
1252 let dst = Self::read_u8(&mut self.frames[fi]);
1253 let base = self.frames[fi].base;
1254 let gc_ref = self.gc.alloc(HeapObject::Object(ObjectData::new()));
1255 self.registers[base + dst as usize] = Value::Object(gc_ref);
1256
1257 if self.gc.should_collect() {
1258 let roots = self.collect_roots();
1259 self.gc.collect(&roots);
1260 }
1261 }
1262 Op::CreateArray => {
1263 let dst = Self::read_u8(&mut self.frames[fi]);
1264 let base = self.frames[fi].base;
1265 let mut obj = ObjectData::new();
1266 obj.properties.insert(
1267 "length".to_string(),
1268 Property {
1269 value: Value::Number(0.0),
1270 writable: true,
1271 enumerable: false,
1272 configurable: false,
1273 },
1274 );
1275 let gc_ref = self.gc.alloc(HeapObject::Object(obj));
1276 self.registers[base + dst as usize] = Value::Object(gc_ref);
1277
1278 if self.gc.should_collect() {
1279 let roots = self.collect_roots();
1280 self.gc.collect(&roots);
1281 }
1282 }
1283 Op::GetPropertyByName => {
1284 let dst = Self::read_u8(&mut self.frames[fi]);
1285 let obj_r = Self::read_u8(&mut self.frames[fi]);
1286 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
1287 let base = self.frames[fi].base;
1288 let key = self.frames[fi].func.names[name_idx].clone();
1289 let val = match self.registers[base + obj_r as usize] {
1290 Value::Object(gc_ref) => gc_get_property(&self.gc, gc_ref, &key),
1291 Value::String(ref s) => string_get_property(s, &key),
1292 _ => Value::Undefined,
1293 };
1294 self.registers[base + dst as usize] = val;
1295 }
1296 Op::SetPropertyByName => {
1297 let obj_r = Self::read_u8(&mut self.frames[fi]);
1298 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
1299 let val_r = Self::read_u8(&mut self.frames[fi]);
1300 let base = self.frames[fi].base;
1301 let key = self.frames[fi].func.names[name_idx].clone();
1302 let val = self.registers[base + val_r as usize].clone();
1303 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] {
1304 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1305 if let Some(prop) = data.properties.get_mut(&key) {
1306 if prop.writable {
1307 prop.value = val;
1308 }
1309 } else {
1310 data.properties.insert(key, Property::data(val));
1311 }
1312 }
1313 }
1314 }
1315
1316 // ── Misc ───────────────────────────────────────
1317 Op::Delete => {
1318 let dst = Self::read_u8(&mut self.frames[fi]);
1319 let obj_r = Self::read_u8(&mut self.frames[fi]);
1320 let key_r = Self::read_u8(&mut self.frames[fi]);
1321 let base = self.frames[fi].base;
1322 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1323 let result =
1324 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] {
1325 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1326 match data.properties.get(&key) {
1327 Some(prop) if !prop.configurable => false,
1328 Some(_) => {
1329 data.properties.remove(&key);
1330 true
1331 }
1332 None => true,
1333 }
1334 } else {
1335 true
1336 }
1337 } else {
1338 true
1339 };
1340 self.registers[base + dst as usize] = Value::Boolean(result);
1341 }
1342 Op::ForInInit => {
1343 let dst = Self::read_u8(&mut self.frames[fi]);
1344 let obj_r = Self::read_u8(&mut self.frames[fi]);
1345 let base = self.frames[fi].base;
1346 let keys = match self.registers[base + obj_r as usize] {
1347 Value::Object(gc_ref) => gc_enumerate_keys(&self.gc, gc_ref),
1348 _ => Vec::new(),
1349 };
1350 // Store keys as an array object.
1351 let mut arr = ObjectData::new();
1352 for (i, key) in keys.iter().enumerate() {
1353 arr.properties
1354 .insert(i.to_string(), Property::data(Value::String(key.clone())));
1355 }
1356 arr.properties.insert(
1357 "length".to_string(),
1358 Property {
1359 value: Value::Number(keys.len() as f64),
1360 writable: true,
1361 enumerable: false,
1362 configurable: false,
1363 },
1364 );
1365 let gc_ref = self.gc.alloc(HeapObject::Object(arr));
1366 self.registers[base + dst as usize] = Value::Object(gc_ref);
1367 }
1368 Op::ForInNext => {
1369 let dst_val = Self::read_u8(&mut self.frames[fi]);
1370 let dst_done = Self::read_u8(&mut self.frames[fi]);
1371 let keys_r = Self::read_u8(&mut self.frames[fi]);
1372 let idx_r = Self::read_u8(&mut self.frames[fi]);
1373 let base = self.frames[fi].base;
1374 let idx = self.registers[base + idx_r as usize].to_number() as usize;
1375 let len = match self.registers[base + keys_r as usize] {
1376 Value::Object(gc_ref) => {
1377 gc_get_property(&self.gc, gc_ref, "length").to_number() as usize
1378 }
1379 _ => 0,
1380 };
1381 if idx >= len {
1382 self.registers[base + dst_done as usize] = Value::Boolean(true);
1383 self.registers[base + dst_val as usize] = Value::Undefined;
1384 } else {
1385 let key_str = idx.to_string();
1386 let key = match self.registers[base + keys_r as usize] {
1387 Value::Object(gc_ref) => gc_get_property(&self.gc, gc_ref, &key_str),
1388 _ => Value::Undefined,
1389 };
1390 self.registers[base + dst_val as usize] = key;
1391 self.registers[base + dst_done as usize] = Value::Boolean(false);
1392 }
1393 }
1394 Op::SetPrototype => {
1395 let obj_r = Self::read_u8(&mut self.frames[fi]);
1396 let proto_r = Self::read_u8(&mut self.frames[fi]);
1397 let base = self.frames[fi].base;
1398 let proto = match &self.registers[base + proto_r as usize] {
1399 Value::Object(r) => Some(*r),
1400 Value::Null => None,
1401 _ => None,
1402 };
1403 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] {
1404 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1405 data.prototype = proto;
1406 }
1407 }
1408 }
1409 Op::GetPrototype => {
1410 let dst = Self::read_u8(&mut self.frames[fi]);
1411 let obj_r = Self::read_u8(&mut self.frames[fi]);
1412 let base = self.frames[fi].base;
1413 let proto = match self.registers[base + obj_r as usize] {
1414 Value::Object(gc_ref) => match self.gc.get(gc_ref) {
1415 Some(HeapObject::Object(data)) => {
1416 data.prototype.map(Value::Object).unwrap_or(Value::Null)
1417 }
1418 _ => Value::Null,
1419 },
1420 _ => Value::Null,
1421 };
1422 self.registers[base + dst as usize] = proto;
1423 }
1424 }
1425 }
1426 }
1427
1428 /// Read 3 register operands and return (dst, lhs, rhs, base) as usize indices.
1429 fn read_3reg(&mut self, fi: usize) -> (usize, usize, usize, usize) {
1430 let dst = Self::read_u8(&mut self.frames[fi]) as usize;
1431 let lhs = Self::read_u8(&mut self.frames[fi]) as usize;
1432 let rhs = Self::read_u8(&mut self.frames[fi]) as usize;
1433 let base = self.frames[fi].base;
1434 (dst, lhs, rhs, base)
1435 }
1436
1437 /// Try to find an exception handler on the call stack.
1438 /// Returns true if a handler was found (execution resumes there).
1439 fn handle_exception(&mut self, value: Value) -> bool {
1440 while let Some(frame) = self.frames.last_mut() {
1441 if let Some(handler) = frame.exception_handlers.pop() {
1442 let base = frame.base;
1443 frame.ip = handler.catch_ip;
1444 self.registers[base + handler.catch_reg as usize] = value;
1445 return true;
1446 }
1447 if self.frames.len() == 1 {
1448 break;
1449 }
1450 self.frames.pop();
1451 }
1452 false
1453 }
1454
1455 /// Register a native function as a global.
1456 pub fn define_native(
1457 &mut self,
1458 name: &str,
1459 callback: fn(&[Value]) -> Result<Value, RuntimeError>,
1460 ) {
1461 let gc_ref = self.gc.alloc(HeapObject::Function(FunctionData {
1462 name: name.to_string(),
1463 kind: FunctionKind::Native(NativeFunc { callback }),
1464 prototype_obj: None,
1465 }));
1466 self.globals
1467 .insert(name.to_string(), Value::Function(gc_ref));
1468 }
1469
1470 /// Get a global variable value.
1471 pub fn get_global(&self, name: &str) -> Option<&Value> {
1472 self.globals.get(name)
1473 }
1474
1475 /// Set a global variable.
1476 pub fn set_global(&mut self, name: &str, val: Value) {
1477 self.globals.insert(name.to_string(), val);
1478 }
1479}
1480
1481impl Default for Vm {
1482 fn default() -> Self {
1483 Self::new()
1484 }
1485}
1486
1487/// Internal enum to avoid holding a GC borrow across the call setup.
1488enum CallInfo {
1489 Native(fn(&[Value]) -> Result<Value, RuntimeError>),
1490 Bytecode(Function),
1491}
1492
1493// ── Tests ────────────────────────────────────────────────────
1494
1495#[cfg(test)]
1496mod tests {
1497 use super::*;
1498 use crate::bytecode::{BytecodeBuilder, Constant, Op};
1499 use crate::compiler;
1500 use crate::parser::Parser;
1501
1502 /// Helper: compile and execute JS source, return the completion value.
1503 fn eval(source: &str) -> Result<Value, RuntimeError> {
1504 let program = Parser::parse(source).expect("parse failed");
1505 let func = compiler::compile(&program).expect("compile failed");
1506 let mut vm = Vm::new();
1507 vm.execute(&func)
1508 }
1509
1510 // ── Value tests ─────────────────────────────────────────
1511
1512 #[test]
1513 fn test_to_boolean() {
1514 let mut gc: Gc<HeapObject> = Gc::new();
1515 assert!(!Value::Undefined.to_boolean());
1516 assert!(!Value::Null.to_boolean());
1517 assert!(!Value::Boolean(false).to_boolean());
1518 assert!(Value::Boolean(true).to_boolean());
1519 assert!(!Value::Number(0.0).to_boolean());
1520 assert!(!Value::Number(f64::NAN).to_boolean());
1521 assert!(Value::Number(1.0).to_boolean());
1522 assert!(!Value::String(String::new()).to_boolean());
1523 assert!(Value::String("hello".to_string()).to_boolean());
1524 let obj_ref = gc.alloc(HeapObject::Object(ObjectData::new()));
1525 assert!(Value::Object(obj_ref).to_boolean());
1526 }
1527
1528 #[test]
1529 fn test_to_number() {
1530 assert!(Value::Undefined.to_number().is_nan());
1531 assert_eq!(Value::Null.to_number(), 0.0);
1532 assert_eq!(Value::Boolean(true).to_number(), 1.0);
1533 assert_eq!(Value::Boolean(false).to_number(), 0.0);
1534 assert_eq!(Value::Number(42.0).to_number(), 42.0);
1535 assert_eq!(Value::String("42".to_string()).to_number(), 42.0);
1536 assert_eq!(Value::String("".to_string()).to_number(), 0.0);
1537 assert!(Value::String("abc".to_string()).to_number().is_nan());
1538 }
1539
1540 #[test]
1541 fn test_type_of() {
1542 let mut gc: Gc<HeapObject> = Gc::new();
1543 assert_eq!(Value::Undefined.type_of(), "undefined");
1544 assert_eq!(Value::Null.type_of(), "object");
1545 assert_eq!(Value::Boolean(true).type_of(), "boolean");
1546 assert_eq!(Value::Number(1.0).type_of(), "number");
1547 assert_eq!(Value::String("hi".to_string()).type_of(), "string");
1548 let obj_ref = gc.alloc(HeapObject::Object(ObjectData::new()));
1549 assert_eq!(Value::Object(obj_ref).type_of(), "object");
1550 }
1551
1552 // ── VM bytecode-level tests ─────────────────────────────
1553
1554 #[test]
1555 fn test_load_const_number() {
1556 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1557 b.func.register_count = 1;
1558 let ci = b.add_constant(Constant::Number(42.0));
1559 b.emit_reg_u16(Op::LoadConst, 0, ci);
1560 b.emit_reg(Op::Return, 0);
1561 let func = b.finish();
1562
1563 let mut vm = Vm::new();
1564 let result = vm.execute(&func).unwrap();
1565 match result {
1566 Value::Number(n) => assert_eq!(n, 42.0),
1567 _ => panic!("expected Number, got {result:?}"),
1568 }
1569 }
1570
1571 #[test]
1572 fn test_arithmetic_ops() {
1573 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1574 b.func.register_count = 3;
1575 let c10 = b.add_constant(Constant::Number(10.0));
1576 let c3 = b.add_constant(Constant::Number(3.0));
1577 b.emit_reg_u16(Op::LoadConst, 0, c10);
1578 b.emit_reg_u16(Op::LoadConst, 1, c3);
1579 b.emit_reg3(Op::Add, 2, 0, 1);
1580 b.emit_reg(Op::Return, 2);
1581 let func = b.finish();
1582
1583 let mut vm = Vm::new();
1584 match vm.execute(&func).unwrap() {
1585 Value::Number(n) => assert_eq!(n, 13.0),
1586 v => panic!("expected 13, got {v:?}"),
1587 }
1588 }
1589
1590 #[test]
1591 fn test_string_concat() {
1592 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1593 b.func.register_count = 3;
1594 let c1 = b.add_constant(Constant::String("hello".into()));
1595 let c2 = b.add_constant(Constant::String(" world".into()));
1596 b.emit_reg_u16(Op::LoadConst, 0, c1);
1597 b.emit_reg_u16(Op::LoadConst, 1, c2);
1598 b.emit_reg3(Op::Add, 2, 0, 1);
1599 b.emit_reg(Op::Return, 2);
1600 let func = b.finish();
1601
1602 let mut vm = Vm::new();
1603 match vm.execute(&func).unwrap() {
1604 Value::String(s) => assert_eq!(s, "hello world"),
1605 v => panic!("expected string, got {v:?}"),
1606 }
1607 }
1608
1609 #[test]
1610 fn test_jump_if_false() {
1611 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1612 b.func.register_count = 2;
1613 b.emit_reg(Op::LoadFalse, 0);
1614 let patch = b.emit_cond_jump(Op::JumpIfFalse, 0);
1615 b.emit_load_int8(1, 1);
1616 let skip = b.emit_jump(Op::Jump);
1617 b.patch_jump(patch);
1618 b.emit_load_int8(1, 2);
1619 b.patch_jump(skip);
1620 b.emit_reg(Op::Return, 1);
1621 let func = b.finish();
1622
1623 let mut vm = Vm::new();
1624 match vm.execute(&func).unwrap() {
1625 Value::Number(n) => assert_eq!(n, 2.0),
1626 v => panic!("expected 2, got {v:?}"),
1627 }
1628 }
1629
1630 #[test]
1631 fn test_globals() {
1632 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1633 b.func.register_count = 2;
1634 let name = b.add_name("x");
1635 b.emit_load_int8(0, 42);
1636 b.emit_store_global(name, 0);
1637 b.emit_load_global(1, name);
1638 b.emit_reg(Op::Return, 1);
1639 let func = b.finish();
1640
1641 let mut vm = Vm::new();
1642 match vm.execute(&func).unwrap() {
1643 Value::Number(n) => assert_eq!(n, 42.0),
1644 v => panic!("expected 42, got {v:?}"),
1645 }
1646 }
1647
1648 #[test]
1649 fn test_function_call() {
1650 let mut inner_b = BytecodeBuilder::new("add1".into(), 1);
1651 inner_b.func.register_count = 2;
1652 inner_b.emit_load_int8(1, 1);
1653 inner_b.emit_reg3(Op::Add, 0, 0, 1);
1654 inner_b.emit_reg(Op::Return, 0);
1655 let inner = inner_b.finish();
1656
1657 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1658 b.func.register_count = 4;
1659 let fi = b.add_function(inner);
1660 b.emit_reg_u16(Op::CreateClosure, 0, fi);
1661 b.emit_load_int8(1, 10);
1662 b.emit_call(2, 0, 1, 1);
1663 b.emit_reg(Op::Return, 2);
1664 let func = b.finish();
1665
1666 let mut vm = Vm::new();
1667 match vm.execute(&func).unwrap() {
1668 Value::Number(n) => assert_eq!(n, 11.0),
1669 v => panic!("expected 11, got {v:?}"),
1670 }
1671 }
1672
1673 #[test]
1674 fn test_native_function() {
1675 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1676 b.func.register_count = 3;
1677 let name = b.add_name("double");
1678 b.emit_load_global(0, name);
1679 b.emit_load_int8(1, 21);
1680 b.emit_call(2, 0, 1, 1);
1681 b.emit_reg(Op::Return, 2);
1682 let func = b.finish();
1683
1684 let mut vm = Vm::new();
1685 vm.define_native("double", |args| {
1686 let n = args.first().unwrap_or(&Value::Undefined).to_number();
1687 Ok(Value::Number(n * 2.0))
1688 });
1689 match vm.execute(&func).unwrap() {
1690 Value::Number(n) => assert_eq!(n, 42.0),
1691 v => panic!("expected 42, got {v:?}"),
1692 }
1693 }
1694
1695 #[test]
1696 fn test_object_property() {
1697 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1698 b.func.register_count = 3;
1699 let name = b.add_name("x");
1700 b.emit_reg(Op::CreateObject, 0);
1701 b.emit_load_int8(1, 42);
1702 b.emit_set_prop_name(0, name, 1);
1703 b.emit_get_prop_name(2, 0, name);
1704 b.emit_reg(Op::Return, 2);
1705 let func = b.finish();
1706
1707 let mut vm = Vm::new();
1708 match vm.execute(&func).unwrap() {
1709 Value::Number(n) => assert_eq!(n, 42.0),
1710 v => panic!("expected 42, got {v:?}"),
1711 }
1712 }
1713
1714 #[test]
1715 fn test_typeof_operator() {
1716 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1717 b.func.register_count = 2;
1718 b.emit_load_int8(0, 5);
1719 b.emit_reg_reg(Op::TypeOf, 1, 0);
1720 b.emit_reg(Op::Return, 1);
1721 let func = b.finish();
1722
1723 let mut vm = Vm::new();
1724 match vm.execute(&func).unwrap() {
1725 Value::String(s) => assert_eq!(s, "number"),
1726 v => panic!("expected 'number', got {v:?}"),
1727 }
1728 }
1729
1730 #[test]
1731 fn test_comparison() {
1732 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1733 b.func.register_count = 3;
1734 b.emit_load_int8(0, 5);
1735 b.emit_load_int8(1, 10);
1736 b.emit_reg3(Op::LessThan, 2, 0, 1);
1737 b.emit_reg(Op::Return, 2);
1738 let func = b.finish();
1739
1740 let mut vm = Vm::new();
1741 match vm.execute(&func).unwrap() {
1742 Value::Boolean(b) => assert!(b),
1743 v => panic!("expected true, got {v:?}"),
1744 }
1745 }
1746
1747 #[test]
1748 fn test_abstract_equality() {
1749 assert!(abstract_eq(&Value::Null, &Value::Undefined));
1750 assert!(abstract_eq(&Value::Undefined, &Value::Null));
1751 assert!(abstract_eq(
1752 &Value::Number(1.0),
1753 &Value::String("1".to_string())
1754 ));
1755 assert!(abstract_eq(&Value::Boolean(true), &Value::Number(1.0)));
1756 assert!(!abstract_eq(&Value::Number(0.0), &Value::Null));
1757 }
1758
1759 #[test]
1760 fn test_strict_equality() {
1761 assert!(strict_eq(&Value::Number(1.0), &Value::Number(1.0)));
1762 assert!(!strict_eq(
1763 &Value::Number(1.0),
1764 &Value::String("1".to_string())
1765 ));
1766 assert!(strict_eq(&Value::Null, &Value::Null));
1767 assert!(!strict_eq(&Value::Null, &Value::Undefined));
1768 }
1769
1770 #[test]
1771 fn test_bitwise_ops() {
1772 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1773 b.func.register_count = 3;
1774 b.emit_load_int8(0, 0x0F);
1775 b.emit_load_int8(1, 0x03);
1776 b.emit_reg3(Op::BitAnd, 2, 0, 1);
1777 b.emit_reg(Op::Return, 2);
1778 let func = b.finish();
1779
1780 let mut vm = Vm::new();
1781 match vm.execute(&func).unwrap() {
1782 Value::Number(n) => assert_eq!(n, 3.0),
1783 v => panic!("expected 3, got {v:?}"),
1784 }
1785 }
1786
1787 #[test]
1788 fn test_throw_uncaught() {
1789 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1790 b.func.register_count = 1;
1791 let ci = b.add_constant(Constant::String("oops".into()));
1792 b.emit_reg_u16(Op::LoadConst, 0, ci);
1793 b.emit_reg(Op::Throw, 0);
1794 let func = b.finish();
1795
1796 let mut vm = Vm::new();
1797 let err = vm.execute(&func).unwrap_err();
1798 assert_eq!(err.message, "oops");
1799 }
1800
1801 #[test]
1802 fn test_loop_counting() {
1803 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1804 b.func.register_count = 3;
1805 b.emit_load_int8(0, 0);
1806 b.emit_load_int8(1, 5);
1807 let loop_start = b.offset();
1808 b.emit_reg3(Op::LessThan, 2, 0, 1);
1809 let exit_patch = b.emit_cond_jump(Op::JumpIfFalse, 2);
1810 b.emit_load_int8(2, 1);
1811 b.emit_reg3(Op::Add, 0, 0, 2);
1812 b.emit_jump_to(loop_start);
1813 b.patch_jump(exit_patch);
1814 b.emit_reg(Op::Return, 0);
1815 let func = b.finish();
1816
1817 let mut vm = Vm::new();
1818 match vm.execute(&func).unwrap() {
1819 Value::Number(n) => assert_eq!(n, 5.0),
1820 v => panic!("expected 5, got {v:?}"),
1821 }
1822 }
1823
1824 // ── End-to-end (compile + execute) tests ────────────────
1825
1826 #[test]
1827 fn test_e2e_arithmetic() {
1828 match eval("2 + 3 * 4").unwrap() {
1829 Value::Number(n) => assert_eq!(n, 14.0),
1830 v => panic!("expected 14, got {v:?}"),
1831 }
1832 }
1833
1834 #[test]
1835 fn test_e2e_variables() {
1836 match eval("var x = 10; var y = 20; x + y").unwrap() {
1837 Value::Number(n) => assert_eq!(n, 30.0),
1838 v => panic!("expected 30, got {v:?}"),
1839 }
1840 }
1841
1842 #[test]
1843 fn test_e2e_if_else() {
1844 match eval("var x = 5; if (x > 3) { x = 100; } x").unwrap() {
1845 Value::Number(n) => assert_eq!(n, 100.0),
1846 v => panic!("expected 100, got {v:?}"),
1847 }
1848 }
1849
1850 #[test]
1851 fn test_e2e_while_loop() {
1852 match eval("var i = 0; var sum = 0; while (i < 10) { sum = sum + i; i = i + 1; } sum")
1853 .unwrap()
1854 {
1855 Value::Number(n) => assert_eq!(n, 45.0),
1856 v => panic!("expected 45, got {v:?}"),
1857 }
1858 }
1859
1860 #[test]
1861 fn test_e2e_function_call() {
1862 match eval("function add(a, b) { return a + b; } add(3, 4)").unwrap() {
1863 Value::Number(n) => assert_eq!(n, 7.0),
1864 v => panic!("expected 7, got {v:?}"),
1865 }
1866 }
1867
1868 #[test]
1869 fn test_e2e_recursive_factorial() {
1870 let src = r#"
1871 function fact(n) {
1872 if (n <= 1) return 1;
1873 return n * fact(n - 1);
1874 }
1875 fact(6)
1876 "#;
1877 match eval(src).unwrap() {
1878 Value::Number(n) => assert_eq!(n, 720.0),
1879 v => panic!("expected 720, got {v:?}"),
1880 }
1881 }
1882
1883 #[test]
1884 fn test_e2e_string_concat() {
1885 match eval("'hello' + ' ' + 'world'").unwrap() {
1886 Value::String(s) => assert_eq!(s, "hello world"),
1887 v => panic!("expected 'hello world', got {v:?}"),
1888 }
1889 }
1890
1891 #[test]
1892 fn test_e2e_comparison_coercion() {
1893 match eval("1 == '1'").unwrap() {
1894 Value::Boolean(b) => assert!(b),
1895 v => panic!("expected true, got {v:?}"),
1896 }
1897 match eval("1 === '1'").unwrap() {
1898 Value::Boolean(b) => assert!(!b),
1899 v => panic!("expected false, got {v:?}"),
1900 }
1901 }
1902
1903 #[test]
1904 fn test_e2e_typeof() {
1905 match eval("typeof 42").unwrap() {
1906 Value::String(s) => assert_eq!(s, "number"),
1907 v => panic!("expected 'number', got {v:?}"),
1908 }
1909 match eval("typeof 'hello'").unwrap() {
1910 Value::String(s) => assert_eq!(s, "string"),
1911 v => panic!("expected 'string', got {v:?}"),
1912 }
1913 }
1914
1915 #[test]
1916 fn test_e2e_object_literal() {
1917 match eval("var o = { x: 10, y: 20 }; o.x + o.y").unwrap() {
1918 Value::Number(n) => assert_eq!(n, 30.0),
1919 v => panic!("expected 30, got {v:?}"),
1920 }
1921 }
1922
1923 #[test]
1924 fn test_e2e_for_loop() {
1925 match eval("var s = 0; for (var i = 0; i < 5; i = i + 1) { s = s + i; } s").unwrap() {
1926 Value::Number(n) => assert_eq!(n, 10.0),
1927 v => panic!("expected 10, got {v:?}"),
1928 }
1929 }
1930
1931 #[test]
1932 fn test_e2e_nested_functions() {
1933 // Note: closures (capturing parent scope vars) not yet supported.
1934 // This test verifies nested function declarations and calls work.
1935 let src = r#"
1936 function outer(x) {
1937 function inner(y) {
1938 return y + 1;
1939 }
1940 return inner(x);
1941 }
1942 outer(5)
1943 "#;
1944 match eval(src).unwrap() {
1945 Value::Number(n) => assert_eq!(n, 6.0),
1946 v => panic!("expected 6, got {v:?}"),
1947 }
1948 }
1949
1950 #[test]
1951 fn test_e2e_logical_operators() {
1952 match eval("true && false").unwrap() {
1953 Value::Boolean(b) => assert!(!b),
1954 v => panic!("expected false, got {v:?}"),
1955 }
1956 match eval("false || true").unwrap() {
1957 Value::Boolean(b) => assert!(b),
1958 v => panic!("expected true, got {v:?}"),
1959 }
1960 }
1961
1962 #[test]
1963 fn test_e2e_unary_neg() {
1964 match eval("-(5 + 3)").unwrap() {
1965 Value::Number(n) => assert_eq!(n, -8.0),
1966 v => panic!("expected -8, got {v:?}"),
1967 }
1968 }
1969
1970 #[test]
1971 fn test_e2e_ternary() {
1972 match eval("true ? 1 : 2").unwrap() {
1973 Value::Number(n) => assert_eq!(n, 1.0),
1974 v => panic!("expected 1, got {v:?}"),
1975 }
1976 match eval("false ? 1 : 2").unwrap() {
1977 Value::Number(n) => assert_eq!(n, 2.0),
1978 v => panic!("expected 2, got {v:?}"),
1979 }
1980 }
1981
1982 #[test]
1983 fn test_e2e_fibonacci() {
1984 let src = r#"
1985 function fib(n) {
1986 if (n <= 1) return n;
1987 return fib(n - 1) + fib(n - 2);
1988 }
1989 fib(10)
1990 "#;
1991 match eval(src).unwrap() {
1992 Value::Number(n) => assert_eq!(n, 55.0),
1993 v => panic!("expected 55, got {v:?}"),
1994 }
1995 }
1996
1997 // ── GC integration tests ────────────────────────────────
1998
1999 #[test]
2000 fn test_gc_object_survives_collection() {
2001 let src = r#"
2002 var o = { x: 42 };
2003 o.x
2004 "#;
2005 match eval(src).unwrap() {
2006 Value::Number(n) => assert_eq!(n, 42.0),
2007 v => panic!("expected 42, got {v:?}"),
2008 }
2009 }
2010
2011 #[test]
2012 fn test_gc_many_objects() {
2013 // Allocate many objects to trigger GC threshold.
2014 let src = r#"
2015 var sum = 0;
2016 var i = 0;
2017 while (i < 100) {
2018 var o = { val: i };
2019 sum = sum + o.val;
2020 i = i + 1;
2021 }
2022 sum
2023 "#;
2024 match eval(src).unwrap() {
2025 Value::Number(n) => assert_eq!(n, 4950.0),
2026 v => panic!("expected 4950, got {v:?}"),
2027 }
2028 }
2029
2030 #[test]
2031 fn test_gc_reference_identity() {
2032 // With GC, object assignment is by reference.
2033 let mut gc: Gc<HeapObject> = Gc::new();
2034 let r = gc.alloc(HeapObject::Object(ObjectData::new()));
2035 let a = Value::Object(r);
2036 let b = a.clone();
2037 assert!(strict_eq(&a, &b)); // Same GcRef → strict equal.
2038 }
2039
2040 // ── Object model tests ──────────────────────────────────
2041
2042 #[test]
2043 fn test_prototype_chain_lookup() {
2044 // Property lookup walks the prototype chain.
2045 let src = r#"
2046 function Animal() {}
2047 var a = {};
2048 a.sound = "woof";
2049 a.sound
2050 "#;
2051 match eval(src).unwrap() {
2052 Value::String(s) => assert_eq!(s, "woof"),
2053 v => panic!("expected 'woof', got {v:?}"),
2054 }
2055 }
2056
2057 #[test]
2058 fn test_typeof_all_types() {
2059 // typeof returns correct strings for all types.
2060 let cases = [
2061 ("typeof undefined", "undefined"),
2062 ("typeof null", "object"),
2063 ("typeof true", "boolean"),
2064 ("typeof 42", "number"),
2065 ("typeof 'hello'", "string"),
2066 ("typeof {}", "object"),
2067 ("typeof function(){}", "function"),
2068 ];
2069 for (src, expected) in cases {
2070 match eval(src).unwrap() {
2071 Value::String(s) => assert_eq!(s, expected, "typeof failed for: {src}"),
2072 v => panic!("expected string for {src}, got {v:?}"),
2073 }
2074 }
2075 }
2076
2077 #[test]
2078 fn test_instanceof_basic() {
2079 let src = r#"
2080 function Foo() {}
2081 var f = {};
2082 f instanceof Foo
2083 "#;
2084 // Plain object without prototype link → false
2085 match eval(src).unwrap() {
2086 Value::Boolean(b) => assert!(!b),
2087 v => panic!("expected false, got {v:?}"),
2088 }
2089 }
2090
2091 #[test]
2092 fn test_in_operator() {
2093 let src = r#"
2094 var o = { x: 1, y: 2 };
2095 var r1 = "x" in o;
2096 var r2 = "z" in o;
2097 r1 === true && r2 === false
2098 "#;
2099 match eval(src).unwrap() {
2100 Value::Boolean(b) => assert!(b),
2101 v => panic!("expected true, got {v:?}"),
2102 }
2103 }
2104
2105 #[test]
2106 fn test_delete_property() {
2107 let src = r#"
2108 var o = { x: 1, y: 2 };
2109 delete o.x;
2110 typeof o.x === "undefined" && o.y === 2
2111 "#;
2112 match eval(src).unwrap() {
2113 Value::Boolean(b) => assert!(b),
2114 v => panic!("expected true, got {v:?}"),
2115 }
2116 }
2117
2118 #[test]
2119 fn test_delete_computed_property() {
2120 let src = r#"
2121 var o = { a: 10, b: 20 };
2122 var key = "a";
2123 delete o[key];
2124 typeof o.a === "undefined" && o.b === 20
2125 "#;
2126 match eval(src).unwrap() {
2127 Value::Boolean(b) => assert!(b),
2128 v => panic!("expected true, got {v:?}"),
2129 }
2130 }
2131
2132 #[test]
2133 fn test_delete_non_configurable() {
2134 // Array length is non-configurable, delete should return false.
2135 let mut gc: Gc<HeapObject> = Gc::new();
2136 let mut obj = ObjectData::new();
2137 obj.properties.insert(
2138 "x".to_string(),
2139 Property {
2140 value: Value::Number(1.0),
2141 writable: true,
2142 enumerable: true,
2143 configurable: false,
2144 },
2145 );
2146 let obj_ref = gc.alloc(HeapObject::Object(obj));
2147
2148 // Try to delete the non-configurable property.
2149 match gc.get_mut(obj_ref) {
2150 Some(HeapObject::Object(data)) => {
2151 let prop = data.properties.get("x").unwrap();
2152 assert!(!prop.configurable);
2153 // The property should still be there.
2154 assert!(data.properties.contains_key("x"));
2155 }
2156 _ => panic!("expected object"),
2157 }
2158 }
2159
2160 #[test]
2161 fn test_property_writable_flag() {
2162 // Setting a non-writable property should silently fail.
2163 let mut gc: Gc<HeapObject> = Gc::new();
2164 let mut obj = ObjectData::new();
2165 obj.properties.insert(
2166 "frozen".to_string(),
2167 Property {
2168 value: Value::Number(42.0),
2169 writable: false,
2170 enumerable: true,
2171 configurable: false,
2172 },
2173 );
2174 let obj_ref = gc.alloc(HeapObject::Object(obj));
2175
2176 // Verify the property value.
2177 match gc.get(obj_ref) {
2178 Some(HeapObject::Object(data)) => {
2179 assert_eq!(
2180 data.properties.get("frozen").unwrap().value.to_number(),
2181 42.0
2182 );
2183 }
2184 _ => panic!("expected object"),
2185 }
2186 }
2187
2188 #[test]
2189 fn test_for_in_basic() {
2190 let src = r#"
2191 var o = { a: 1, b: 2, c: 3 };
2192 var sum = 0;
2193 for (var key in o) {
2194 sum = sum + o[key];
2195 }
2196 sum
2197 "#;
2198 match eval(src).unwrap() {
2199 Value::Number(n) => assert_eq!(n, 6.0),
2200 v => panic!("expected 6, got {v:?}"),
2201 }
2202 }
2203
2204 #[test]
2205 fn test_for_in_collects_keys() {
2206 let src = r#"
2207 var o = { x: 10, y: 20 };
2208 var keys = "";
2209 for (var k in o) {
2210 keys = keys + k + ",";
2211 }
2212 keys
2213 "#;
2214 match eval(src).unwrap() {
2215 Value::String(s) => {
2216 // Both keys should appear (order may vary with HashMap).
2217 assert!(s.contains("x,"));
2218 assert!(s.contains("y,"));
2219 }
2220 v => panic!("expected string, got {v:?}"),
2221 }
2222 }
2223
2224 #[test]
2225 fn test_for_in_empty_object() {
2226 let src = r#"
2227 var o = {};
2228 var count = 0;
2229 for (var k in o) {
2230 count = count + 1;
2231 }
2232 count
2233 "#;
2234 match eval(src).unwrap() {
2235 Value::Number(n) => assert_eq!(n, 0.0),
2236 v => panic!("expected 0, got {v:?}"),
2237 }
2238 }
2239
2240 #[test]
2241 fn test_property_enumerable_flag() {
2242 // Array "length" is non-enumerable, should not appear in for-in.
2243 let src = r#"
2244 var arr = [10, 20, 30];
2245 var keys = "";
2246 for (var k in arr) {
2247 keys = keys + k + ",";
2248 }
2249 keys
2250 "#;
2251 match eval(src).unwrap() {
2252 Value::String(s) => {
2253 // "length" should NOT be in the keys (it's non-enumerable).
2254 assert!(!s.contains("length"));
2255 // Array indices should be present.
2256 assert!(s.contains("0,"));
2257 assert!(s.contains("1,"));
2258 assert!(s.contains("2,"));
2259 }
2260 v => panic!("expected string, got {v:?}"),
2261 }
2262 }
2263
2264 #[test]
2265 fn test_object_reference_semantics() {
2266 // Objects have reference semantics (shared via GcRef).
2267 let src = r#"
2268 var a = { x: 1 };
2269 var b = a;
2270 b.x = 42;
2271 a.x
2272 "#;
2273 match eval(src).unwrap() {
2274 Value::Number(n) => assert_eq!(n, 42.0),
2275 v => panic!("expected 42, got {v:?}"),
2276 }
2277 }
2278
2279 #[test]
2280 fn test_gc_enumerate_keys_order() {
2281 // Integer keys should come first, sorted numerically.
2282 let mut gc: Gc<HeapObject> = Gc::new();
2283 let mut obj = ObjectData::new();
2284 obj.properties
2285 .insert("b".to_string(), Property::data(Value::Number(2.0)));
2286 obj.properties
2287 .insert("2".to_string(), Property::data(Value::Number(3.0)));
2288 obj.properties
2289 .insert("a".to_string(), Property::data(Value::Number(1.0)));
2290 obj.properties
2291 .insert("0".to_string(), Property::data(Value::Number(0.0)));
2292 let obj_ref = gc.alloc(HeapObject::Object(obj));
2293
2294 let keys = gc_enumerate_keys(&gc, obj_ref);
2295 // Integer keys first (sorted), then string keys.
2296 let int_part: Vec<&str> = keys.iter().take(2).map(|s| s.as_str()).collect();
2297 assert_eq!(int_part, vec!["0", "2"]);
2298 // Remaining keys are string keys (order depends on HashMap iteration).
2299 let str_part: Vec<&str> = keys.iter().skip(2).map(|s| s.as_str()).collect();
2300 assert!(str_part.contains(&"a"));
2301 assert!(str_part.contains(&"b"));
2302 }
2303
2304 #[test]
2305 fn test_instanceof_with_gc() {
2306 // Direct test of gc_instanceof.
2307 let mut gc: Gc<HeapObject> = Gc::new();
2308
2309 // Create a constructor function with a .prototype object.
2310 let proto = gc.alloc(HeapObject::Object(ObjectData::new()));
2311 let ctor = gc.alloc(HeapObject::Function(FunctionData {
2312 name: "Foo".to_string(),
2313 kind: FunctionKind::Native(NativeFunc {
2314 callback: |_| Ok(Value::Undefined),
2315 }),
2316 prototype_obj: Some(proto),
2317 }));
2318
2319 // Create an object whose [[Prototype]] is the constructor's .prototype.
2320 let mut obj_data = ObjectData::new();
2321 obj_data.prototype = Some(proto);
2322 let obj = gc.alloc(HeapObject::Object(obj_data));
2323
2324 assert!(gc_instanceof(&gc, obj, ctor));
2325
2326 // An unrelated object should not match.
2327 let other = gc.alloc(HeapObject::Object(ObjectData::new()));
2328 assert!(!gc_instanceof(&gc, other, ctor));
2329 }
2330}