we (web engine): Experimental web browser project to understand the limits of Claude
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement JS virtual machine: register-based instruction dispatch

Add the core VM execution engine for the JavaScript bytecode compiler:

- Value type with all JS primitives (Number, String, Boolean, Null,
Undefined, Object, Function)
- Register file with call frame base offsets for nested calls
- Instruction dispatch loop executing all ~50 bytecode opcodes
- Arithmetic with type coercion (ToNumber), string concatenation
- Comparison with abstract/strict equality (ECMA-262 §7.2.14/15)
- Bitwise operations with ToInt32/ToUint32 conversion
- Control flow: jumps, conditional branches, loops
- Function calls: push/pop call frames, argument passing, returns
- Native function support (fn(&[Value]) -> Result<Value, RuntimeError>)
- Object property get/set (plain objects with prototype chain)
- Exception handling with throw and handler stack unwinding
- typeof, instanceof, in, void, delete operators
- Global variable storage via HashMap
- Stack overflow protection (512 frame limit)

Also store function declarations as globals so recursive calls work,
update evaluate() to return completion values, and fix test262 harness
for new signature.

40+ unit tests and end-to-end tests including fibonacci, factorial,
loops, objects, string concat, and type coercion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+1606 -7
+6
crates/js/src/compiler.rs
··· 416 416 417 417 let reg = fc.define_local(&name); 418 418 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx); 419 + 420 + // Also store as global so inner/recursive calls via LoadGlobal can find it. 421 + if !name.is_empty() { 422 + let name_idx = fc.builder.add_name(&name); 423 + fc.builder.emit_store_global(name_idx, reg); 424 + } 419 425 Ok(()) 420 426 } 421 427
+11 -5
crates/js/src/lib.rs
··· 5 5 pub mod compiler; 6 6 pub mod lexer; 7 7 pub mod parser; 8 + pub mod vm; 8 9 9 10 use std::fmt; 10 11 ··· 29 30 } 30 31 } 31 32 32 - /// Evaluate a JavaScript source string and return the completion value. 33 + /// Evaluate a JavaScript source string and return the completion value as a string. 33 34 /// 34 - /// This is a stub that always returns `NotImplemented`. The real 35 - /// implementation will lex, parse, compile to bytecode, and execute. 36 - pub fn evaluate(_source: &str) -> Result<(), JsError> { 37 - Err(JsError::NotImplemented) 35 + /// Parses the source, compiles to bytecode, and executes in the VM. 36 + pub fn evaluate(source: &str) -> Result<String, JsError> { 37 + let program = parser::Parser::parse(source).map_err(|e| JsError::SyntaxError(e.to_string()))?; 38 + let func = compiler::compile(&program)?; 39 + let mut engine = vm::Vm::new(); 40 + let result = engine 41 + .execute(&func) 42 + .map_err(|e| JsError::RuntimeError(e.to_string()))?; 43 + Ok(result.to_string()) 38 44 }
+1587
crates/js/src/vm.rs
··· 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. 5 + 6 + use crate::bytecode::{Constant, Function, Op, Reg}; 7 + use std::collections::HashMap; 8 + use std::fmt; 9 + 10 + // ── JS Value ────────────────────────────────────────────────── 11 + 12 + /// A JavaScript runtime value. 13 + #[derive(Clone)] 14 + pub enum Value { 15 + Undefined, 16 + Null, 17 + Boolean(bool), 18 + Number(f64), 19 + String(String), 20 + Object(Object), 21 + Function(FunctionValue), 22 + } 23 + 24 + impl fmt::Debug for Value { 25 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 + match self { 27 + Value::Undefined => write!(f, "undefined"), 28 + Value::Null => write!(f, "null"), 29 + Value::Boolean(b) => write!(f, "{b}"), 30 + Value::Number(n) => write!(f, "{n}"), 31 + Value::String(s) => write!(f, "\"{}\"", s), 32 + Value::Object(_) => write!(f, "[object Object]"), 33 + Value::Function(fv) => write!(f, "function {}()", fv.name), 34 + } 35 + } 36 + } 37 + 38 + impl fmt::Display for Value { 39 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 + match self { 41 + Value::Undefined => write!(f, "undefined"), 42 + Value::Null => write!(f, "null"), 43 + Value::Boolean(b) => write!(f, "{b}"), 44 + Value::Number(n) => format_number(*n, f), 45 + Value::String(s) => write!(f, "{s}"), 46 + Value::Object(_) => write!(f, "[object Object]"), 47 + Value::Function(fv) => { 48 + write!(f, "function {}() {{ [native code] }}", fv.name) 49 + } 50 + } 51 + } 52 + } 53 + 54 + /// Format a number following JS conventions (no trailing .0 for integers). 55 + fn format_number(n: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 + if n.is_nan() { 57 + write!(f, "NaN") 58 + } else if n.is_infinite() { 59 + if n.is_sign_positive() { 60 + write!(f, "Infinity") 61 + } else { 62 + write!(f, "-Infinity") 63 + } 64 + } else if n == 0.0 { 65 + write!(f, "0") 66 + } else if n.fract() == 0.0 && n.abs() < 1e20 { 67 + write!(f, "{}", n as i64) 68 + } else { 69 + write!(f, "{n}") 70 + } 71 + } 72 + 73 + impl Value { 74 + /// Abstract `ToBoolean` (ECMA-262 §7.1.2). 75 + pub fn to_boolean(&self) -> bool { 76 + match self { 77 + Value::Undefined | Value::Null => false, 78 + Value::Boolean(b) => *b, 79 + Value::Number(n) => *n != 0.0 && !n.is_nan(), 80 + Value::String(s) => !s.is_empty(), 81 + Value::Object(_) | Value::Function(_) => true, 82 + } 83 + } 84 + 85 + /// Abstract `ToNumber` (ECMA-262 §7.1.3). 86 + pub fn to_number(&self) -> f64 { 87 + match self { 88 + Value::Undefined => f64::NAN, 89 + Value::Null => 0.0, 90 + Value::Boolean(true) => 1.0, 91 + Value::Boolean(false) => 0.0, 92 + Value::Number(n) => *n, 93 + Value::String(s) => { 94 + let s = s.trim(); 95 + if s.is_empty() { 96 + 0.0 97 + } else if s == "Infinity" || s == "+Infinity" { 98 + f64::INFINITY 99 + } else if s == "-Infinity" { 100 + f64::NEG_INFINITY 101 + } else { 102 + s.parse::<f64>().unwrap_or(f64::NAN) 103 + } 104 + } 105 + Value::Object(_) | Value::Function(_) => f64::NAN, 106 + } 107 + } 108 + 109 + /// Abstract `ToString` (ECMA-262 §7.1.12). 110 + pub fn to_js_string(&self) -> String { 111 + match self { 112 + Value::Undefined => "undefined".to_string(), 113 + Value::Null => "null".to_string(), 114 + Value::Boolean(true) => "true".to_string(), 115 + Value::Boolean(false) => "false".to_string(), 116 + Value::Number(n) => js_number_to_string(*n), 117 + Value::String(s) => s.clone(), 118 + Value::Object(_) => "[object Object]".to_string(), 119 + Value::Function(fv) => format!("function {}() {{ [native code] }}", fv.name), 120 + } 121 + } 122 + 123 + /// `typeof` operator result. 124 + pub fn type_of(&self) -> &'static str { 125 + match self { 126 + Value::Undefined => "undefined", 127 + Value::Null => "object", // yes, this is the spec 128 + Value::Boolean(_) => "boolean", 129 + Value::Number(_) => "number", 130 + Value::String(_) => "string", 131 + Value::Object(_) => "object", 132 + Value::Function(_) => "function", 133 + } 134 + } 135 + 136 + /// Is this value nullish (null or undefined)? 137 + pub fn is_nullish(&self) -> bool { 138 + matches!(self, Value::Undefined | Value::Null) 139 + } 140 + } 141 + 142 + /// Format a number as JS would. 143 + fn js_number_to_string(n: f64) -> String { 144 + if n.is_nan() { 145 + "NaN".to_string() 146 + } else if n.is_infinite() { 147 + if n.is_sign_positive() { 148 + "Infinity".to_string() 149 + } else { 150 + "-Infinity".to_string() 151 + } 152 + } else if n == 0.0 { 153 + "0".to_string() 154 + } else if n.fract() == 0.0 && n.abs() < 1e20 { 155 + format!("{}", n as i64) 156 + } else { 157 + format!("{n}") 158 + } 159 + } 160 + 161 + // ── Object ──────────────────────────────────────────────────── 162 + 163 + /// A JS plain object (properties stored as a HashMap). 164 + #[derive(Clone, Debug)] 165 + pub struct Object { 166 + pub properties: HashMap<String, Value>, 167 + pub prototype: Option<Box<Object>>, 168 + } 169 + 170 + impl Object { 171 + pub fn new() -> Self { 172 + Self { 173 + properties: HashMap::new(), 174 + prototype: None, 175 + } 176 + } 177 + 178 + pub fn get(&self, key: &str) -> Value { 179 + if let Some(val) = self.properties.get(key) { 180 + val.clone() 181 + } else if let Some(proto) = &self.prototype { 182 + proto.get(key) 183 + } else { 184 + Value::Undefined 185 + } 186 + } 187 + 188 + pub fn set(&mut self, key: String, value: Value) { 189 + self.properties.insert(key, value); 190 + } 191 + 192 + pub fn delete(&mut self, key: &str) -> bool { 193 + self.properties.remove(key).is_some() 194 + } 195 + 196 + pub fn has(&self, key: &str) -> bool { 197 + self.properties.contains_key(key) || self.prototype.as_ref().is_some_and(|p| p.has(key)) 198 + } 199 + } 200 + 201 + impl Default for Object { 202 + fn default() -> Self { 203 + Self::new() 204 + } 205 + } 206 + 207 + // ── Function value ──────────────────────────────────────────── 208 + 209 + /// A runtime function value: either bytecode or native. 210 + #[derive(Clone)] 211 + pub struct FunctionValue { 212 + pub name: String, 213 + pub kind: FunctionKind, 214 + } 215 + 216 + #[derive(Clone)] 217 + pub enum FunctionKind { 218 + /// Bytecode function. 219 + Bytecode(BytecodeFunc), 220 + /// Native (Rust) function. 221 + Native(NativeFunc), 222 + } 223 + 224 + #[derive(Clone)] 225 + pub struct BytecodeFunc { 226 + pub func: Function, 227 + } 228 + 229 + /// A native function callable from JS. 230 + #[derive(Clone)] 231 + pub struct NativeFunc { 232 + pub callback: fn(&[Value]) -> Result<Value, RuntimeError>, 233 + } 234 + 235 + // ── Runtime errors ──────────────────────────────────────────── 236 + 237 + /// JavaScript runtime error types. 238 + #[derive(Debug, Clone)] 239 + pub struct RuntimeError { 240 + pub kind: ErrorKind, 241 + pub message: String, 242 + } 243 + 244 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 245 + pub enum ErrorKind { 246 + TypeError, 247 + ReferenceError, 248 + RangeError, 249 + SyntaxError, 250 + Error, 251 + } 252 + 253 + impl fmt::Display for RuntimeError { 254 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 255 + let name = match self.kind { 256 + ErrorKind::TypeError => "TypeError", 257 + ErrorKind::ReferenceError => "ReferenceError", 258 + ErrorKind::RangeError => "RangeError", 259 + ErrorKind::SyntaxError => "SyntaxError", 260 + ErrorKind::Error => "Error", 261 + }; 262 + write!(f, "{name}: {}", self.message) 263 + } 264 + } 265 + 266 + impl RuntimeError { 267 + pub fn type_error(msg: impl Into<String>) -> Self { 268 + Self { 269 + kind: ErrorKind::TypeError, 270 + message: msg.into(), 271 + } 272 + } 273 + 274 + pub fn reference_error(msg: impl Into<String>) -> Self { 275 + Self { 276 + kind: ErrorKind::ReferenceError, 277 + message: msg.into(), 278 + } 279 + } 280 + 281 + pub fn range_error(msg: impl Into<String>) -> Self { 282 + Self { 283 + kind: ErrorKind::RangeError, 284 + message: msg.into(), 285 + } 286 + } 287 + 288 + /// Convert to a JS Value (an error object). 289 + pub fn to_value(&self) -> Value { 290 + let mut obj = Object::new(); 291 + obj.set("message".to_string(), Value::String(self.message.clone())); 292 + let name = match self.kind { 293 + ErrorKind::TypeError => "TypeError", 294 + ErrorKind::ReferenceError => "ReferenceError", 295 + ErrorKind::RangeError => "RangeError", 296 + ErrorKind::SyntaxError => "SyntaxError", 297 + ErrorKind::Error => "Error", 298 + }; 299 + obj.set("name".to_string(), Value::String(name.to_string())); 300 + Value::Object(obj) 301 + } 302 + } 303 + 304 + // ── Call frame ──────────────────────────────────────────────── 305 + 306 + /// A single call frame on the VM's call stack. 307 + struct CallFrame { 308 + /// The function being executed. 309 + func: Function, 310 + /// Instruction pointer (byte offset into func.code). 311 + ip: usize, 312 + /// Base register index in the VM's register file. 313 + base: usize, 314 + /// Register to write the return value into (absolute index in register file). 315 + return_reg: usize, 316 + /// Exception handler stack for this frame. 317 + exception_handlers: Vec<ExceptionHandler>, 318 + } 319 + 320 + /// An exception handler entry (for try/catch). 321 + struct ExceptionHandler { 322 + /// IP to jump to on exception (the catch block start). 323 + catch_ip: usize, 324 + /// Register to store the caught exception value. 325 + catch_reg: Reg, 326 + } 327 + 328 + // ── VM ─────────────────────────────────────────────────────── 329 + 330 + /// The JavaScript virtual machine. 331 + pub struct Vm { 332 + /// Register file (flat array shared across frames via base offsets). 333 + registers: Vec<Value>, 334 + /// Call stack. 335 + frames: Vec<CallFrame>, 336 + /// Global variables. 337 + globals: HashMap<String, Value>, 338 + } 339 + 340 + /// Maximum register file size. 341 + const MAX_REGISTERS: usize = 4096; 342 + /// Maximum call depth. 343 + const MAX_CALL_DEPTH: usize = 512; 344 + 345 + impl Vm { 346 + pub fn new() -> Self { 347 + Self { 348 + registers: vec![Value::Undefined; 256], 349 + frames: Vec::new(), 350 + globals: HashMap::new(), 351 + } 352 + } 353 + 354 + /// Execute a compiled top-level function and return the completion value. 355 + pub fn execute(&mut self, func: &Function) -> Result<Value, RuntimeError> { 356 + let reg_count = func.register_count as usize; 357 + self.ensure_registers(reg_count); 358 + 359 + self.frames.push(CallFrame { 360 + func: func.clone(), 361 + ip: 0, 362 + base: 0, 363 + return_reg: 0, 364 + exception_handlers: Vec::new(), 365 + }); 366 + 367 + self.run() 368 + } 369 + 370 + /// Ensure the register file has at least `needed` slots. 371 + fn ensure_registers(&mut self, needed: usize) { 372 + if needed > self.registers.len() { 373 + if needed > MAX_REGISTERS { 374 + return; 375 + } 376 + self.registers.resize(needed, Value::Undefined); 377 + } 378 + } 379 + 380 + /// Read a u8 from the current frame's bytecode and advance IP. 381 + #[inline] 382 + fn read_u8(frame: &mut CallFrame) -> u8 { 383 + let b = frame.func.code[frame.ip]; 384 + frame.ip += 1; 385 + b 386 + } 387 + 388 + /// Read a u16 (little-endian) from the current frame's bytecode and advance IP. 389 + #[inline] 390 + fn read_u16(frame: &mut CallFrame) -> u16 { 391 + let lo = frame.func.code[frame.ip]; 392 + let hi = frame.func.code[frame.ip + 1]; 393 + frame.ip += 2; 394 + u16::from_le_bytes([lo, hi]) 395 + } 396 + 397 + /// Read an i32 (little-endian) from the current frame's bytecode and advance IP. 398 + #[inline] 399 + fn read_i32(frame: &mut CallFrame) -> i32 { 400 + let bytes = [ 401 + frame.func.code[frame.ip], 402 + frame.func.code[frame.ip + 1], 403 + frame.func.code[frame.ip + 2], 404 + frame.func.code[frame.ip + 3], 405 + ]; 406 + frame.ip += 4; 407 + i32::from_le_bytes(bytes) 408 + } 409 + 410 + /// Main dispatch loop. 411 + fn run(&mut self) -> Result<Value, RuntimeError> { 412 + loop { 413 + let fi = self.frames.len() - 1; 414 + 415 + // Check if we've reached the end of bytecode. 416 + if self.frames[fi].ip >= self.frames[fi].func.code.len() { 417 + if self.frames.len() == 1 { 418 + self.frames.pop(); 419 + return Ok(Value::Undefined); 420 + } 421 + let old = self.frames.pop().unwrap(); 422 + self.registers[old.return_reg] = Value::Undefined; 423 + continue; 424 + } 425 + 426 + let opcode_byte = self.frames[fi].func.code[self.frames[fi].ip]; 427 + self.frames[fi].ip += 1; 428 + 429 + let Some(op) = Op::from_byte(opcode_byte) else { 430 + return Err(RuntimeError { 431 + kind: ErrorKind::Error, 432 + message: format!("unknown opcode: 0x{opcode_byte:02X}"), 433 + }); 434 + }; 435 + 436 + match op { 437 + // ── Register loads ────────────────────────────── 438 + Op::LoadConst => { 439 + let dst = Self::read_u8(&mut self.frames[fi]); 440 + let idx = Self::read_u16(&mut self.frames[fi]) as usize; 441 + let base = self.frames[fi].base; 442 + let val = match &self.frames[fi].func.constants[idx] { 443 + Constant::Number(n) => Value::Number(*n), 444 + Constant::String(s) => Value::String(s.clone()), 445 + }; 446 + self.registers[base + dst as usize] = val; 447 + } 448 + Op::LoadNull => { 449 + let dst = Self::read_u8(&mut self.frames[fi]); 450 + let base = self.frames[fi].base; 451 + self.registers[base + dst as usize] = Value::Null; 452 + } 453 + Op::LoadUndefined => { 454 + let dst = Self::read_u8(&mut self.frames[fi]); 455 + let base = self.frames[fi].base; 456 + self.registers[base + dst as usize] = Value::Undefined; 457 + } 458 + Op::LoadTrue => { 459 + let dst = Self::read_u8(&mut self.frames[fi]); 460 + let base = self.frames[fi].base; 461 + self.registers[base + dst as usize] = Value::Boolean(true); 462 + } 463 + Op::LoadFalse => { 464 + let dst = Self::read_u8(&mut self.frames[fi]); 465 + let base = self.frames[fi].base; 466 + self.registers[base + dst as usize] = Value::Boolean(false); 467 + } 468 + Op::LoadInt8 => { 469 + let dst = Self::read_u8(&mut self.frames[fi]); 470 + let val = Self::read_u8(&mut self.frames[fi]) as i8; 471 + let base = self.frames[fi].base; 472 + self.registers[base + dst as usize] = Value::Number(val as f64); 473 + } 474 + Op::Move => { 475 + let dst = Self::read_u8(&mut self.frames[fi]); 476 + let src = Self::read_u8(&mut self.frames[fi]); 477 + let base = self.frames[fi].base; 478 + let val = self.registers[base + src as usize].clone(); 479 + self.registers[base + dst as usize] = val; 480 + } 481 + 482 + // ── Global access ────────────────────────────── 483 + Op::LoadGlobal => { 484 + let dst = Self::read_u8(&mut self.frames[fi]); 485 + let name_idx = Self::read_u16(&mut self.frames[fi]) as usize; 486 + let base = self.frames[fi].base; 487 + let name = &self.frames[fi].func.names[name_idx]; 488 + let val = self.globals.get(name).cloned().unwrap_or(Value::Undefined); 489 + self.registers[base + dst as usize] = val; 490 + } 491 + Op::StoreGlobal => { 492 + let name_idx = Self::read_u16(&mut self.frames[fi]) as usize; 493 + let src = Self::read_u8(&mut self.frames[fi]); 494 + let base = self.frames[fi].base; 495 + let name = self.frames[fi].func.names[name_idx].clone(); 496 + let val = self.registers[base + src as usize].clone(); 497 + self.globals.insert(name, val); 498 + } 499 + 500 + // ── Arithmetic ───────────────────────────────── 501 + Op::Add => { 502 + let dst = Self::read_u8(&mut self.frames[fi]); 503 + let lhs_r = Self::read_u8(&mut self.frames[fi]); 504 + let rhs_r = Self::read_u8(&mut self.frames[fi]); 505 + let base = self.frames[fi].base; 506 + let result = add_values( 507 + &self.registers[base + lhs_r as usize], 508 + &self.registers[base + rhs_r as usize], 509 + ); 510 + self.registers[base + dst as usize] = result; 511 + } 512 + Op::Sub => { 513 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 514 + let result = self.registers[base + lhs_r].to_number() 515 + - self.registers[base + rhs_r].to_number(); 516 + self.registers[base + dst] = Value::Number(result); 517 + } 518 + Op::Mul => { 519 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 520 + let result = self.registers[base + lhs_r].to_number() 521 + * self.registers[base + rhs_r].to_number(); 522 + self.registers[base + dst] = Value::Number(result); 523 + } 524 + Op::Div => { 525 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 526 + let result = self.registers[base + lhs_r].to_number() 527 + / self.registers[base + rhs_r].to_number(); 528 + self.registers[base + dst] = Value::Number(result); 529 + } 530 + Op::Rem => { 531 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 532 + let result = self.registers[base + lhs_r].to_number() 533 + % self.registers[base + rhs_r].to_number(); 534 + self.registers[base + dst] = Value::Number(result); 535 + } 536 + Op::Exp => { 537 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 538 + let result = self.registers[base + lhs_r] 539 + .to_number() 540 + .powf(self.registers[base + rhs_r].to_number()); 541 + self.registers[base + dst] = Value::Number(result); 542 + } 543 + Op::Neg => { 544 + let dst = Self::read_u8(&mut self.frames[fi]); 545 + let src = Self::read_u8(&mut self.frames[fi]); 546 + let base = self.frames[fi].base; 547 + let result = -self.registers[base + src as usize].to_number(); 548 + self.registers[base + dst as usize] = Value::Number(result); 549 + } 550 + 551 + // ── Bitwise ──────────────────────────────────── 552 + Op::BitAnd => { 553 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 554 + let a = to_int32(&self.registers[base + lhs_r]); 555 + let b = to_int32(&self.registers[base + rhs_r]); 556 + self.registers[base + dst] = Value::Number((a & b) as f64); 557 + } 558 + Op::BitOr => { 559 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 560 + let a = to_int32(&self.registers[base + lhs_r]); 561 + let b = to_int32(&self.registers[base + rhs_r]); 562 + self.registers[base + dst] = Value::Number((a | b) as f64); 563 + } 564 + Op::BitXor => { 565 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 566 + let a = to_int32(&self.registers[base + lhs_r]); 567 + let b = to_int32(&self.registers[base + rhs_r]); 568 + self.registers[base + dst] = Value::Number((a ^ b) as f64); 569 + } 570 + Op::ShiftLeft => { 571 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 572 + let a = to_int32(&self.registers[base + lhs_r]); 573 + let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F; 574 + self.registers[base + dst] = Value::Number((a << b) as f64); 575 + } 576 + Op::ShiftRight => { 577 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 578 + let a = to_int32(&self.registers[base + lhs_r]); 579 + let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F; 580 + self.registers[base + dst] = Value::Number((a >> b) as f64); 581 + } 582 + Op::UShiftRight => { 583 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 584 + let a = to_uint32(&self.registers[base + lhs_r]); 585 + let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F; 586 + self.registers[base + dst] = Value::Number((a >> b) as f64); 587 + } 588 + Op::BitNot => { 589 + let dst = Self::read_u8(&mut self.frames[fi]); 590 + let src = Self::read_u8(&mut self.frames[fi]); 591 + let base = self.frames[fi].base; 592 + let result = !to_int32(&self.registers[base + src as usize]); 593 + self.registers[base + dst as usize] = Value::Number(result as f64); 594 + } 595 + 596 + // ── Comparison ───────────────────────────────── 597 + Op::Eq => { 598 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 599 + let result = 600 + abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]); 601 + self.registers[base + dst] = Value::Boolean(result); 602 + } 603 + Op::StrictEq => { 604 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 605 + let result = 606 + strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]); 607 + self.registers[base + dst] = Value::Boolean(result); 608 + } 609 + Op::NotEq => { 610 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 611 + let result = 612 + !abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]); 613 + self.registers[base + dst] = Value::Boolean(result); 614 + } 615 + Op::StrictNotEq => { 616 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 617 + let result = 618 + !strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]); 619 + self.registers[base + dst] = Value::Boolean(result); 620 + } 621 + Op::LessThan => { 622 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 623 + let result = abstract_relational( 624 + &self.registers[base + lhs_r], 625 + &self.registers[base + rhs_r], 626 + |ord| ord == std::cmp::Ordering::Less, 627 + ); 628 + self.registers[base + dst] = Value::Boolean(result); 629 + } 630 + Op::LessEq => { 631 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 632 + let result = abstract_relational( 633 + &self.registers[base + lhs_r], 634 + &self.registers[base + rhs_r], 635 + |ord| ord != std::cmp::Ordering::Greater, 636 + ); 637 + self.registers[base + dst] = Value::Boolean(result); 638 + } 639 + Op::GreaterThan => { 640 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 641 + let result = abstract_relational( 642 + &self.registers[base + lhs_r], 643 + &self.registers[base + rhs_r], 644 + |ord| ord == std::cmp::Ordering::Greater, 645 + ); 646 + self.registers[base + dst] = Value::Boolean(result); 647 + } 648 + Op::GreaterEq => { 649 + let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); 650 + let result = abstract_relational( 651 + &self.registers[base + lhs_r], 652 + &self.registers[base + rhs_r], 653 + |ord| ord != std::cmp::Ordering::Less, 654 + ); 655 + self.registers[base + dst] = Value::Boolean(result); 656 + } 657 + 658 + // ── Logical / unary ──────────────────────────── 659 + Op::LogicalNot => { 660 + let dst = Self::read_u8(&mut self.frames[fi]); 661 + let src = Self::read_u8(&mut self.frames[fi]); 662 + let base = self.frames[fi].base; 663 + let result = !self.registers[base + src as usize].to_boolean(); 664 + self.registers[base + dst as usize] = Value::Boolean(result); 665 + } 666 + Op::TypeOf => { 667 + let dst = Self::read_u8(&mut self.frames[fi]); 668 + let src = Self::read_u8(&mut self.frames[fi]); 669 + let base = self.frames[fi].base; 670 + let t = self.registers[base + src as usize].type_of(); 671 + self.registers[base + dst as usize] = Value::String(t.to_string()); 672 + } 673 + Op::InstanceOf => { 674 + let dst = Self::read_u8(&mut self.frames[fi]); 675 + let _lhs_r = Self::read_u8(&mut self.frames[fi]); 676 + let _rhs_r = Self::read_u8(&mut self.frames[fi]); 677 + let base = self.frames[fi].base; 678 + // Simplified: always false (needs constructor/prototype support). 679 + self.registers[base + dst as usize] = Value::Boolean(false); 680 + } 681 + Op::In => { 682 + let dst = Self::read_u8(&mut self.frames[fi]); 683 + let key_r = Self::read_u8(&mut self.frames[fi]); 684 + let obj_r = Self::read_u8(&mut self.frames[fi]); 685 + let base = self.frames[fi].base; 686 + let key = self.registers[base + key_r as usize].to_js_string(); 687 + let result = match &self.registers[base + obj_r as usize] { 688 + Value::Object(obj) => Value::Boolean(obj.has(&key)), 689 + _ => { 690 + return Err(RuntimeError::type_error( 691 + "Cannot use 'in' operator to search for property in non-object", 692 + )); 693 + } 694 + }; 695 + self.registers[base + dst as usize] = result; 696 + } 697 + Op::Void => { 698 + let dst = Self::read_u8(&mut self.frames[fi]); 699 + let _src = Self::read_u8(&mut self.frames[fi]); 700 + let base = self.frames[fi].base; 701 + self.registers[base + dst as usize] = Value::Undefined; 702 + } 703 + 704 + // ── Control flow ─────────────────────────────── 705 + Op::Jump => { 706 + let offset = Self::read_i32(&mut self.frames[fi]); 707 + self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize; 708 + } 709 + Op::JumpIfTrue => { 710 + let reg = Self::read_u8(&mut self.frames[fi]); 711 + let offset = Self::read_i32(&mut self.frames[fi]); 712 + let base = self.frames[fi].base; 713 + if self.registers[base + reg as usize].to_boolean() { 714 + self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize; 715 + } 716 + } 717 + Op::JumpIfFalse => { 718 + let reg = Self::read_u8(&mut self.frames[fi]); 719 + let offset = Self::read_i32(&mut self.frames[fi]); 720 + let base = self.frames[fi].base; 721 + if !self.registers[base + reg as usize].to_boolean() { 722 + self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize; 723 + } 724 + } 725 + Op::JumpIfNullish => { 726 + let reg = Self::read_u8(&mut self.frames[fi]); 727 + let offset = Self::read_i32(&mut self.frames[fi]); 728 + let base = self.frames[fi].base; 729 + if self.registers[base + reg as usize].is_nullish() { 730 + self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize; 731 + } 732 + } 733 + 734 + // ── Functions / calls ────────────────────────── 735 + Op::Call => { 736 + let dst = Self::read_u8(&mut self.frames[fi]); 737 + let func_r = Self::read_u8(&mut self.frames[fi]); 738 + let args_start = Self::read_u8(&mut self.frames[fi]); 739 + let arg_count = Self::read_u8(&mut self.frames[fi]); 740 + let base = self.frames[fi].base; 741 + let func_val = self.registers[base + func_r as usize].clone(); 742 + 743 + // Collect arguments. 744 + let mut args = Vec::with_capacity(arg_count as usize); 745 + for i in 0..arg_count { 746 + args.push(self.registers[base + (args_start + i) as usize].clone()); 747 + } 748 + 749 + match func_val { 750 + Value::Function(fv) => match &fv.kind { 751 + FunctionKind::Native(native) => match (native.callback)(&args) { 752 + Ok(val) => { 753 + self.registers[base + dst as usize] = val; 754 + } 755 + Err(err) => { 756 + if !self.handle_exception(err.to_value()) { 757 + return Err(err); 758 + } 759 + } 760 + }, 761 + FunctionKind::Bytecode(bc) => { 762 + if self.frames.len() >= MAX_CALL_DEPTH { 763 + let err = RuntimeError::range_error( 764 + "Maximum call stack size exceeded", 765 + ); 766 + if !self.handle_exception(err.to_value()) { 767 + return Err(err); 768 + } 769 + continue; 770 + } 771 + 772 + let callee_func = bc.func.clone(); 773 + let callee_base = 774 + base + self.frames[fi].func.register_count as usize; 775 + let callee_regs = callee_func.register_count as usize; 776 + self.ensure_registers(callee_base + callee_regs); 777 + 778 + // Copy arguments into callee's registers. 779 + for i in 0..callee_func.param_count.min(arg_count) { 780 + self.registers[callee_base + i as usize] = 781 + args[i as usize].clone(); 782 + } 783 + // Fill remaining params with undefined. 784 + for i in arg_count..callee_func.param_count { 785 + self.registers[callee_base + i as usize] = Value::Undefined; 786 + } 787 + 788 + self.frames.push(CallFrame { 789 + func: callee_func, 790 + ip: 0, 791 + base: callee_base, 792 + return_reg: base + dst as usize, 793 + exception_handlers: Vec::new(), 794 + }); 795 + } 796 + }, 797 + _ => { 798 + let err = RuntimeError::type_error(format!( 799 + "{} is not a function", 800 + func_val.to_js_string() 801 + )); 802 + if !self.handle_exception(err.to_value()) { 803 + return Err(err); 804 + } 805 + } 806 + } 807 + } 808 + Op::Return => { 809 + let reg = Self::read_u8(&mut self.frames[fi]); 810 + let base = self.frames[fi].base; 811 + let val = self.registers[base + reg as usize].clone(); 812 + 813 + if self.frames.len() == 1 { 814 + self.frames.pop(); 815 + return Ok(val); 816 + } 817 + 818 + let old = self.frames.pop().unwrap(); 819 + self.registers[old.return_reg] = val; 820 + } 821 + Op::Throw => { 822 + let reg = Self::read_u8(&mut self.frames[fi]); 823 + let base = self.frames[fi].base; 824 + let val = self.registers[base + reg as usize].clone(); 825 + 826 + if !self.handle_exception(val) { 827 + let msg = self.registers[base + reg as usize].to_js_string(); 828 + return Err(RuntimeError { 829 + kind: ErrorKind::Error, 830 + message: msg, 831 + }); 832 + } 833 + } 834 + Op::CreateClosure => { 835 + let dst = Self::read_u8(&mut self.frames[fi]); 836 + let func_idx = Self::read_u16(&mut self.frames[fi]) as usize; 837 + let base = self.frames[fi].base; 838 + let inner_func = self.frames[fi].func.functions[func_idx].clone(); 839 + let name = inner_func.name.clone(); 840 + self.registers[base + dst as usize] = Value::Function(FunctionValue { 841 + name, 842 + kind: FunctionKind::Bytecode(BytecodeFunc { func: inner_func }), 843 + }); 844 + } 845 + 846 + // ── Object / property ────────────────────────── 847 + Op::GetProperty => { 848 + let dst = Self::read_u8(&mut self.frames[fi]); 849 + let obj_r = Self::read_u8(&mut self.frames[fi]); 850 + let key_r = Self::read_u8(&mut self.frames[fi]); 851 + let base = self.frames[fi].base; 852 + let key = self.registers[base + key_r as usize].to_js_string(); 853 + let val = match &self.registers[base + obj_r as usize] { 854 + Value::Object(obj) => obj.get(&key), 855 + Value::String(s) => string_get_property(s, &key), 856 + _ => Value::Undefined, 857 + }; 858 + self.registers[base + dst as usize] = val; 859 + } 860 + Op::SetProperty => { 861 + let obj_r = Self::read_u8(&mut self.frames[fi]); 862 + let key_r = Self::read_u8(&mut self.frames[fi]); 863 + let val_r = Self::read_u8(&mut self.frames[fi]); 864 + let base = self.frames[fi].base; 865 + let key = self.registers[base + key_r as usize].to_js_string(); 866 + let val = self.registers[base + val_r as usize].clone(); 867 + if let Value::Object(ref mut obj) = self.registers[base + obj_r as usize] { 868 + obj.set(key, val); 869 + } 870 + } 871 + Op::CreateObject => { 872 + let dst = Self::read_u8(&mut self.frames[fi]); 873 + let base = self.frames[fi].base; 874 + self.registers[base + dst as usize] = Value::Object(Object::new()); 875 + } 876 + Op::CreateArray => { 877 + let dst = Self::read_u8(&mut self.frames[fi]); 878 + let base = self.frames[fi].base; 879 + let mut obj = Object::new(); 880 + obj.set("length".to_string(), Value::Number(0.0)); 881 + self.registers[base + dst as usize] = Value::Object(obj); 882 + } 883 + Op::GetPropertyByName => { 884 + let dst = Self::read_u8(&mut self.frames[fi]); 885 + let obj_r = Self::read_u8(&mut self.frames[fi]); 886 + let name_idx = Self::read_u16(&mut self.frames[fi]) as usize; 887 + let base = self.frames[fi].base; 888 + let key = self.frames[fi].func.names[name_idx].clone(); 889 + let val = match &self.registers[base + obj_r as usize] { 890 + Value::Object(obj) => obj.get(&key), 891 + Value::String(s) => string_get_property(s, &key), 892 + _ => Value::Undefined, 893 + }; 894 + self.registers[base + dst as usize] = val; 895 + } 896 + Op::SetPropertyByName => { 897 + let obj_r = Self::read_u8(&mut self.frames[fi]); 898 + let name_idx = Self::read_u16(&mut self.frames[fi]) as usize; 899 + let val_r = Self::read_u8(&mut self.frames[fi]); 900 + let base = self.frames[fi].base; 901 + let key = self.frames[fi].func.names[name_idx].clone(); 902 + let val = self.registers[base + val_r as usize].clone(); 903 + if let Value::Object(ref mut obj) = self.registers[base + obj_r as usize] { 904 + obj.set(key, val); 905 + } 906 + } 907 + 908 + // ── Misc ─────────────────────────────────────── 909 + Op::Delete => { 910 + let dst = Self::read_u8(&mut self.frames[fi]); 911 + let obj_r = Self::read_u8(&mut self.frames[fi]); 912 + let key_r = Self::read_u8(&mut self.frames[fi]); 913 + let base = self.frames[fi].base; 914 + let key = self.registers[base + key_r as usize].to_js_string(); 915 + let result = 916 + if let Value::Object(ref mut obj) = self.registers[base + obj_r as usize] { 917 + obj.delete(&key) 918 + } else { 919 + true 920 + }; 921 + self.registers[base + dst as usize] = Value::Boolean(result); 922 + } 923 + } 924 + } 925 + } 926 + 927 + /// Read 3 register operands and return (dst, lhs, rhs, base) as usize indices. 928 + fn read_3reg(&mut self, fi: usize) -> (usize, usize, usize, usize) { 929 + let dst = Self::read_u8(&mut self.frames[fi]) as usize; 930 + let lhs = Self::read_u8(&mut self.frames[fi]) as usize; 931 + let rhs = Self::read_u8(&mut self.frames[fi]) as usize; 932 + let base = self.frames[fi].base; 933 + (dst, lhs, rhs, base) 934 + } 935 + 936 + /// Try to find an exception handler on the call stack. 937 + /// Returns true if a handler was found (execution resumes there). 938 + fn handle_exception(&mut self, value: Value) -> bool { 939 + while let Some(frame) = self.frames.last_mut() { 940 + if let Some(handler) = frame.exception_handlers.pop() { 941 + let base = frame.base; 942 + frame.ip = handler.catch_ip; 943 + self.registers[base + handler.catch_reg as usize] = value; 944 + return true; 945 + } 946 + if self.frames.len() == 1 { 947 + break; 948 + } 949 + self.frames.pop(); 950 + } 951 + false 952 + } 953 + 954 + /// Register a native function as a global. 955 + pub fn define_native( 956 + &mut self, 957 + name: &str, 958 + callback: fn(&[Value]) -> Result<Value, RuntimeError>, 959 + ) { 960 + self.globals.insert( 961 + name.to_string(), 962 + Value::Function(FunctionValue { 963 + name: name.to_string(), 964 + kind: FunctionKind::Native(NativeFunc { callback }), 965 + }), 966 + ); 967 + } 968 + 969 + /// Get a global variable value. 970 + pub fn get_global(&self, name: &str) -> Option<&Value> { 971 + self.globals.get(name) 972 + } 973 + 974 + /// Set a global variable. 975 + pub fn set_global(&mut self, name: &str, val: Value) { 976 + self.globals.insert(name.to_string(), val); 977 + } 978 + } 979 + 980 + impl Default for Vm { 981 + fn default() -> Self { 982 + Self::new() 983 + } 984 + } 985 + 986 + // ── String property access ─────────────────────────────────── 987 + 988 + fn string_get_property(s: &str, key: &str) -> Value { 989 + if key == "length" { 990 + Value::Number(s.len() as f64) 991 + } else if let Ok(idx) = key.parse::<usize>() { 992 + s.chars() 993 + .nth(idx) 994 + .map(|c| Value::String(c.to_string())) 995 + .unwrap_or(Value::Undefined) 996 + } else { 997 + Value::Undefined 998 + } 999 + } 1000 + 1001 + // ── Type conversion helpers ────────────────────────────────── 1002 + 1003 + /// ToInt32 (ECMA-262 §7.1.5). 1004 + fn to_int32(val: &Value) -> i32 { 1005 + let n = val.to_number(); 1006 + if n.is_nan() || n.is_infinite() || n == 0.0 { 1007 + return 0; 1008 + } 1009 + let i = n.trunc() as i64; 1010 + (i & 0xFFFF_FFFF) as i32 1011 + } 1012 + 1013 + /// ToUint32 (ECMA-262 §7.1.6). 1014 + fn to_uint32(val: &Value) -> u32 { 1015 + let n = val.to_number(); 1016 + if n.is_nan() || n.is_infinite() || n == 0.0 { 1017 + return 0; 1018 + } 1019 + let i = n.trunc() as i64; 1020 + (i & 0xFFFF_FFFF) as u32 1021 + } 1022 + 1023 + // ── Equality ───────────────────────────────────────────────── 1024 + 1025 + /// Abstract equality comparison (==) per ECMA-262 §7.2.14. 1026 + fn abstract_eq(x: &Value, y: &Value) -> bool { 1027 + match (x, y) { 1028 + (Value::Undefined, Value::Undefined) => true, 1029 + (Value::Null, Value::Null) => true, 1030 + (Value::Undefined, Value::Null) | (Value::Null, Value::Undefined) => true, 1031 + (Value::Number(a), Value::Number(b)) => a == b, 1032 + (Value::String(a), Value::String(b)) => a == b, 1033 + (Value::Boolean(a), Value::Boolean(b)) => a == b, 1034 + // Number / String → convert String to Number. 1035 + (Value::Number(_), Value::String(_)) => abstract_eq(x, &Value::Number(y.to_number())), 1036 + (Value::String(_), Value::Number(_)) => abstract_eq(&Value::Number(x.to_number()), y), 1037 + // Boolean → Number. 1038 + (Value::Boolean(_), _) => abstract_eq(&Value::Number(x.to_number()), y), 1039 + (_, Value::Boolean(_)) => abstract_eq(x, &Value::Number(y.to_number())), 1040 + _ => false, 1041 + } 1042 + } 1043 + 1044 + /// Strict equality comparison (===) per ECMA-262 §7.2.15. 1045 + fn strict_eq(x: &Value, y: &Value) -> bool { 1046 + match (x, y) { 1047 + (Value::Undefined, Value::Undefined) => true, 1048 + (Value::Null, Value::Null) => true, 1049 + (Value::Number(a), Value::Number(b)) => a == b, 1050 + (Value::String(a), Value::String(b)) => a == b, 1051 + (Value::Boolean(a), Value::Boolean(b)) => a == b, 1052 + _ => false, 1053 + } 1054 + } 1055 + 1056 + // ── Relational comparison ──────────────────────────────────── 1057 + 1058 + /// Abstract relational comparison. Returns false for NaN comparisons. 1059 + fn abstract_relational( 1060 + lhs: &Value, 1061 + rhs: &Value, 1062 + predicate: fn(std::cmp::Ordering) -> bool, 1063 + ) -> bool { 1064 + // If both are strings, compare lexicographically. 1065 + if let (Value::String(a), Value::String(b)) = (lhs, rhs) { 1066 + return predicate(a.cmp(b)); 1067 + } 1068 + // Otherwise, compare as numbers. 1069 + let a = lhs.to_number(); 1070 + let b = rhs.to_number(); 1071 + if a.is_nan() || b.is_nan() { 1072 + return false; 1073 + } 1074 + predicate(a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal)) 1075 + } 1076 + 1077 + // ── Addition ───────────────────────────────────────────────── 1078 + 1079 + /// The + operator: string concat if either operand is a string, else numeric add. 1080 + fn add_values(lhs: &Value, rhs: &Value) -> Value { 1081 + match (lhs, rhs) { 1082 + (Value::String(a), _) => Value::String(format!("{a}{}", rhs.to_js_string())), 1083 + (_, Value::String(b)) => Value::String(format!("{}{b}", lhs.to_js_string())), 1084 + _ => Value::Number(lhs.to_number() + rhs.to_number()), 1085 + } 1086 + } 1087 + 1088 + // ── Tests ──────────────────────────────────────────────────── 1089 + 1090 + #[cfg(test)] 1091 + mod tests { 1092 + use super::*; 1093 + use crate::bytecode::{BytecodeBuilder, Constant, Op}; 1094 + use crate::compiler; 1095 + use crate::parser::Parser; 1096 + 1097 + /// Helper: compile and execute JS source, return the completion value. 1098 + fn eval(source: &str) -> Result<Value, RuntimeError> { 1099 + let program = Parser::parse(source).expect("parse failed"); 1100 + let func = compiler::compile(&program).expect("compile failed"); 1101 + let mut vm = Vm::new(); 1102 + vm.execute(&func) 1103 + } 1104 + 1105 + // ── Value tests ───────────────────────────────────────── 1106 + 1107 + #[test] 1108 + fn test_to_boolean() { 1109 + assert!(!Value::Undefined.to_boolean()); 1110 + assert!(!Value::Null.to_boolean()); 1111 + assert!(!Value::Boolean(false).to_boolean()); 1112 + assert!(Value::Boolean(true).to_boolean()); 1113 + assert!(!Value::Number(0.0).to_boolean()); 1114 + assert!(!Value::Number(f64::NAN).to_boolean()); 1115 + assert!(Value::Number(1.0).to_boolean()); 1116 + assert!(!Value::String(String::new()).to_boolean()); 1117 + assert!(Value::String("hello".to_string()).to_boolean()); 1118 + assert!(Value::Object(Object::new()).to_boolean()); 1119 + } 1120 + 1121 + #[test] 1122 + fn test_to_number() { 1123 + assert!(Value::Undefined.to_number().is_nan()); 1124 + assert_eq!(Value::Null.to_number(), 0.0); 1125 + assert_eq!(Value::Boolean(true).to_number(), 1.0); 1126 + assert_eq!(Value::Boolean(false).to_number(), 0.0); 1127 + assert_eq!(Value::Number(42.0).to_number(), 42.0); 1128 + assert_eq!(Value::String("42".to_string()).to_number(), 42.0); 1129 + assert_eq!(Value::String("".to_string()).to_number(), 0.0); 1130 + assert!(Value::String("abc".to_string()).to_number().is_nan()); 1131 + } 1132 + 1133 + #[test] 1134 + fn test_type_of() { 1135 + assert_eq!(Value::Undefined.type_of(), "undefined"); 1136 + assert_eq!(Value::Null.type_of(), "object"); 1137 + assert_eq!(Value::Boolean(true).type_of(), "boolean"); 1138 + assert_eq!(Value::Number(1.0).type_of(), "number"); 1139 + assert_eq!(Value::String("hi".to_string()).type_of(), "string"); 1140 + assert_eq!(Value::Object(Object::new()).type_of(), "object"); 1141 + } 1142 + 1143 + // ── VM bytecode-level tests ───────────────────────────── 1144 + 1145 + #[test] 1146 + fn test_load_const_number() { 1147 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1148 + b.func.register_count = 1; 1149 + let ci = b.add_constant(Constant::Number(42.0)); 1150 + b.emit_reg_u16(Op::LoadConst, 0, ci); 1151 + b.emit_reg(Op::Return, 0); 1152 + let func = b.finish(); 1153 + 1154 + let mut vm = Vm::new(); 1155 + let result = vm.execute(&func).unwrap(); 1156 + match result { 1157 + Value::Number(n) => assert_eq!(n, 42.0), 1158 + _ => panic!("expected Number, got {result:?}"), 1159 + } 1160 + } 1161 + 1162 + #[test] 1163 + fn test_arithmetic_ops() { 1164 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1165 + b.func.register_count = 3; 1166 + let c10 = b.add_constant(Constant::Number(10.0)); 1167 + let c3 = b.add_constant(Constant::Number(3.0)); 1168 + b.emit_reg_u16(Op::LoadConst, 0, c10); 1169 + b.emit_reg_u16(Op::LoadConst, 1, c3); 1170 + b.emit_reg3(Op::Add, 2, 0, 1); 1171 + b.emit_reg(Op::Return, 2); 1172 + let func = b.finish(); 1173 + 1174 + let mut vm = Vm::new(); 1175 + match vm.execute(&func).unwrap() { 1176 + Value::Number(n) => assert_eq!(n, 13.0), 1177 + v => panic!("expected 13, got {v:?}"), 1178 + } 1179 + } 1180 + 1181 + #[test] 1182 + fn test_string_concat() { 1183 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1184 + b.func.register_count = 3; 1185 + let c1 = b.add_constant(Constant::String("hello".into())); 1186 + let c2 = b.add_constant(Constant::String(" world".into())); 1187 + b.emit_reg_u16(Op::LoadConst, 0, c1); 1188 + b.emit_reg_u16(Op::LoadConst, 1, c2); 1189 + b.emit_reg3(Op::Add, 2, 0, 1); 1190 + b.emit_reg(Op::Return, 2); 1191 + let func = b.finish(); 1192 + 1193 + let mut vm = Vm::new(); 1194 + match vm.execute(&func).unwrap() { 1195 + Value::String(s) => assert_eq!(s, "hello world"), 1196 + v => panic!("expected string, got {v:?}"), 1197 + } 1198 + } 1199 + 1200 + #[test] 1201 + fn test_jump_if_false() { 1202 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1203 + b.func.register_count = 2; 1204 + b.emit_reg(Op::LoadFalse, 0); 1205 + let patch = b.emit_cond_jump(Op::JumpIfFalse, 0); 1206 + b.emit_load_int8(1, 1); 1207 + let skip = b.emit_jump(Op::Jump); 1208 + b.patch_jump(patch); 1209 + b.emit_load_int8(1, 2); 1210 + b.patch_jump(skip); 1211 + b.emit_reg(Op::Return, 1); 1212 + let func = b.finish(); 1213 + 1214 + let mut vm = Vm::new(); 1215 + match vm.execute(&func).unwrap() { 1216 + Value::Number(n) => assert_eq!(n, 2.0), 1217 + v => panic!("expected 2, got {v:?}"), 1218 + } 1219 + } 1220 + 1221 + #[test] 1222 + fn test_globals() { 1223 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1224 + b.func.register_count = 2; 1225 + let name = b.add_name("x"); 1226 + b.emit_load_int8(0, 42); 1227 + b.emit_store_global(name, 0); 1228 + b.emit_load_global(1, name); 1229 + b.emit_reg(Op::Return, 1); 1230 + let func = b.finish(); 1231 + 1232 + let mut vm = Vm::new(); 1233 + match vm.execute(&func).unwrap() { 1234 + Value::Number(n) => assert_eq!(n, 42.0), 1235 + v => panic!("expected 42, got {v:?}"), 1236 + } 1237 + } 1238 + 1239 + #[test] 1240 + fn test_function_call() { 1241 + let mut inner_b = BytecodeBuilder::new("add1".into(), 1); 1242 + inner_b.func.register_count = 2; 1243 + inner_b.emit_load_int8(1, 1); 1244 + inner_b.emit_reg3(Op::Add, 0, 0, 1); 1245 + inner_b.emit_reg(Op::Return, 0); 1246 + let inner = inner_b.finish(); 1247 + 1248 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1249 + b.func.register_count = 4; 1250 + let fi = b.add_function(inner); 1251 + b.emit_reg_u16(Op::CreateClosure, 0, fi); 1252 + b.emit_load_int8(1, 10); 1253 + b.emit_call(2, 0, 1, 1); 1254 + b.emit_reg(Op::Return, 2); 1255 + let func = b.finish(); 1256 + 1257 + let mut vm = Vm::new(); 1258 + match vm.execute(&func).unwrap() { 1259 + Value::Number(n) => assert_eq!(n, 11.0), 1260 + v => panic!("expected 11, got {v:?}"), 1261 + } 1262 + } 1263 + 1264 + #[test] 1265 + fn test_native_function() { 1266 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1267 + b.func.register_count = 3; 1268 + let name = b.add_name("double"); 1269 + b.emit_load_global(0, name); 1270 + b.emit_load_int8(1, 21); 1271 + b.emit_call(2, 0, 1, 1); 1272 + b.emit_reg(Op::Return, 2); 1273 + let func = b.finish(); 1274 + 1275 + let mut vm = Vm::new(); 1276 + vm.define_native("double", |args| { 1277 + let n = args.first().unwrap_or(&Value::Undefined).to_number(); 1278 + Ok(Value::Number(n * 2.0)) 1279 + }); 1280 + match vm.execute(&func).unwrap() { 1281 + Value::Number(n) => assert_eq!(n, 42.0), 1282 + v => panic!("expected 42, got {v:?}"), 1283 + } 1284 + } 1285 + 1286 + #[test] 1287 + fn test_object_property() { 1288 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1289 + b.func.register_count = 3; 1290 + let name = b.add_name("x"); 1291 + b.emit_reg(Op::CreateObject, 0); 1292 + b.emit_load_int8(1, 42); 1293 + b.emit_set_prop_name(0, name, 1); 1294 + b.emit_get_prop_name(2, 0, name); 1295 + b.emit_reg(Op::Return, 2); 1296 + let func = b.finish(); 1297 + 1298 + let mut vm = Vm::new(); 1299 + match vm.execute(&func).unwrap() { 1300 + Value::Number(n) => assert_eq!(n, 42.0), 1301 + v => panic!("expected 42, got {v:?}"), 1302 + } 1303 + } 1304 + 1305 + #[test] 1306 + fn test_typeof_operator() { 1307 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1308 + b.func.register_count = 2; 1309 + b.emit_load_int8(0, 5); 1310 + b.emit_reg_reg(Op::TypeOf, 1, 0); 1311 + b.emit_reg(Op::Return, 1); 1312 + let func = b.finish(); 1313 + 1314 + let mut vm = Vm::new(); 1315 + match vm.execute(&func).unwrap() { 1316 + Value::String(s) => assert_eq!(s, "number"), 1317 + v => panic!("expected 'number', got {v:?}"), 1318 + } 1319 + } 1320 + 1321 + #[test] 1322 + fn test_comparison() { 1323 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1324 + b.func.register_count = 3; 1325 + b.emit_load_int8(0, 5); 1326 + b.emit_load_int8(1, 10); 1327 + b.emit_reg3(Op::LessThan, 2, 0, 1); 1328 + b.emit_reg(Op::Return, 2); 1329 + let func = b.finish(); 1330 + 1331 + let mut vm = Vm::new(); 1332 + match vm.execute(&func).unwrap() { 1333 + Value::Boolean(b) => assert!(b), 1334 + v => panic!("expected true, got {v:?}"), 1335 + } 1336 + } 1337 + 1338 + #[test] 1339 + fn test_abstract_equality() { 1340 + assert!(abstract_eq(&Value::Null, &Value::Undefined)); 1341 + assert!(abstract_eq(&Value::Undefined, &Value::Null)); 1342 + assert!(abstract_eq( 1343 + &Value::Number(1.0), 1344 + &Value::String("1".to_string()) 1345 + )); 1346 + assert!(abstract_eq(&Value::Boolean(true), &Value::Number(1.0))); 1347 + assert!(!abstract_eq(&Value::Number(0.0), &Value::Null)); 1348 + } 1349 + 1350 + #[test] 1351 + fn test_strict_equality() { 1352 + assert!(strict_eq(&Value::Number(1.0), &Value::Number(1.0))); 1353 + assert!(!strict_eq( 1354 + &Value::Number(1.0), 1355 + &Value::String("1".to_string()) 1356 + )); 1357 + assert!(strict_eq(&Value::Null, &Value::Null)); 1358 + assert!(!strict_eq(&Value::Null, &Value::Undefined)); 1359 + } 1360 + 1361 + #[test] 1362 + fn test_bitwise_ops() { 1363 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1364 + b.func.register_count = 3; 1365 + b.emit_load_int8(0, 0x0F); 1366 + b.emit_load_int8(1, 0x03); 1367 + b.emit_reg3(Op::BitAnd, 2, 0, 1); 1368 + b.emit_reg(Op::Return, 2); 1369 + let func = b.finish(); 1370 + 1371 + let mut vm = Vm::new(); 1372 + match vm.execute(&func).unwrap() { 1373 + Value::Number(n) => assert_eq!(n, 3.0), 1374 + v => panic!("expected 3, got {v:?}"), 1375 + } 1376 + } 1377 + 1378 + #[test] 1379 + fn test_throw_uncaught() { 1380 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1381 + b.func.register_count = 1; 1382 + let ci = b.add_constant(Constant::String("oops".into())); 1383 + b.emit_reg_u16(Op::LoadConst, 0, ci); 1384 + b.emit_reg(Op::Throw, 0); 1385 + let func = b.finish(); 1386 + 1387 + let mut vm = Vm::new(); 1388 + let err = vm.execute(&func).unwrap_err(); 1389 + assert_eq!(err.message, "oops"); 1390 + } 1391 + 1392 + #[test] 1393 + fn test_loop_counting() { 1394 + let mut b = BytecodeBuilder::new("<test>".into(), 0); 1395 + b.func.register_count = 3; 1396 + b.emit_load_int8(0, 0); 1397 + b.emit_load_int8(1, 5); 1398 + let loop_start = b.offset(); 1399 + b.emit_reg3(Op::LessThan, 2, 0, 1); 1400 + let exit_patch = b.emit_cond_jump(Op::JumpIfFalse, 2); 1401 + b.emit_load_int8(2, 1); 1402 + b.emit_reg3(Op::Add, 0, 0, 2); 1403 + b.emit_jump_to(loop_start); 1404 + b.patch_jump(exit_patch); 1405 + b.emit_reg(Op::Return, 0); 1406 + let func = b.finish(); 1407 + 1408 + let mut vm = Vm::new(); 1409 + match vm.execute(&func).unwrap() { 1410 + Value::Number(n) => assert_eq!(n, 5.0), 1411 + v => panic!("expected 5, got {v:?}"), 1412 + } 1413 + } 1414 + 1415 + // ── End-to-end (compile + execute) tests ──────────────── 1416 + 1417 + #[test] 1418 + fn test_e2e_arithmetic() { 1419 + match eval("2 + 3 * 4").unwrap() { 1420 + Value::Number(n) => assert_eq!(n, 14.0), 1421 + v => panic!("expected 14, got {v:?}"), 1422 + } 1423 + } 1424 + 1425 + #[test] 1426 + fn test_e2e_variables() { 1427 + match eval("var x = 10; var y = 20; x + y").unwrap() { 1428 + Value::Number(n) => assert_eq!(n, 30.0), 1429 + v => panic!("expected 30, got {v:?}"), 1430 + } 1431 + } 1432 + 1433 + #[test] 1434 + fn test_e2e_if_else() { 1435 + match eval("var x = 5; if (x > 3) { x = 100; } x").unwrap() { 1436 + Value::Number(n) => assert_eq!(n, 100.0), 1437 + v => panic!("expected 100, got {v:?}"), 1438 + } 1439 + } 1440 + 1441 + #[test] 1442 + fn test_e2e_while_loop() { 1443 + match eval("var i = 0; var sum = 0; while (i < 10) { sum = sum + i; i = i + 1; } sum") 1444 + .unwrap() 1445 + { 1446 + Value::Number(n) => assert_eq!(n, 45.0), 1447 + v => panic!("expected 45, got {v:?}"), 1448 + } 1449 + } 1450 + 1451 + #[test] 1452 + fn test_e2e_function_call() { 1453 + match eval("function add(a, b) { return a + b; } add(3, 4)").unwrap() { 1454 + Value::Number(n) => assert_eq!(n, 7.0), 1455 + v => panic!("expected 7, got {v:?}"), 1456 + } 1457 + } 1458 + 1459 + #[test] 1460 + fn test_e2e_recursive_factorial() { 1461 + let src = r#" 1462 + function fact(n) { 1463 + if (n <= 1) return 1; 1464 + return n * fact(n - 1); 1465 + } 1466 + fact(6) 1467 + "#; 1468 + match eval(src).unwrap() { 1469 + Value::Number(n) => assert_eq!(n, 720.0), 1470 + v => panic!("expected 720, got {v:?}"), 1471 + } 1472 + } 1473 + 1474 + #[test] 1475 + fn test_e2e_string_concat() { 1476 + match eval("'hello' + ' ' + 'world'").unwrap() { 1477 + Value::String(s) => assert_eq!(s, "hello world"), 1478 + v => panic!("expected 'hello world', got {v:?}"), 1479 + } 1480 + } 1481 + 1482 + #[test] 1483 + fn test_e2e_comparison_coercion() { 1484 + match eval("1 == '1'").unwrap() { 1485 + Value::Boolean(b) => assert!(b), 1486 + v => panic!("expected true, got {v:?}"), 1487 + } 1488 + match eval("1 === '1'").unwrap() { 1489 + Value::Boolean(b) => assert!(!b), 1490 + v => panic!("expected false, got {v:?}"), 1491 + } 1492 + } 1493 + 1494 + #[test] 1495 + fn test_e2e_typeof() { 1496 + match eval("typeof 42").unwrap() { 1497 + Value::String(s) => assert_eq!(s, "number"), 1498 + v => panic!("expected 'number', got {v:?}"), 1499 + } 1500 + match eval("typeof 'hello'").unwrap() { 1501 + Value::String(s) => assert_eq!(s, "string"), 1502 + v => panic!("expected 'string', got {v:?}"), 1503 + } 1504 + } 1505 + 1506 + #[test] 1507 + fn test_e2e_object_literal() { 1508 + match eval("var o = { x: 10, y: 20 }; o.x + o.y").unwrap() { 1509 + Value::Number(n) => assert_eq!(n, 30.0), 1510 + v => panic!("expected 30, got {v:?}"), 1511 + } 1512 + } 1513 + 1514 + #[test] 1515 + fn test_e2e_for_loop() { 1516 + match eval("var s = 0; for (var i = 0; i < 5; i = i + 1) { s = s + i; } s").unwrap() { 1517 + Value::Number(n) => assert_eq!(n, 10.0), 1518 + v => panic!("expected 10, got {v:?}"), 1519 + } 1520 + } 1521 + 1522 + #[test] 1523 + fn test_e2e_nested_functions() { 1524 + // Note: closures (capturing parent scope vars) not yet supported. 1525 + // This test verifies nested function declarations and calls work. 1526 + let src = r#" 1527 + function outer(x) { 1528 + function inner(y) { 1529 + return y + 1; 1530 + } 1531 + return inner(x); 1532 + } 1533 + outer(5) 1534 + "#; 1535 + match eval(src).unwrap() { 1536 + Value::Number(n) => assert_eq!(n, 6.0), 1537 + v => panic!("expected 6, got {v:?}"), 1538 + } 1539 + } 1540 + 1541 + #[test] 1542 + fn test_e2e_logical_operators() { 1543 + match eval("true && false").unwrap() { 1544 + Value::Boolean(b) => assert!(!b), 1545 + v => panic!("expected false, got {v:?}"), 1546 + } 1547 + match eval("false || true").unwrap() { 1548 + Value::Boolean(b) => assert!(b), 1549 + v => panic!("expected true, got {v:?}"), 1550 + } 1551 + } 1552 + 1553 + #[test] 1554 + fn test_e2e_unary_neg() { 1555 + match eval("-(5 + 3)").unwrap() { 1556 + Value::Number(n) => assert_eq!(n, -8.0), 1557 + v => panic!("expected -8, got {v:?}"), 1558 + } 1559 + } 1560 + 1561 + #[test] 1562 + fn test_e2e_ternary() { 1563 + match eval("true ? 1 : 2").unwrap() { 1564 + Value::Number(n) => assert_eq!(n, 1.0), 1565 + v => panic!("expected 1, got {v:?}"), 1566 + } 1567 + match eval("false ? 1 : 2").unwrap() { 1568 + Value::Number(n) => assert_eq!(n, 2.0), 1569 + v => panic!("expected 2, got {v:?}"), 1570 + } 1571 + } 1572 + 1573 + #[test] 1574 + fn test_e2e_fibonacci() { 1575 + let src = r#" 1576 + function fib(n) { 1577 + if (n <= 1) return n; 1578 + return fib(n - 1) + fib(n - 2); 1579 + } 1580 + fib(10) 1581 + "#; 1582 + match eval(src).unwrap() { 1583 + Value::Number(n) => assert_eq!(n, 55.0), 1584 + v => panic!("expected 55, got {v:?}"), 1585 + } 1586 + } 1587 + }
+2 -2
crates/js/tests/test262.rs
··· 217 217 // We expect a parse error. If our engine returns any error, count as pass. 218 218 match result { 219 219 Err(_) => (1, 0, 0), 220 - Ok(()) => (0, 1, 0), 220 + Ok(_) => (0, 1, 0), 221 221 } 222 222 } else { 223 223 // We expect success. 224 224 match result { 225 - Ok(()) => (1, 0, 0), 225 + Ok(_) => (1, 0, 0), 226 226 Err(_) => (0, 1, 0), 227 227 } 228 228 }