we (web engine): Experimental web browser project to understand the limits of Claude
at object-model 2330 lines 90 kB view raw
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}