we (web engine): Experimental web browser project to understand the limits of Claude
1//! Register-based JavaScript virtual machine.
2//!
3//! Executes bytecode produced by the compiler. Each call frame has a register
4//! file, and the VM dispatches instructions in a loop. Heap-allocated objects
5//! (plain objects and functions) are managed by a tri-color mark-and-sweep
6//! garbage collector.
7
8use crate::bytecode::{Constant, Function, Op, Reg};
9use crate::gc::{Gc, GcRef, Traceable};
10use std::collections::HashMap;
11use std::fmt;
12
13// ── Heap objects (GC-managed) ────────────────────────────────
14
15/// A GC-managed heap object: a plain object, a function, or a closure cell.
16pub enum HeapObject {
17 Object(ObjectData),
18 Function(Box<FunctionData>),
19 /// A mutable cell holding one Value — used for closure-captured variables.
20 Cell(Value),
21}
22
23impl Traceable for HeapObject {
24 fn trace(&self, visitor: &mut dyn FnMut(GcRef)) {
25 match self {
26 HeapObject::Object(data) => {
27 for prop in data.properties.values() {
28 if let Some(r) = prop.value.gc_ref() {
29 visitor(r);
30 }
31 }
32 if let Some(proto) = data.prototype {
33 visitor(proto);
34 }
35 }
36 HeapObject::Function(fdata) => {
37 if let Some(proto) = fdata.prototype_obj {
38 visitor(proto);
39 }
40 for prop in fdata.properties.values() {
41 if let Some(r) = prop.value.gc_ref() {
42 visitor(r);
43 }
44 }
45 for &uv in &fdata.upvalues {
46 visitor(uv);
47 }
48 }
49 HeapObject::Cell(val) => {
50 if let Some(r) = val.gc_ref() {
51 visitor(r);
52 }
53 }
54 }
55 }
56}
57
58/// A property descriptor stored in an object's property map.
59#[derive(Clone)]
60pub struct Property {
61 /// The property's value (for data properties).
62 pub value: Value,
63 /// Whether the value can be changed via assignment.
64 pub writable: bool,
65 /// Whether the property shows up in `for...in` and `Object.keys`.
66 pub enumerable: bool,
67 /// Whether the property can be deleted or its attributes changed.
68 pub configurable: bool,
69}
70
71impl Property {
72 /// Create a new data property with all flags set to true (the JS default for
73 /// properties created by assignment).
74 pub fn data(value: Value) -> Self {
75 Self {
76 value,
77 writable: true,
78 enumerable: true,
79 configurable: true,
80 }
81 }
82
83 /// Create a non-enumerable, non-configurable property (e.g. built-in methods).
84 pub fn builtin(value: Value) -> Self {
85 Self {
86 value,
87 writable: true,
88 enumerable: false,
89 configurable: false,
90 }
91 }
92}
93
94/// A JS plain object (properties stored as a HashMap with descriptors).
95pub struct ObjectData {
96 pub properties: HashMap<String, Property>,
97 pub prototype: Option<GcRef>,
98 /// Whether new properties can be added (Object.preventExtensions).
99 pub extensible: bool,
100}
101
102impl ObjectData {
103 pub fn new() -> Self {
104 Self {
105 properties: HashMap::new(),
106 prototype: None,
107 extensible: true,
108 }
109 }
110}
111
112impl Default for ObjectData {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118/// A runtime function value: either bytecode or native.
119///
120/// In JavaScript, functions are objects and can have arbitrary properties
121/// (e.g. `assert.sameValue = function() {}`).
122pub struct FunctionData {
123 pub name: String,
124 pub kind: FunctionKind,
125 /// The `.prototype` property object (for use as a constructor with `instanceof`).
126 pub prototype_obj: Option<GcRef>,
127 /// Arbitrary properties set on this function (functions are objects in JS).
128 pub properties: HashMap<String, Property>,
129 /// Captured upvalue cells (GcRefs to HeapObject::Cell values).
130 pub upvalues: Vec<GcRef>,
131}
132
133#[derive(Clone)]
134pub enum FunctionKind {
135 /// Bytecode function.
136 Bytecode(BytecodeFunc),
137 /// Native (Rust) function.
138 Native(NativeFunc),
139}
140
141#[derive(Clone)]
142pub struct BytecodeFunc {
143 pub func: Function,
144}
145
146/// A native function callable from JS.
147#[derive(Clone)]
148pub struct NativeFunc {
149 pub callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>,
150}
151
152/// Context passed to native functions, providing GC access and `this` binding.
153pub struct NativeContext<'a> {
154 pub gc: &'a mut Gc<HeapObject>,
155 pub this: Value,
156}
157
158// ── JS Value ──────────────────────────────────────────────────
159
160/// A JavaScript runtime value.
161///
162/// Primitive types (Undefined, Null, Boolean, Number, String) are stored
163/// inline. Objects and Functions are heap-allocated via the GC and referenced
164/// by a [`GcRef`] handle.
165#[derive(Clone)]
166pub enum Value {
167 Undefined,
168 Null,
169 Boolean(bool),
170 Number(f64),
171 String(String),
172 /// A GC-managed plain object.
173 Object(GcRef),
174 /// A GC-managed function.
175 Function(GcRef),
176}
177
178impl fmt::Debug for Value {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 match self {
181 Value::Undefined => write!(f, "undefined"),
182 Value::Null => write!(f, "null"),
183 Value::Boolean(b) => write!(f, "{b}"),
184 Value::Number(n) => write!(f, "{n}"),
185 Value::String(s) => write!(f, "\"{}\"", s),
186 Value::Object(_) => write!(f, "[object Object]"),
187 Value::Function(_) => write!(f, "[Function]"),
188 }
189 }
190}
191
192impl fmt::Display for Value {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 match self {
195 Value::Undefined => write!(f, "undefined"),
196 Value::Null => write!(f, "null"),
197 Value::Boolean(b) => write!(f, "{b}"),
198 Value::Number(n) => format_number(*n, f),
199 Value::String(s) => write!(f, "{s}"),
200 Value::Object(_) => write!(f, "[object Object]"),
201 Value::Function(_) => write!(f, "function() {{ [native code] }}"),
202 }
203 }
204}
205
206/// Format a number following JS conventions (no trailing .0 for integers).
207fn format_number(n: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 if n.is_nan() {
209 write!(f, "NaN")
210 } else if n.is_infinite() {
211 if n.is_sign_positive() {
212 write!(f, "Infinity")
213 } else {
214 write!(f, "-Infinity")
215 }
216 } else if n == 0.0 {
217 write!(f, "0")
218 } else if n.fract() == 0.0 && n.abs() < 1e20 {
219 write!(f, "{}", n as i64)
220 } else {
221 write!(f, "{n}")
222 }
223}
224
225impl Value {
226 /// Abstract `ToBoolean` (ECMA-262 §7.1.2).
227 pub fn to_boolean(&self) -> bool {
228 match self {
229 Value::Undefined | Value::Null => false,
230 Value::Boolean(b) => *b,
231 Value::Number(n) => *n != 0.0 && !n.is_nan(),
232 Value::String(s) => !s.is_empty(),
233 Value::Object(_) | Value::Function(_) => true,
234 }
235 }
236
237 /// Abstract `ToNumber` (ECMA-262 §7.1.3).
238 pub fn to_number(&self) -> f64 {
239 match self {
240 Value::Undefined => f64::NAN,
241 Value::Null => 0.0,
242 Value::Boolean(true) => 1.0,
243 Value::Boolean(false) => 0.0,
244 Value::Number(n) => *n,
245 Value::String(s) => {
246 let s = s.trim();
247 if s.is_empty() {
248 0.0
249 } else if s == "Infinity" || s == "+Infinity" {
250 f64::INFINITY
251 } else if s == "-Infinity" {
252 f64::NEG_INFINITY
253 } else {
254 s.parse::<f64>().unwrap_or(f64::NAN)
255 }
256 }
257 Value::Object(_) | Value::Function(_) => f64::NAN,
258 }
259 }
260
261 /// Abstract `ToString` (ECMA-262 §7.1.12).
262 ///
263 /// Requires `&Gc` to look up function names for `Value::Function`.
264 pub fn to_js_string(&self, gc: &Gc<HeapObject>) -> String {
265 match self {
266 Value::Undefined => "undefined".to_string(),
267 Value::Null => "null".to_string(),
268 Value::Boolean(true) => "true".to_string(),
269 Value::Boolean(false) => "false".to_string(),
270 Value::Number(n) => js_number_to_string(*n),
271 Value::String(s) => s.clone(),
272 Value::Object(_) => "[object Object]".to_string(),
273 Value::Function(gc_ref) => gc
274 .get(*gc_ref)
275 .and_then(|obj| match obj {
276 HeapObject::Function(f) => {
277 Some(format!("function {}() {{ [native code] }}", f.name))
278 }
279 _ => None,
280 })
281 .unwrap_or_else(|| "function() { [native code] }".to_string()),
282 }
283 }
284
285 /// `typeof` operator result.
286 pub fn type_of(&self) -> &'static str {
287 match self {
288 Value::Undefined => "undefined",
289 Value::Null => "object", // yes, this is the spec
290 Value::Boolean(_) => "boolean",
291 Value::Number(_) => "number",
292 Value::String(_) => "string",
293 Value::Object(_) => "object",
294 Value::Function(_) => "function",
295 }
296 }
297
298 /// Is this value nullish (null or undefined)?
299 pub fn is_nullish(&self) -> bool {
300 matches!(self, Value::Undefined | Value::Null)
301 }
302
303 /// Extract the `GcRef` if this value is an Object or Function.
304 pub fn gc_ref(&self) -> Option<GcRef> {
305 match self {
306 Value::Object(r) | Value::Function(r) => Some(*r),
307 _ => None,
308 }
309 }
310}
311
312/// Format a number as JS would.
313pub(crate) fn js_number_to_string(n: f64) -> String {
314 if n.is_nan() {
315 "NaN".to_string()
316 } else if n.is_infinite() {
317 if n.is_sign_positive() {
318 "Infinity".to_string()
319 } else {
320 "-Infinity".to_string()
321 }
322 } else if n == 0.0 {
323 "0".to_string()
324 } else if n.fract() == 0.0 && n.abs() < 1e20 {
325 format!("{}", n as i64)
326 } else {
327 format!("{n}")
328 }
329}
330
331// ── Runtime errors ────────────────────────────────────────────
332
333/// JavaScript runtime error types.
334#[derive(Debug, Clone)]
335pub struct RuntimeError {
336 pub kind: ErrorKind,
337 pub message: String,
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub enum ErrorKind {
342 TypeError,
343 ReferenceError,
344 RangeError,
345 SyntaxError,
346 Error,
347}
348
349impl fmt::Display for RuntimeError {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 let name = match self.kind {
352 ErrorKind::TypeError => "TypeError",
353 ErrorKind::ReferenceError => "ReferenceError",
354 ErrorKind::RangeError => "RangeError",
355 ErrorKind::SyntaxError => "SyntaxError",
356 ErrorKind::Error => "Error",
357 };
358 write!(f, "{name}: {}", self.message)
359 }
360}
361
362impl RuntimeError {
363 pub fn type_error(msg: impl Into<String>) -> Self {
364 Self {
365 kind: ErrorKind::TypeError,
366 message: msg.into(),
367 }
368 }
369
370 pub fn reference_error(msg: impl Into<String>) -> Self {
371 Self {
372 kind: ErrorKind::ReferenceError,
373 message: msg.into(),
374 }
375 }
376
377 pub fn range_error(msg: impl Into<String>) -> Self {
378 Self {
379 kind: ErrorKind::RangeError,
380 message: msg.into(),
381 }
382 }
383
384 pub fn syntax_error(msg: impl Into<String>) -> Self {
385 Self {
386 kind: ErrorKind::SyntaxError,
387 message: msg.into(),
388 }
389 }
390
391 /// Convert to a JS Value (an error object). Allocates through the GC.
392 pub fn to_value(&self, gc: &mut Gc<HeapObject>) -> Value {
393 let mut obj = ObjectData::new();
394 obj.properties.insert(
395 "message".to_string(),
396 Property::data(Value::String(self.message.clone())),
397 );
398 let name = match self.kind {
399 ErrorKind::TypeError => "TypeError",
400 ErrorKind::ReferenceError => "ReferenceError",
401 ErrorKind::RangeError => "RangeError",
402 ErrorKind::SyntaxError => "SyntaxError",
403 ErrorKind::Error => "Error",
404 };
405 obj.properties.insert(
406 "name".to_string(),
407 Property::data(Value::String(name.to_string())),
408 );
409 Value::Object(gc.alloc(HeapObject::Object(obj)))
410 }
411}
412
413// ── Property access helpers ──────────────────────────────────
414
415/// Get a property from an object, walking the prototype chain.
416fn gc_get_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str) -> Value {
417 let proto = {
418 match gc.get(obj_ref) {
419 Some(HeapObject::Object(data)) => {
420 if let Some(prop) = data.properties.get(key) {
421 return prop.value.clone();
422 }
423 data.prototype
424 }
425 Some(HeapObject::Function(fdata)) => {
426 // Check user-defined properties first.
427 if let Some(prop) = fdata.properties.get(key) {
428 return prop.value.clone();
429 }
430 // Functions have a `.prototype` property.
431 if key == "prototype" {
432 if let Some(proto_ref) = fdata.prototype_obj {
433 return Value::Object(proto_ref);
434 }
435 return Value::Undefined;
436 }
437 None
438 }
439 _ => return Value::Undefined,
440 }
441 };
442 if let Some(proto_ref) = proto {
443 gc_get_property(gc, proto_ref, key)
444 } else {
445 Value::Undefined
446 }
447}
448
449/// Check if an object has a property (own or inherited).
450fn gc_has_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str) -> bool {
451 let proto = {
452 match gc.get(obj_ref) {
453 Some(HeapObject::Object(data)) => {
454 if data.properties.contains_key(key) {
455 return true;
456 }
457 data.prototype
458 }
459 Some(HeapObject::Function(fdata)) => {
460 if fdata.properties.contains_key(key) || key == "prototype" {
461 return true;
462 }
463 None
464 }
465 _ => return false,
466 }
467 };
468 if let Some(proto_ref) = proto {
469 gc_has_property(gc, proto_ref, key)
470 } else {
471 false
472 }
473}
474
475/// Collect all enumerable string keys of an object (own + inherited), in proper order.
476/// Integer indices first (sorted numerically), then string keys in insertion order.
477fn gc_enumerate_keys(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> {
478 let mut seen = std::collections::HashSet::new();
479 let mut integer_keys: Vec<(u32, String)> = Vec::new();
480 let mut string_keys: Vec<String> = Vec::new();
481 let mut current = Some(obj_ref);
482
483 while let Some(cur_ref) = current {
484 match gc.get(cur_ref) {
485 Some(HeapObject::Object(data)) => {
486 for (key, prop) in &data.properties {
487 if prop.enumerable && seen.insert(key.clone()) {
488 if let Ok(idx) = key.parse::<u32>() {
489 integer_keys.push((idx, key.clone()));
490 } else {
491 string_keys.push(key.clone());
492 }
493 }
494 }
495 current = data.prototype;
496 }
497 _ => break,
498 }
499 }
500
501 // Integer indices sorted numerically first, then string keys in collected order.
502 integer_keys.sort_by_key(|(idx, _)| *idx);
503 let mut result: Vec<String> = integer_keys.into_iter().map(|(_, k)| k).collect();
504 result.extend(string_keys);
505 result
506}
507
508/// Check if `obj_ref` is an instance of the constructor at `ctor_ref`.
509/// Walks the prototype chain of `obj_ref` looking for `ctor.prototype`.
510fn gc_instanceof(gc: &Gc<HeapObject>, obj_ref: GcRef, ctor_ref: GcRef) -> bool {
511 // Get the constructor's .prototype object.
512 let ctor_proto = match gc.get(ctor_ref) {
513 Some(HeapObject::Function(fdata)) => match fdata.prototype_obj {
514 Some(p) => p,
515 None => return false,
516 },
517 _ => return false,
518 };
519
520 // Walk the prototype chain of obj_ref.
521 let mut current = match gc.get(obj_ref) {
522 Some(HeapObject::Object(data)) => data.prototype,
523 _ => None,
524 };
525
526 while let Some(proto_ref) = current {
527 if proto_ref == ctor_proto {
528 return true;
529 }
530 current = match gc.get(proto_ref) {
531 Some(HeapObject::Object(data)) => data.prototype,
532 _ => None,
533 };
534 }
535 false
536}
537
538/// Get a string property (length, index access).
539fn string_get_property(s: &str, key: &str) -> Value {
540 if key == "length" {
541 Value::Number(s.len() as f64)
542 } else if let Ok(idx) = key.parse::<usize>() {
543 s.chars()
544 .nth(idx)
545 .map(|c| Value::String(c.to_string()))
546 .unwrap_or(Value::Undefined)
547 } else {
548 Value::Undefined
549 }
550}
551
552// ── Type conversion helpers ──────────────────────────────────
553
554/// ToInt32 (ECMA-262 §7.1.5).
555fn to_int32(val: &Value) -> i32 {
556 let n = val.to_number();
557 if n.is_nan() || n.is_infinite() || n == 0.0 {
558 return 0;
559 }
560 let i = n.trunc() as i64;
561 (i & 0xFFFF_FFFF) as i32
562}
563
564/// ToUint32 (ECMA-262 §7.1.6).
565fn to_uint32(val: &Value) -> u32 {
566 let n = val.to_number();
567 if n.is_nan() || n.is_infinite() || n == 0.0 {
568 return 0;
569 }
570 let i = n.trunc() as i64;
571 (i & 0xFFFF_FFFF) as u32
572}
573
574// ── Equality ─────────────────────────────────────────────────
575
576/// Abstract equality comparison (==) per ECMA-262 §7.2.14.
577fn abstract_eq(x: &Value, y: &Value) -> bool {
578 match (x, y) {
579 (Value::Undefined, Value::Undefined) => true,
580 (Value::Null, Value::Null) => true,
581 (Value::Undefined, Value::Null) | (Value::Null, Value::Undefined) => true,
582 (Value::Number(a), Value::Number(b)) => a == b,
583 (Value::String(a), Value::String(b)) => a == b,
584 (Value::Boolean(a), Value::Boolean(b)) => a == b,
585 // Number / String → convert String to Number.
586 (Value::Number(_), Value::String(_)) => abstract_eq(x, &Value::Number(y.to_number())),
587 (Value::String(_), Value::Number(_)) => abstract_eq(&Value::Number(x.to_number()), y),
588 // Boolean → Number.
589 (Value::Boolean(_), _) => abstract_eq(&Value::Number(x.to_number()), y),
590 (_, Value::Boolean(_)) => abstract_eq(x, &Value::Number(y.to_number())),
591 // Same GcRef → equal.
592 (Value::Object(a), Value::Object(b)) => a == b,
593 (Value::Function(a), Value::Function(b)) => a == b,
594 _ => false,
595 }
596}
597
598/// Strict equality comparison (===) per ECMA-262 §7.2.15.
599fn strict_eq(x: &Value, y: &Value) -> bool {
600 match (x, y) {
601 (Value::Undefined, Value::Undefined) => true,
602 (Value::Null, Value::Null) => true,
603 (Value::Number(a), Value::Number(b)) => a == b,
604 (Value::String(a), Value::String(b)) => a == b,
605 (Value::Boolean(a), Value::Boolean(b)) => a == b,
606 // Reference identity for heap objects.
607 (Value::Object(a), Value::Object(b)) => a == b,
608 (Value::Function(a), Value::Function(b)) => a == b,
609 _ => false,
610 }
611}
612
613// ── Relational comparison ────────────────────────────────────
614
615/// Abstract relational comparison. Returns false for NaN comparisons.
616fn abstract_relational(
617 lhs: &Value,
618 rhs: &Value,
619 predicate: fn(std::cmp::Ordering) -> bool,
620) -> bool {
621 // If both are strings, compare lexicographically.
622 if let (Value::String(a), Value::String(b)) = (lhs, rhs) {
623 return predicate(a.cmp(b));
624 }
625 // Otherwise, compare as numbers.
626 let a = lhs.to_number();
627 let b = rhs.to_number();
628 if a.is_nan() || b.is_nan() {
629 return false;
630 }
631 predicate(a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal))
632}
633
634// ── Addition ─────────────────────────────────────────────────
635
636/// The + operator: string concat if either operand is a string, else numeric add.
637fn add_values(lhs: &Value, rhs: &Value, gc: &Gc<HeapObject>) -> Value {
638 match (lhs, rhs) {
639 (Value::String(a), _) => Value::String(format!("{a}{}", rhs.to_js_string(gc))),
640 (_, Value::String(b)) => Value::String(format!("{}{b}", lhs.to_js_string(gc))),
641 _ => Value::Number(lhs.to_number() + rhs.to_number()),
642 }
643}
644
645// ── Call frame ────────────────────────────────────────────────
646
647/// A single call frame on the VM's call stack.
648struct CallFrame {
649 /// The function being executed.
650 func: Function,
651 /// Instruction pointer (byte offset into func.code).
652 ip: usize,
653 /// Base register index in the VM's register file.
654 base: usize,
655 /// Register to write the return value into (absolute index in register file).
656 return_reg: usize,
657 /// Exception handler stack for this frame.
658 exception_handlers: Vec<ExceptionHandler>,
659 /// Captured upvalue cells from the closure that created this call frame.
660 upvalues: Vec<GcRef>,
661}
662
663/// An exception handler entry (for try/catch).
664struct ExceptionHandler {
665 /// IP to jump to on exception (the catch block start).
666 catch_ip: usize,
667 /// Register to store the caught exception value.
668 catch_reg: Reg,
669}
670
671// ── VM ───────────────────────────────────────────────────────
672
673/// The JavaScript virtual machine.
674pub struct Vm {
675 /// Register file (flat array shared across frames via base offsets).
676 registers: Vec<Value>,
677 /// Call stack.
678 frames: Vec<CallFrame>,
679 /// Global variables.
680 globals: HashMap<String, Value>,
681 /// Garbage collector managing heap objects.
682 pub gc: Gc<HeapObject>,
683 /// Optional instruction limit. If set, the VM will return an error after
684 /// executing this many instructions (prevents infinite loops).
685 instruction_limit: Option<u64>,
686 /// Number of instructions executed so far.
687 instructions_executed: u64,
688 /// Built-in Object.prototype (root of the prototype chain).
689 pub object_prototype: Option<GcRef>,
690 /// Built-in Array.prototype (set on newly created arrays).
691 pub array_prototype: Option<GcRef>,
692 /// Built-in String.prototype (for primitive auto-boxing).
693 pub string_prototype: Option<GcRef>,
694 /// Built-in Number.prototype (for primitive auto-boxing).
695 pub number_prototype: Option<GcRef>,
696 /// Built-in Boolean.prototype (for primitive auto-boxing).
697 pub boolean_prototype: Option<GcRef>,
698 /// Built-in Date.prototype (for Date constructor objects).
699 pub date_prototype: Option<GcRef>,
700}
701
702/// Maximum register file size.
703const MAX_REGISTERS: usize = 4096;
704/// Maximum call depth.
705const MAX_CALL_DEPTH: usize = 512;
706
707impl Vm {
708 pub fn new() -> Self {
709 let mut vm = Self {
710 registers: vec![Value::Undefined; 256],
711 frames: Vec::new(),
712 globals: HashMap::new(),
713 gc: Gc::new(),
714 instruction_limit: None,
715 instructions_executed: 0,
716 object_prototype: None,
717 array_prototype: None,
718 string_prototype: None,
719 number_prototype: None,
720 boolean_prototype: None,
721 date_prototype: None,
722 };
723 crate::builtins::init_builtins(&mut vm);
724 vm
725 }
726
727 /// Set an instruction limit. The VM will return a RuntimeError after
728 /// executing this many instructions.
729 pub fn set_instruction_limit(&mut self, limit: u64) {
730 self.instruction_limit = Some(limit);
731 }
732
733 /// Execute a compiled top-level function and return the completion value.
734 pub fn execute(&mut self, func: &Function) -> Result<Value, RuntimeError> {
735 let reg_count = func.register_count as usize;
736 self.ensure_registers(reg_count);
737
738 self.frames.push(CallFrame {
739 func: func.clone(),
740 ip: 0,
741 base: 0,
742 return_reg: 0,
743 exception_handlers: Vec::new(),
744 upvalues: Vec::new(),
745 });
746
747 self.run()
748 }
749
750 /// Ensure the register file has at least `needed` slots.
751 fn ensure_registers(&mut self, needed: usize) {
752 if needed > self.registers.len() {
753 if needed > MAX_REGISTERS {
754 return;
755 }
756 self.registers.resize(needed, Value::Undefined);
757 }
758 }
759
760 /// Read a u8 from the current frame's bytecode and advance IP.
761 #[inline]
762 fn read_u8(frame: &mut CallFrame) -> u8 {
763 let b = frame.func.code[frame.ip];
764 frame.ip += 1;
765 b
766 }
767
768 /// Read a u16 (little-endian) from the current frame's bytecode and advance IP.
769 #[inline]
770 fn read_u16(frame: &mut CallFrame) -> u16 {
771 let lo = frame.func.code[frame.ip];
772 let hi = frame.func.code[frame.ip + 1];
773 frame.ip += 2;
774 u16::from_le_bytes([lo, hi])
775 }
776
777 /// Read an i32 (little-endian) from the current frame's bytecode and advance IP.
778 #[inline]
779 fn read_i32(frame: &mut CallFrame) -> i32 {
780 let bytes = [
781 frame.func.code[frame.ip],
782 frame.func.code[frame.ip + 1],
783 frame.func.code[frame.ip + 2],
784 frame.func.code[frame.ip + 3],
785 ];
786 frame.ip += 4;
787 i32::from_le_bytes(bytes)
788 }
789
790 /// Collect all GcRef values reachable from the mutator (roots for GC).
791 fn collect_roots(&self) -> Vec<GcRef> {
792 let mut roots = Vec::new();
793 for val in &self.registers {
794 if let Some(r) = val.gc_ref() {
795 roots.push(r);
796 }
797 }
798 for val in self.globals.values() {
799 if let Some(r) = val.gc_ref() {
800 roots.push(r);
801 }
802 }
803 for frame in &self.frames {
804 for &uv in &frame.upvalues {
805 roots.push(uv);
806 }
807 }
808 // Built-in prototype roots.
809 if let Some(r) = self.object_prototype {
810 roots.push(r);
811 }
812 if let Some(r) = self.array_prototype {
813 roots.push(r);
814 }
815 if let Some(r) = self.string_prototype {
816 roots.push(r);
817 }
818 if let Some(r) = self.number_prototype {
819 roots.push(r);
820 }
821 if let Some(r) = self.boolean_prototype {
822 roots.push(r);
823 }
824 roots
825 }
826
827 /// Main dispatch loop.
828 fn run(&mut self) -> Result<Value, RuntimeError> {
829 loop {
830 let fi = self.frames.len() - 1;
831
832 // Check if we've reached the end of bytecode.
833 if self.frames[fi].ip >= self.frames[fi].func.code.len() {
834 if self.frames.len() == 1 {
835 self.frames.pop();
836 return Ok(Value::Undefined);
837 }
838 let old = self.frames.pop().unwrap();
839 self.registers[old.return_reg] = Value::Undefined;
840 continue;
841 }
842
843 // Instruction limit check (for test harnesses).
844 if let Some(limit) = self.instruction_limit {
845 self.instructions_executed += 1;
846 if self.instructions_executed > limit {
847 return Err(RuntimeError {
848 kind: ErrorKind::Error,
849 message: "instruction limit exceeded".into(),
850 });
851 }
852 }
853
854 let opcode_byte = self.frames[fi].func.code[self.frames[fi].ip];
855 self.frames[fi].ip += 1;
856
857 let Some(op) = Op::from_byte(opcode_byte) else {
858 return Err(RuntimeError {
859 kind: ErrorKind::Error,
860 message: format!("unknown opcode: 0x{opcode_byte:02X}"),
861 });
862 };
863
864 match op {
865 // ── Register loads ──────────────────────────────
866 Op::LoadConst => {
867 let dst = Self::read_u8(&mut self.frames[fi]);
868 let idx = Self::read_u16(&mut self.frames[fi]) as usize;
869 let base = self.frames[fi].base;
870 let val = match &self.frames[fi].func.constants[idx] {
871 Constant::Number(n) => Value::Number(*n),
872 Constant::String(s) => Value::String(s.clone()),
873 };
874 self.registers[base + dst as usize] = val;
875 }
876 Op::LoadNull => {
877 let dst = Self::read_u8(&mut self.frames[fi]);
878 let base = self.frames[fi].base;
879 self.registers[base + dst as usize] = Value::Null;
880 }
881 Op::LoadUndefined => {
882 let dst = Self::read_u8(&mut self.frames[fi]);
883 let base = self.frames[fi].base;
884 self.registers[base + dst as usize] = Value::Undefined;
885 }
886 Op::LoadTrue => {
887 let dst = Self::read_u8(&mut self.frames[fi]);
888 let base = self.frames[fi].base;
889 self.registers[base + dst as usize] = Value::Boolean(true);
890 }
891 Op::LoadFalse => {
892 let dst = Self::read_u8(&mut self.frames[fi]);
893 let base = self.frames[fi].base;
894 self.registers[base + dst as usize] = Value::Boolean(false);
895 }
896 Op::LoadInt8 => {
897 let dst = Self::read_u8(&mut self.frames[fi]);
898 let val = Self::read_u8(&mut self.frames[fi]) as i8;
899 let base = self.frames[fi].base;
900 self.registers[base + dst as usize] = Value::Number(val as f64);
901 }
902 Op::Move => {
903 let dst = Self::read_u8(&mut self.frames[fi]);
904 let src = Self::read_u8(&mut self.frames[fi]);
905 let base = self.frames[fi].base;
906 let val = self.registers[base + src as usize].clone();
907 self.registers[base + dst as usize] = val;
908 }
909
910 // ── Global access ──────────────────────────────
911 Op::LoadGlobal => {
912 let dst = Self::read_u8(&mut self.frames[fi]);
913 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
914 let base = self.frames[fi].base;
915 let name = &self.frames[fi].func.names[name_idx];
916 let val = self.globals.get(name).cloned().unwrap_or(Value::Undefined);
917 self.registers[base + dst as usize] = val;
918 }
919 Op::StoreGlobal => {
920 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
921 let src = Self::read_u8(&mut self.frames[fi]);
922 let base = self.frames[fi].base;
923 let name = self.frames[fi].func.names[name_idx].clone();
924 let val = self.registers[base + src as usize].clone();
925 self.globals.insert(name, val);
926 }
927
928 // ── Arithmetic ─────────────────────────────────
929 Op::Add => {
930 let dst = Self::read_u8(&mut self.frames[fi]);
931 let lhs_r = Self::read_u8(&mut self.frames[fi]);
932 let rhs_r = Self::read_u8(&mut self.frames[fi]);
933 let base = self.frames[fi].base;
934 let result = add_values(
935 &self.registers[base + lhs_r as usize],
936 &self.registers[base + rhs_r as usize],
937 &self.gc,
938 );
939 self.registers[base + dst as usize] = result;
940 }
941 Op::Sub => {
942 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
943 let result = self.registers[base + lhs_r].to_number()
944 - self.registers[base + rhs_r].to_number();
945 self.registers[base + dst] = Value::Number(result);
946 }
947 Op::Mul => {
948 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
949 let result = self.registers[base + lhs_r].to_number()
950 * self.registers[base + rhs_r].to_number();
951 self.registers[base + dst] = Value::Number(result);
952 }
953 Op::Div => {
954 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
955 let result = self.registers[base + lhs_r].to_number()
956 / self.registers[base + rhs_r].to_number();
957 self.registers[base + dst] = Value::Number(result);
958 }
959 Op::Rem => {
960 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
961 let result = self.registers[base + lhs_r].to_number()
962 % self.registers[base + rhs_r].to_number();
963 self.registers[base + dst] = Value::Number(result);
964 }
965 Op::Exp => {
966 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
967 let result = self.registers[base + lhs_r]
968 .to_number()
969 .powf(self.registers[base + rhs_r].to_number());
970 self.registers[base + dst] = Value::Number(result);
971 }
972 Op::Neg => {
973 let dst = Self::read_u8(&mut self.frames[fi]);
974 let src = Self::read_u8(&mut self.frames[fi]);
975 let base = self.frames[fi].base;
976 let result = -self.registers[base + src as usize].to_number();
977 self.registers[base + dst as usize] = Value::Number(result);
978 }
979
980 // ── Bitwise ────────────────────────────────────
981 Op::BitAnd => {
982 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
983 let a = to_int32(&self.registers[base + lhs_r]);
984 let b = to_int32(&self.registers[base + rhs_r]);
985 self.registers[base + dst] = Value::Number((a & b) as f64);
986 }
987 Op::BitOr => {
988 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
989 let a = to_int32(&self.registers[base + lhs_r]);
990 let b = to_int32(&self.registers[base + rhs_r]);
991 self.registers[base + dst] = Value::Number((a | b) as f64);
992 }
993 Op::BitXor => {
994 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
995 let a = to_int32(&self.registers[base + lhs_r]);
996 let b = to_int32(&self.registers[base + rhs_r]);
997 self.registers[base + dst] = Value::Number((a ^ b) as f64);
998 }
999 Op::ShiftLeft => {
1000 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1001 let a = to_int32(&self.registers[base + lhs_r]);
1002 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
1003 self.registers[base + dst] = Value::Number((a << b) as f64);
1004 }
1005 Op::ShiftRight => {
1006 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1007 let a = to_int32(&self.registers[base + lhs_r]);
1008 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
1009 self.registers[base + dst] = Value::Number((a >> b) as f64);
1010 }
1011 Op::UShiftRight => {
1012 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1013 let a = to_uint32(&self.registers[base + lhs_r]);
1014 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
1015 self.registers[base + dst] = Value::Number((a >> b) as f64);
1016 }
1017 Op::BitNot => {
1018 let dst = Self::read_u8(&mut self.frames[fi]);
1019 let src = Self::read_u8(&mut self.frames[fi]);
1020 let base = self.frames[fi].base;
1021 let result = !to_int32(&self.registers[base + src as usize]);
1022 self.registers[base + dst as usize] = Value::Number(result as f64);
1023 }
1024
1025 // ── Comparison ─────────────────────────────────
1026 Op::Eq => {
1027 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1028 let result =
1029 abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
1030 self.registers[base + dst] = Value::Boolean(result);
1031 }
1032 Op::StrictEq => {
1033 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1034 let result =
1035 strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
1036 self.registers[base + dst] = Value::Boolean(result);
1037 }
1038 Op::NotEq => {
1039 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1040 let result =
1041 !abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
1042 self.registers[base + dst] = Value::Boolean(result);
1043 }
1044 Op::StrictNotEq => {
1045 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1046 let result =
1047 !strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
1048 self.registers[base + dst] = Value::Boolean(result);
1049 }
1050 Op::LessThan => {
1051 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1052 let result = abstract_relational(
1053 &self.registers[base + lhs_r],
1054 &self.registers[base + rhs_r],
1055 |ord| ord == std::cmp::Ordering::Less,
1056 );
1057 self.registers[base + dst] = Value::Boolean(result);
1058 }
1059 Op::LessEq => {
1060 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1061 let result = abstract_relational(
1062 &self.registers[base + lhs_r],
1063 &self.registers[base + rhs_r],
1064 |ord| ord != std::cmp::Ordering::Greater,
1065 );
1066 self.registers[base + dst] = Value::Boolean(result);
1067 }
1068 Op::GreaterThan => {
1069 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1070 let result = abstract_relational(
1071 &self.registers[base + lhs_r],
1072 &self.registers[base + rhs_r],
1073 |ord| ord == std::cmp::Ordering::Greater,
1074 );
1075 self.registers[base + dst] = Value::Boolean(result);
1076 }
1077 Op::GreaterEq => {
1078 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1079 let result = abstract_relational(
1080 &self.registers[base + lhs_r],
1081 &self.registers[base + rhs_r],
1082 |ord| ord != std::cmp::Ordering::Less,
1083 );
1084 self.registers[base + dst] = Value::Boolean(result);
1085 }
1086
1087 // ── Logical / unary ────────────────────────────
1088 Op::LogicalNot => {
1089 let dst = Self::read_u8(&mut self.frames[fi]);
1090 let src = Self::read_u8(&mut self.frames[fi]);
1091 let base = self.frames[fi].base;
1092 let result = !self.registers[base + src as usize].to_boolean();
1093 self.registers[base + dst as usize] = Value::Boolean(result);
1094 }
1095 Op::TypeOf => {
1096 let dst = Self::read_u8(&mut self.frames[fi]);
1097 let src = Self::read_u8(&mut self.frames[fi]);
1098 let base = self.frames[fi].base;
1099 let t = self.registers[base + src as usize].type_of();
1100 self.registers[base + dst as usize] = Value::String(t.to_string());
1101 }
1102 Op::InstanceOf => {
1103 let dst = Self::read_u8(&mut self.frames[fi]);
1104 let lhs_r = Self::read_u8(&mut self.frames[fi]);
1105 let rhs_r = Self::read_u8(&mut self.frames[fi]);
1106 let base = self.frames[fi].base;
1107 let result = match (
1108 &self.registers[base + lhs_r as usize],
1109 &self.registers[base + rhs_r as usize],
1110 ) {
1111 (Value::Object(obj_ref), Value::Function(ctor_ref)) => {
1112 gc_instanceof(&self.gc, *obj_ref, *ctor_ref)
1113 }
1114 (_, Value::Function(_)) => false,
1115 _ => {
1116 return Err(RuntimeError::type_error(
1117 "Right-hand side of instanceof is not callable",
1118 ));
1119 }
1120 };
1121 self.registers[base + dst as usize] = Value::Boolean(result);
1122 }
1123 Op::In => {
1124 let dst = Self::read_u8(&mut self.frames[fi]);
1125 let key_r = Self::read_u8(&mut self.frames[fi]);
1126 let obj_r = Self::read_u8(&mut self.frames[fi]);
1127 let base = self.frames[fi].base;
1128 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1129 let result = match self.registers[base + obj_r as usize] {
1130 Value::Object(gc_ref) => {
1131 Value::Boolean(gc_has_property(&self.gc, gc_ref, &key))
1132 }
1133 _ => {
1134 return Err(RuntimeError::type_error(
1135 "Cannot use 'in' operator to search for property in non-object",
1136 ));
1137 }
1138 };
1139 self.registers[base + dst as usize] = result;
1140 }
1141 Op::Void => {
1142 let dst = Self::read_u8(&mut self.frames[fi]);
1143 let _src = Self::read_u8(&mut self.frames[fi]);
1144 let base = self.frames[fi].base;
1145 self.registers[base + dst as usize] = Value::Undefined;
1146 }
1147
1148 // ── Control flow ───────────────────────────────
1149 Op::Jump => {
1150 let offset = Self::read_i32(&mut self.frames[fi]);
1151 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1152 }
1153 Op::JumpIfTrue => {
1154 let reg = Self::read_u8(&mut self.frames[fi]);
1155 let offset = Self::read_i32(&mut self.frames[fi]);
1156 let base = self.frames[fi].base;
1157 if self.registers[base + reg as usize].to_boolean() {
1158 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1159 }
1160 }
1161 Op::JumpIfFalse => {
1162 let reg = Self::read_u8(&mut self.frames[fi]);
1163 let offset = Self::read_i32(&mut self.frames[fi]);
1164 let base = self.frames[fi].base;
1165 if !self.registers[base + reg as usize].to_boolean() {
1166 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1167 }
1168 }
1169 Op::JumpIfNullish => {
1170 let reg = Self::read_u8(&mut self.frames[fi]);
1171 let offset = Self::read_i32(&mut self.frames[fi]);
1172 let base = self.frames[fi].base;
1173 if self.registers[base + reg as usize].is_nullish() {
1174 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1175 }
1176 }
1177
1178 // ── Functions / calls ──────────────────────────
1179 Op::Call => {
1180 let dst = Self::read_u8(&mut self.frames[fi]);
1181 let func_r = Self::read_u8(&mut self.frames[fi]);
1182 let args_start = Self::read_u8(&mut self.frames[fi]);
1183 let arg_count = Self::read_u8(&mut self.frames[fi]);
1184 let base = self.frames[fi].base;
1185
1186 // Extract function GcRef.
1187 let func_gc_ref = match self.registers[base + func_r as usize] {
1188 Value::Function(r) => r,
1189 _ => {
1190 let desc =
1191 self.registers[base + func_r as usize].to_js_string(&self.gc);
1192 let err = RuntimeError::type_error(format!("{desc} is not a function"));
1193 let err_val = err.to_value(&mut self.gc);
1194 if !self.handle_exception(err_val) {
1195 return Err(err);
1196 }
1197 continue;
1198 }
1199 };
1200
1201 // Collect arguments.
1202 let mut args = Vec::with_capacity(arg_count as usize);
1203 for i in 0..arg_count {
1204 args.push(self.registers[base + (args_start + i) as usize].clone());
1205 }
1206
1207 // Read function data from GC (scoped borrow).
1208 let call_info = {
1209 match self.gc.get(func_gc_ref) {
1210 Some(HeapObject::Function(fdata)) => match &fdata.kind {
1211 FunctionKind::Native(n) => CallInfo::Native(n.callback),
1212 FunctionKind::Bytecode(bc) => {
1213 CallInfo::Bytecode(bc.func.clone(), fdata.upvalues.clone())
1214 }
1215 },
1216 _ => {
1217 let err = RuntimeError::type_error("not a function");
1218 let err_val = err.to_value(&mut self.gc);
1219 if !self.handle_exception(err_val) {
1220 return Err(err);
1221 }
1222 continue;
1223 }
1224 }
1225 };
1226
1227 match call_info {
1228 CallInfo::Native(callback) => {
1229 let this = self
1230 .globals
1231 .get("this")
1232 .cloned()
1233 .unwrap_or(Value::Undefined);
1234 let mut ctx = NativeContext {
1235 gc: &mut self.gc,
1236 this,
1237 };
1238 match callback(&args, &mut ctx) {
1239 Ok(val) => {
1240 self.registers[base + dst as usize] = val;
1241 }
1242 Err(err) => {
1243 let err_val = err.to_value(&mut self.gc);
1244 if !self.handle_exception(err_val) {
1245 return Err(err);
1246 }
1247 }
1248 }
1249 }
1250 CallInfo::Bytecode(callee_func, callee_upvalues) => {
1251 if self.frames.len() >= MAX_CALL_DEPTH {
1252 let err =
1253 RuntimeError::range_error("Maximum call stack size exceeded");
1254 let err_val = err.to_value(&mut self.gc);
1255 if !self.handle_exception(err_val) {
1256 return Err(err);
1257 }
1258 continue;
1259 }
1260
1261 let callee_base = base + self.frames[fi].func.register_count as usize;
1262 let callee_regs = callee_func.register_count as usize;
1263 self.ensure_registers(callee_base + callee_regs);
1264
1265 // Copy arguments into callee's registers.
1266 for i in 0..callee_func.param_count.min(arg_count) {
1267 self.registers[callee_base + i as usize] = args[i as usize].clone();
1268 }
1269 // Fill remaining params with undefined.
1270 for i in arg_count..callee_func.param_count {
1271 self.registers[callee_base + i as usize] = Value::Undefined;
1272 }
1273
1274 self.frames.push(CallFrame {
1275 func: callee_func,
1276 ip: 0,
1277 base: callee_base,
1278 return_reg: base + dst as usize,
1279 exception_handlers: Vec::new(),
1280 upvalues: callee_upvalues,
1281 });
1282 }
1283 }
1284 }
1285 Op::Return => {
1286 let reg = Self::read_u8(&mut self.frames[fi]);
1287 let base = self.frames[fi].base;
1288 let val = self.registers[base + reg as usize].clone();
1289
1290 if self.frames.len() == 1 {
1291 self.frames.pop();
1292 return Ok(val);
1293 }
1294
1295 let old = self.frames.pop().unwrap();
1296 self.registers[old.return_reg] = val;
1297 }
1298 Op::Throw => {
1299 let reg = Self::read_u8(&mut self.frames[fi]);
1300 let base = self.frames[fi].base;
1301 let val = self.registers[base + reg as usize].clone();
1302
1303 if !self.handle_exception(val) {
1304 let msg = self.registers[base + reg as usize].to_js_string(&self.gc);
1305 return Err(RuntimeError {
1306 kind: ErrorKind::Error,
1307 message: msg,
1308 });
1309 }
1310 }
1311 Op::CreateClosure => {
1312 let dst = Self::read_u8(&mut self.frames[fi]);
1313 let func_idx = Self::read_u16(&mut self.frames[fi]) as usize;
1314 let base = self.frames[fi].base;
1315 let inner_func = self.frames[fi].func.functions[func_idx].clone();
1316 let name = inner_func.name.clone();
1317
1318 // Resolve upvalues from the parent scope.
1319 let mut upvalues = Vec::with_capacity(inner_func.upvalue_defs.len());
1320 for def in &inner_func.upvalue_defs {
1321 let cell_ref = if def.is_local {
1322 // Parent has a cell in register `def.index`.
1323 match &self.registers[base + def.index as usize] {
1324 Value::Object(r) => *r,
1325 _ => {
1326 return Err(RuntimeError {
1327 kind: ErrorKind::Error,
1328 message:
1329 "CreateClosure: upvalue register does not hold a cell"
1330 .into(),
1331 });
1332 }
1333 }
1334 } else {
1335 // Transitive: parent's own upvalue at `def.index`.
1336 self.frames[fi].upvalues[def.index as usize]
1337 };
1338 upvalues.push(cell_ref);
1339 }
1340
1341 // Create a .prototype object for the function (for instanceof).
1342 let proto_obj = self.gc.alloc(HeapObject::Object(ObjectData::new()));
1343 let gc_ref = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
1344 name,
1345 kind: FunctionKind::Bytecode(BytecodeFunc { func: inner_func }),
1346 prototype_obj: Some(proto_obj),
1347 properties: HashMap::new(),
1348 upvalues,
1349 })));
1350 // Set .prototype.constructor = this function.
1351 if let Some(HeapObject::Object(data)) = self.gc.get_mut(proto_obj) {
1352 data.properties.insert(
1353 "constructor".to_string(),
1354 Property {
1355 value: Value::Function(gc_ref),
1356 writable: true,
1357 enumerable: false,
1358 configurable: true,
1359 },
1360 );
1361 }
1362 self.registers[base + dst as usize] = Value::Function(gc_ref);
1363
1364 // Trigger GC if needed.
1365 if self.gc.should_collect() {
1366 let roots = self.collect_roots();
1367 self.gc.collect(&roots);
1368 }
1369 }
1370
1371 // ── Object / property ──────────────────────────
1372 Op::GetProperty => {
1373 let dst = Self::read_u8(&mut self.frames[fi]);
1374 let obj_r = Self::read_u8(&mut self.frames[fi]);
1375 let key_r = Self::read_u8(&mut self.frames[fi]);
1376 let base = self.frames[fi].base;
1377 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1378 let val = match self.registers[base + obj_r as usize] {
1379 Value::Object(gc_ref) | Value::Function(gc_ref) => {
1380 gc_get_property(&self.gc, gc_ref, &key)
1381 }
1382 Value::String(ref s) => {
1383 let v = string_get_property(s, &key);
1384 if matches!(v, Value::Undefined) {
1385 self.string_prototype
1386 .map(|p| gc_get_property(&self.gc, p, &key))
1387 .unwrap_or(Value::Undefined)
1388 } else {
1389 v
1390 }
1391 }
1392 Value::Number(_) => self
1393 .number_prototype
1394 .map(|p| gc_get_property(&self.gc, p, &key))
1395 .unwrap_or(Value::Undefined),
1396 Value::Boolean(_) => self
1397 .boolean_prototype
1398 .map(|p| gc_get_property(&self.gc, p, &key))
1399 .unwrap_or(Value::Undefined),
1400 _ => Value::Undefined,
1401 };
1402 self.registers[base + dst as usize] = val;
1403 }
1404 Op::SetProperty => {
1405 let obj_r = Self::read_u8(&mut self.frames[fi]);
1406 let key_r = Self::read_u8(&mut self.frames[fi]);
1407 let val_r = Self::read_u8(&mut self.frames[fi]);
1408 let base = self.frames[fi].base;
1409 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1410 let val = self.registers[base + val_r as usize].clone();
1411 match self.registers[base + obj_r as usize] {
1412 Value::Object(gc_ref) => {
1413 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1414 if let Some(prop) = data.properties.get_mut(&key) {
1415 if prop.writable {
1416 prop.value = val;
1417 }
1418 } else {
1419 data.properties.insert(key, Property::data(val));
1420 }
1421 }
1422 }
1423 Value::Function(gc_ref) => {
1424 if let Some(HeapObject::Function(fdata)) = self.gc.get_mut(gc_ref) {
1425 if let Some(prop) = fdata.properties.get_mut(&key) {
1426 if prop.writable {
1427 prop.value = val;
1428 }
1429 } else {
1430 fdata.properties.insert(key, Property::data(val));
1431 }
1432 }
1433 }
1434 _ => {}
1435 }
1436 }
1437 Op::CreateObject => {
1438 let dst = Self::read_u8(&mut self.frames[fi]);
1439 let base = self.frames[fi].base;
1440 let mut obj = ObjectData::new();
1441 obj.prototype = self.object_prototype;
1442 let gc_ref = self.gc.alloc(HeapObject::Object(obj));
1443 self.registers[base + dst as usize] = Value::Object(gc_ref);
1444
1445 if self.gc.should_collect() {
1446 let roots = self.collect_roots();
1447 self.gc.collect(&roots);
1448 }
1449 }
1450 Op::CreateArray => {
1451 let dst = Self::read_u8(&mut self.frames[fi]);
1452 let base = self.frames[fi].base;
1453 let mut obj = ObjectData::new();
1454 obj.prototype = self.array_prototype;
1455 obj.properties.insert(
1456 "length".to_string(),
1457 Property {
1458 value: Value::Number(0.0),
1459 writable: true,
1460 enumerable: false,
1461 configurable: false,
1462 },
1463 );
1464 let gc_ref = self.gc.alloc(HeapObject::Object(obj));
1465 self.registers[base + dst as usize] = Value::Object(gc_ref);
1466
1467 if self.gc.should_collect() {
1468 let roots = self.collect_roots();
1469 self.gc.collect(&roots);
1470 }
1471 }
1472 Op::GetPropertyByName => {
1473 let dst = Self::read_u8(&mut self.frames[fi]);
1474 let obj_r = Self::read_u8(&mut self.frames[fi]);
1475 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
1476 let base = self.frames[fi].base;
1477 let key = self.frames[fi].func.names[name_idx].clone();
1478 let val = match self.registers[base + obj_r as usize] {
1479 Value::Object(gc_ref) | Value::Function(gc_ref) => {
1480 gc_get_property(&self.gc, gc_ref, &key)
1481 }
1482 Value::String(ref s) => {
1483 let v = string_get_property(s, &key);
1484 if matches!(v, Value::Undefined) {
1485 self.string_prototype
1486 .map(|p| gc_get_property(&self.gc, p, &key))
1487 .unwrap_or(Value::Undefined)
1488 } else {
1489 v
1490 }
1491 }
1492 Value::Number(_) => self
1493 .number_prototype
1494 .map(|p| gc_get_property(&self.gc, p, &key))
1495 .unwrap_or(Value::Undefined),
1496 Value::Boolean(_) => self
1497 .boolean_prototype
1498 .map(|p| gc_get_property(&self.gc, p, &key))
1499 .unwrap_or(Value::Undefined),
1500 _ => Value::Undefined,
1501 };
1502 self.registers[base + dst as usize] = val;
1503 }
1504 Op::SetPropertyByName => {
1505 let obj_r = Self::read_u8(&mut self.frames[fi]);
1506 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
1507 let val_r = Self::read_u8(&mut self.frames[fi]);
1508 let base = self.frames[fi].base;
1509 let key = self.frames[fi].func.names[name_idx].clone();
1510 let val = self.registers[base + val_r as usize].clone();
1511 match self.registers[base + obj_r as usize] {
1512 Value::Object(gc_ref) => {
1513 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1514 if let Some(prop) = data.properties.get_mut(&key) {
1515 if prop.writable {
1516 prop.value = val;
1517 }
1518 } else {
1519 data.properties.insert(key, Property::data(val));
1520 }
1521 }
1522 }
1523 Value::Function(gc_ref) => {
1524 if let Some(HeapObject::Function(fdata)) = self.gc.get_mut(gc_ref) {
1525 if let Some(prop) = fdata.properties.get_mut(&key) {
1526 if prop.writable {
1527 prop.value = val;
1528 }
1529 } else {
1530 fdata.properties.insert(key, Property::data(val));
1531 }
1532 }
1533 }
1534 _ => {}
1535 }
1536 }
1537
1538 // ── Misc ───────────────────────────────────────
1539 Op::Delete => {
1540 let dst = Self::read_u8(&mut self.frames[fi]);
1541 let obj_r = Self::read_u8(&mut self.frames[fi]);
1542 let key_r = Self::read_u8(&mut self.frames[fi]);
1543 let base = self.frames[fi].base;
1544 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1545 let result =
1546 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] {
1547 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1548 match data.properties.get(&key) {
1549 Some(prop) if !prop.configurable => false,
1550 Some(_) => {
1551 data.properties.remove(&key);
1552 true
1553 }
1554 None => true,
1555 }
1556 } else {
1557 true
1558 }
1559 } else {
1560 true
1561 };
1562 self.registers[base + dst as usize] = Value::Boolean(result);
1563 }
1564 Op::ForInInit => {
1565 let dst = Self::read_u8(&mut self.frames[fi]);
1566 let obj_r = Self::read_u8(&mut self.frames[fi]);
1567 let base = self.frames[fi].base;
1568 let keys = match self.registers[base + obj_r as usize] {
1569 Value::Object(gc_ref) => gc_enumerate_keys(&self.gc, gc_ref),
1570 _ => Vec::new(),
1571 };
1572 // Store keys as an array object.
1573 let mut arr = ObjectData::new();
1574 for (i, key) in keys.iter().enumerate() {
1575 arr.properties
1576 .insert(i.to_string(), Property::data(Value::String(key.clone())));
1577 }
1578 arr.properties.insert(
1579 "length".to_string(),
1580 Property {
1581 value: Value::Number(keys.len() as f64),
1582 writable: true,
1583 enumerable: false,
1584 configurable: false,
1585 },
1586 );
1587 let gc_ref = self.gc.alloc(HeapObject::Object(arr));
1588 self.registers[base + dst as usize] = Value::Object(gc_ref);
1589 }
1590 Op::ForInNext => {
1591 let dst_val = Self::read_u8(&mut self.frames[fi]);
1592 let dst_done = Self::read_u8(&mut self.frames[fi]);
1593 let keys_r = Self::read_u8(&mut self.frames[fi]);
1594 let idx_r = Self::read_u8(&mut self.frames[fi]);
1595 let base = self.frames[fi].base;
1596 let idx = self.registers[base + idx_r as usize].to_number() as usize;
1597 let len = match self.registers[base + keys_r as usize] {
1598 Value::Object(gc_ref) => {
1599 gc_get_property(&self.gc, gc_ref, "length").to_number() as usize
1600 }
1601 _ => 0,
1602 };
1603 if idx >= len {
1604 self.registers[base + dst_done as usize] = Value::Boolean(true);
1605 self.registers[base + dst_val as usize] = Value::Undefined;
1606 } else {
1607 let key_str = idx.to_string();
1608 let key = match self.registers[base + keys_r as usize] {
1609 Value::Object(gc_ref) => gc_get_property(&self.gc, gc_ref, &key_str),
1610 _ => Value::Undefined,
1611 };
1612 self.registers[base + dst_val as usize] = key;
1613 self.registers[base + dst_done as usize] = Value::Boolean(false);
1614 }
1615 }
1616 Op::SetPrototype => {
1617 let obj_r = Self::read_u8(&mut self.frames[fi]);
1618 let proto_r = Self::read_u8(&mut self.frames[fi]);
1619 let base = self.frames[fi].base;
1620 let proto = match &self.registers[base + proto_r as usize] {
1621 Value::Object(r) => Some(*r),
1622 Value::Null => None,
1623 _ => None,
1624 };
1625 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] {
1626 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1627 data.prototype = proto;
1628 }
1629 }
1630 }
1631 Op::GetPrototype => {
1632 let dst = Self::read_u8(&mut self.frames[fi]);
1633 let obj_r = Self::read_u8(&mut self.frames[fi]);
1634 let base = self.frames[fi].base;
1635 let proto = match self.registers[base + obj_r as usize] {
1636 Value::Object(gc_ref) => match self.gc.get(gc_ref) {
1637 Some(HeapObject::Object(data)) => {
1638 data.prototype.map(Value::Object).unwrap_or(Value::Null)
1639 }
1640 _ => Value::Null,
1641 },
1642 _ => Value::Null,
1643 };
1644 self.registers[base + dst as usize] = proto;
1645 }
1646
1647 // ── Exception handling ─────────────────────────────
1648 Op::PushExceptionHandler => {
1649 let catch_reg = Self::read_u8(&mut self.frames[fi]);
1650 let offset = Self::read_i32(&mut self.frames[fi]);
1651 let catch_ip = (self.frames[fi].ip as i32 + offset) as usize;
1652 self.frames[fi].exception_handlers.push(ExceptionHandler {
1653 catch_ip,
1654 catch_reg,
1655 });
1656 }
1657 Op::PopExceptionHandler => {
1658 self.frames[fi].exception_handlers.pop();
1659 }
1660
1661 // ── Closure / upvalue ops ─────────────────────────
1662 Op::NewCell => {
1663 let dst = Self::read_u8(&mut self.frames[fi]);
1664 let base = self.frames[fi].base;
1665 let cell = self.gc.alloc(HeapObject::Cell(Value::Undefined));
1666 self.registers[base + dst as usize] = Value::Object(cell);
1667
1668 if self.gc.should_collect() {
1669 let roots = self.collect_roots();
1670 self.gc.collect(&roots);
1671 }
1672 }
1673 Op::CellLoad => {
1674 let dst = Self::read_u8(&mut self.frames[fi]);
1675 let cell_reg = Self::read_u8(&mut self.frames[fi]);
1676 let base = self.frames[fi].base;
1677 let cell_ref = match &self.registers[base + cell_reg as usize] {
1678 Value::Object(r) => *r,
1679 _ => {
1680 return Err(RuntimeError {
1681 kind: ErrorKind::Error,
1682 message: "CellLoad: register does not hold a cell".into(),
1683 });
1684 }
1685 };
1686 let val = match self.gc.get(cell_ref) {
1687 Some(HeapObject::Cell(v)) => v.clone(),
1688 _ => Value::Undefined,
1689 };
1690 self.registers[base + dst as usize] = val;
1691 }
1692 Op::CellStore => {
1693 let cell_reg = Self::read_u8(&mut self.frames[fi]);
1694 let src = Self::read_u8(&mut self.frames[fi]);
1695 let base = self.frames[fi].base;
1696 let cell_ref = match &self.registers[base + cell_reg as usize] {
1697 Value::Object(r) => *r,
1698 _ => {
1699 return Err(RuntimeError {
1700 kind: ErrorKind::Error,
1701 message: "CellStore: register does not hold a cell".into(),
1702 });
1703 }
1704 };
1705 let val = self.registers[base + src as usize].clone();
1706 if let Some(HeapObject::Cell(cell_val)) = self.gc.get_mut(cell_ref) {
1707 *cell_val = val;
1708 }
1709 }
1710 Op::LoadUpvalue => {
1711 let dst = Self::read_u8(&mut self.frames[fi]);
1712 let idx = Self::read_u8(&mut self.frames[fi]) as usize;
1713 let base = self.frames[fi].base;
1714 let cell_ref = self.frames[fi].upvalues[idx];
1715 let val = match self.gc.get(cell_ref) {
1716 Some(HeapObject::Cell(v)) => v.clone(),
1717 _ => Value::Undefined,
1718 };
1719 self.registers[base + dst as usize] = val;
1720 }
1721 Op::StoreUpvalue => {
1722 let idx = Self::read_u8(&mut self.frames[fi]) as usize;
1723 let src = Self::read_u8(&mut self.frames[fi]);
1724 let base = self.frames[fi].base;
1725 let val = self.registers[base + src as usize].clone();
1726 let cell_ref = self.frames[fi].upvalues[idx];
1727 if let Some(HeapObject::Cell(cell_val)) = self.gc.get_mut(cell_ref) {
1728 *cell_val = val;
1729 }
1730 }
1731 }
1732 }
1733 }
1734
1735 /// Read 3 register operands and return (dst, lhs, rhs, base) as usize indices.
1736 fn read_3reg(&mut self, fi: usize) -> (usize, usize, usize, usize) {
1737 let dst = Self::read_u8(&mut self.frames[fi]) as usize;
1738 let lhs = Self::read_u8(&mut self.frames[fi]) as usize;
1739 let rhs = Self::read_u8(&mut self.frames[fi]) as usize;
1740 let base = self.frames[fi].base;
1741 (dst, lhs, rhs, base)
1742 }
1743
1744 /// Try to find an exception handler on the call stack.
1745 /// Returns true if a handler was found (execution resumes there).
1746 fn handle_exception(&mut self, value: Value) -> bool {
1747 while let Some(frame) = self.frames.last_mut() {
1748 if let Some(handler) = frame.exception_handlers.pop() {
1749 let base = frame.base;
1750 frame.ip = handler.catch_ip;
1751 self.registers[base + handler.catch_reg as usize] = value;
1752 return true;
1753 }
1754 if self.frames.len() == 1 {
1755 break;
1756 }
1757 self.frames.pop();
1758 }
1759 false
1760 }
1761
1762 /// Register a native function as a global.
1763 pub fn define_native(
1764 &mut self,
1765 name: &str,
1766 callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>,
1767 ) {
1768 let gc_ref = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
1769 name: name.to_string(),
1770 kind: FunctionKind::Native(NativeFunc { callback }),
1771 prototype_obj: None,
1772 properties: HashMap::new(),
1773 upvalues: Vec::new(),
1774 })));
1775 self.globals
1776 .insert(name.to_string(), Value::Function(gc_ref));
1777 }
1778
1779 /// Get a global variable value.
1780 pub fn get_global(&self, name: &str) -> Option<&Value> {
1781 self.globals.get(name)
1782 }
1783
1784 /// Set a global variable.
1785 pub fn set_global(&mut self, name: &str, val: Value) {
1786 self.globals.insert(name.to_string(), val);
1787 }
1788}
1789
1790impl Default for Vm {
1791 fn default() -> Self {
1792 Self::new()
1793 }
1794}
1795
1796/// Internal enum to avoid holding a GC borrow across the call setup.
1797enum CallInfo {
1798 Native(fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>),
1799 Bytecode(Function, Vec<GcRef>),
1800}
1801
1802// ── Tests ────────────────────────────────────────────────────
1803
1804#[cfg(test)]
1805mod tests {
1806 use super::*;
1807 use crate::bytecode::{BytecodeBuilder, Constant, Op};
1808 use crate::compiler;
1809 use crate::parser::Parser;
1810
1811 /// Helper: compile and execute JS source, return the completion value.
1812 fn eval(source: &str) -> Result<Value, RuntimeError> {
1813 let program = Parser::parse(source).expect("parse failed");
1814 let func = compiler::compile(&program).expect("compile failed");
1815 let mut vm = Vm::new();
1816 vm.execute(&func)
1817 }
1818
1819 // ── Value tests ─────────────────────────────────────────
1820
1821 #[test]
1822 fn test_to_boolean() {
1823 let mut gc: Gc<HeapObject> = Gc::new();
1824 assert!(!Value::Undefined.to_boolean());
1825 assert!(!Value::Null.to_boolean());
1826 assert!(!Value::Boolean(false).to_boolean());
1827 assert!(Value::Boolean(true).to_boolean());
1828 assert!(!Value::Number(0.0).to_boolean());
1829 assert!(!Value::Number(f64::NAN).to_boolean());
1830 assert!(Value::Number(1.0).to_boolean());
1831 assert!(!Value::String(String::new()).to_boolean());
1832 assert!(Value::String("hello".to_string()).to_boolean());
1833 let obj_ref = gc.alloc(HeapObject::Object(ObjectData::new()));
1834 assert!(Value::Object(obj_ref).to_boolean());
1835 }
1836
1837 #[test]
1838 fn test_to_number() {
1839 assert!(Value::Undefined.to_number().is_nan());
1840 assert_eq!(Value::Null.to_number(), 0.0);
1841 assert_eq!(Value::Boolean(true).to_number(), 1.0);
1842 assert_eq!(Value::Boolean(false).to_number(), 0.0);
1843 assert_eq!(Value::Number(42.0).to_number(), 42.0);
1844 assert_eq!(Value::String("42".to_string()).to_number(), 42.0);
1845 assert_eq!(Value::String("".to_string()).to_number(), 0.0);
1846 assert!(Value::String("abc".to_string()).to_number().is_nan());
1847 }
1848
1849 #[test]
1850 fn test_type_of() {
1851 let mut gc: Gc<HeapObject> = Gc::new();
1852 assert_eq!(Value::Undefined.type_of(), "undefined");
1853 assert_eq!(Value::Null.type_of(), "object");
1854 assert_eq!(Value::Boolean(true).type_of(), "boolean");
1855 assert_eq!(Value::Number(1.0).type_of(), "number");
1856 assert_eq!(Value::String("hi".to_string()).type_of(), "string");
1857 let obj_ref = gc.alloc(HeapObject::Object(ObjectData::new()));
1858 assert_eq!(Value::Object(obj_ref).type_of(), "object");
1859 }
1860
1861 // ── VM bytecode-level tests ─────────────────────────────
1862
1863 #[test]
1864 fn test_load_const_number() {
1865 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1866 b.func.register_count = 1;
1867 let ci = b.add_constant(Constant::Number(42.0));
1868 b.emit_reg_u16(Op::LoadConst, 0, ci);
1869 b.emit_reg(Op::Return, 0);
1870 let func = b.finish();
1871
1872 let mut vm = Vm::new();
1873 let result = vm.execute(&func).unwrap();
1874 match result {
1875 Value::Number(n) => assert_eq!(n, 42.0),
1876 _ => panic!("expected Number, got {result:?}"),
1877 }
1878 }
1879
1880 #[test]
1881 fn test_arithmetic_ops() {
1882 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1883 b.func.register_count = 3;
1884 let c10 = b.add_constant(Constant::Number(10.0));
1885 let c3 = b.add_constant(Constant::Number(3.0));
1886 b.emit_reg_u16(Op::LoadConst, 0, c10);
1887 b.emit_reg_u16(Op::LoadConst, 1, c3);
1888 b.emit_reg3(Op::Add, 2, 0, 1);
1889 b.emit_reg(Op::Return, 2);
1890 let func = b.finish();
1891
1892 let mut vm = Vm::new();
1893 match vm.execute(&func).unwrap() {
1894 Value::Number(n) => assert_eq!(n, 13.0),
1895 v => panic!("expected 13, got {v:?}"),
1896 }
1897 }
1898
1899 #[test]
1900 fn test_string_concat() {
1901 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1902 b.func.register_count = 3;
1903 let c1 = b.add_constant(Constant::String("hello".into()));
1904 let c2 = b.add_constant(Constant::String(" world".into()));
1905 b.emit_reg_u16(Op::LoadConst, 0, c1);
1906 b.emit_reg_u16(Op::LoadConst, 1, c2);
1907 b.emit_reg3(Op::Add, 2, 0, 1);
1908 b.emit_reg(Op::Return, 2);
1909 let func = b.finish();
1910
1911 let mut vm = Vm::new();
1912 match vm.execute(&func).unwrap() {
1913 Value::String(s) => assert_eq!(s, "hello world"),
1914 v => panic!("expected string, got {v:?}"),
1915 }
1916 }
1917
1918 #[test]
1919 fn test_jump_if_false() {
1920 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1921 b.func.register_count = 2;
1922 b.emit_reg(Op::LoadFalse, 0);
1923 let patch = b.emit_cond_jump(Op::JumpIfFalse, 0);
1924 b.emit_load_int8(1, 1);
1925 let skip = b.emit_jump(Op::Jump);
1926 b.patch_jump(patch);
1927 b.emit_load_int8(1, 2);
1928 b.patch_jump(skip);
1929 b.emit_reg(Op::Return, 1);
1930 let func = b.finish();
1931
1932 let mut vm = Vm::new();
1933 match vm.execute(&func).unwrap() {
1934 Value::Number(n) => assert_eq!(n, 2.0),
1935 v => panic!("expected 2, got {v:?}"),
1936 }
1937 }
1938
1939 #[test]
1940 fn test_globals() {
1941 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1942 b.func.register_count = 2;
1943 let name = b.add_name("x");
1944 b.emit_load_int8(0, 42);
1945 b.emit_store_global(name, 0);
1946 b.emit_load_global(1, name);
1947 b.emit_reg(Op::Return, 1);
1948 let func = b.finish();
1949
1950 let mut vm = Vm::new();
1951 match vm.execute(&func).unwrap() {
1952 Value::Number(n) => assert_eq!(n, 42.0),
1953 v => panic!("expected 42, got {v:?}"),
1954 }
1955 }
1956
1957 #[test]
1958 fn test_function_call() {
1959 let mut inner_b = BytecodeBuilder::new("add1".into(), 1);
1960 inner_b.func.register_count = 2;
1961 inner_b.emit_load_int8(1, 1);
1962 inner_b.emit_reg3(Op::Add, 0, 0, 1);
1963 inner_b.emit_reg(Op::Return, 0);
1964 let inner = inner_b.finish();
1965
1966 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1967 b.func.register_count = 4;
1968 let fi = b.add_function(inner);
1969 b.emit_reg_u16(Op::CreateClosure, 0, fi);
1970 b.emit_load_int8(1, 10);
1971 b.emit_call(2, 0, 1, 1);
1972 b.emit_reg(Op::Return, 2);
1973 let func = b.finish();
1974
1975 let mut vm = Vm::new();
1976 match vm.execute(&func).unwrap() {
1977 Value::Number(n) => assert_eq!(n, 11.0),
1978 v => panic!("expected 11, got {v:?}"),
1979 }
1980 }
1981
1982 #[test]
1983 fn test_native_function() {
1984 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1985 b.func.register_count = 3;
1986 let name = b.add_name("double");
1987 b.emit_load_global(0, name);
1988 b.emit_load_int8(1, 21);
1989 b.emit_call(2, 0, 1, 1);
1990 b.emit_reg(Op::Return, 2);
1991 let func = b.finish();
1992
1993 let mut vm = Vm::new();
1994 vm.define_native("double", |args, _ctx| {
1995 let n = args.first().unwrap_or(&Value::Undefined).to_number();
1996 Ok(Value::Number(n * 2.0))
1997 });
1998 match vm.execute(&func).unwrap() {
1999 Value::Number(n) => assert_eq!(n, 42.0),
2000 v => panic!("expected 42, got {v:?}"),
2001 }
2002 }
2003
2004 #[test]
2005 fn test_object_property() {
2006 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2007 b.func.register_count = 3;
2008 let name = b.add_name("x");
2009 b.emit_reg(Op::CreateObject, 0);
2010 b.emit_load_int8(1, 42);
2011 b.emit_set_prop_name(0, name, 1);
2012 b.emit_get_prop_name(2, 0, name);
2013 b.emit_reg(Op::Return, 2);
2014 let func = b.finish();
2015
2016 let mut vm = Vm::new();
2017 match vm.execute(&func).unwrap() {
2018 Value::Number(n) => assert_eq!(n, 42.0),
2019 v => panic!("expected 42, got {v:?}"),
2020 }
2021 }
2022
2023 #[test]
2024 fn test_typeof_operator() {
2025 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2026 b.func.register_count = 2;
2027 b.emit_load_int8(0, 5);
2028 b.emit_reg_reg(Op::TypeOf, 1, 0);
2029 b.emit_reg(Op::Return, 1);
2030 let func = b.finish();
2031
2032 let mut vm = Vm::new();
2033 match vm.execute(&func).unwrap() {
2034 Value::String(s) => assert_eq!(s, "number"),
2035 v => panic!("expected 'number', got {v:?}"),
2036 }
2037 }
2038
2039 #[test]
2040 fn test_comparison() {
2041 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2042 b.func.register_count = 3;
2043 b.emit_load_int8(0, 5);
2044 b.emit_load_int8(1, 10);
2045 b.emit_reg3(Op::LessThan, 2, 0, 1);
2046 b.emit_reg(Op::Return, 2);
2047 let func = b.finish();
2048
2049 let mut vm = Vm::new();
2050 match vm.execute(&func).unwrap() {
2051 Value::Boolean(b) => assert!(b),
2052 v => panic!("expected true, got {v:?}"),
2053 }
2054 }
2055
2056 #[test]
2057 fn test_abstract_equality() {
2058 assert!(abstract_eq(&Value::Null, &Value::Undefined));
2059 assert!(abstract_eq(&Value::Undefined, &Value::Null));
2060 assert!(abstract_eq(
2061 &Value::Number(1.0),
2062 &Value::String("1".to_string())
2063 ));
2064 assert!(abstract_eq(&Value::Boolean(true), &Value::Number(1.0)));
2065 assert!(!abstract_eq(&Value::Number(0.0), &Value::Null));
2066 }
2067
2068 #[test]
2069 fn test_strict_equality() {
2070 assert!(strict_eq(&Value::Number(1.0), &Value::Number(1.0)));
2071 assert!(!strict_eq(
2072 &Value::Number(1.0),
2073 &Value::String("1".to_string())
2074 ));
2075 assert!(strict_eq(&Value::Null, &Value::Null));
2076 assert!(!strict_eq(&Value::Null, &Value::Undefined));
2077 }
2078
2079 #[test]
2080 fn test_bitwise_ops() {
2081 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2082 b.func.register_count = 3;
2083 b.emit_load_int8(0, 0x0F);
2084 b.emit_load_int8(1, 0x03);
2085 b.emit_reg3(Op::BitAnd, 2, 0, 1);
2086 b.emit_reg(Op::Return, 2);
2087 let func = b.finish();
2088
2089 let mut vm = Vm::new();
2090 match vm.execute(&func).unwrap() {
2091 Value::Number(n) => assert_eq!(n, 3.0),
2092 v => panic!("expected 3, got {v:?}"),
2093 }
2094 }
2095
2096 #[test]
2097 fn test_throw_uncaught() {
2098 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2099 b.func.register_count = 1;
2100 let ci = b.add_constant(Constant::String("oops".into()));
2101 b.emit_reg_u16(Op::LoadConst, 0, ci);
2102 b.emit_reg(Op::Throw, 0);
2103 let func = b.finish();
2104
2105 let mut vm = Vm::new();
2106 let err = vm.execute(&func).unwrap_err();
2107 assert_eq!(err.message, "oops");
2108 }
2109
2110 #[test]
2111 fn test_loop_counting() {
2112 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2113 b.func.register_count = 3;
2114 b.emit_load_int8(0, 0);
2115 b.emit_load_int8(1, 5);
2116 let loop_start = b.offset();
2117 b.emit_reg3(Op::LessThan, 2, 0, 1);
2118 let exit_patch = b.emit_cond_jump(Op::JumpIfFalse, 2);
2119 b.emit_load_int8(2, 1);
2120 b.emit_reg3(Op::Add, 0, 0, 2);
2121 b.emit_jump_to(loop_start);
2122 b.patch_jump(exit_patch);
2123 b.emit_reg(Op::Return, 0);
2124 let func = b.finish();
2125
2126 let mut vm = Vm::new();
2127 match vm.execute(&func).unwrap() {
2128 Value::Number(n) => assert_eq!(n, 5.0),
2129 v => panic!("expected 5, got {v:?}"),
2130 }
2131 }
2132
2133 // ── End-to-end (compile + execute) tests ────────────────
2134
2135 #[test]
2136 fn test_e2e_arithmetic() {
2137 match eval("2 + 3 * 4").unwrap() {
2138 Value::Number(n) => assert_eq!(n, 14.0),
2139 v => panic!("expected 14, got {v:?}"),
2140 }
2141 }
2142
2143 #[test]
2144 fn test_e2e_variables() {
2145 match eval("var x = 10; var y = 20; x + y").unwrap() {
2146 Value::Number(n) => assert_eq!(n, 30.0),
2147 v => panic!("expected 30, got {v:?}"),
2148 }
2149 }
2150
2151 #[test]
2152 fn test_e2e_if_else() {
2153 match eval("var x = 5; if (x > 3) { x = 100; } x").unwrap() {
2154 Value::Number(n) => assert_eq!(n, 100.0),
2155 v => panic!("expected 100, got {v:?}"),
2156 }
2157 }
2158
2159 #[test]
2160 fn test_e2e_while_loop() {
2161 match eval("var i = 0; var sum = 0; while (i < 10) { sum = sum + i; i = i + 1; } sum")
2162 .unwrap()
2163 {
2164 Value::Number(n) => assert_eq!(n, 45.0),
2165 v => panic!("expected 45, got {v:?}"),
2166 }
2167 }
2168
2169 #[test]
2170 fn test_e2e_function_call() {
2171 match eval("function add(a, b) { return a + b; } add(3, 4)").unwrap() {
2172 Value::Number(n) => assert_eq!(n, 7.0),
2173 v => panic!("expected 7, got {v:?}"),
2174 }
2175 }
2176
2177 #[test]
2178 fn test_e2e_recursive_factorial() {
2179 let src = r#"
2180 function fact(n) {
2181 if (n <= 1) return 1;
2182 return n * fact(n - 1);
2183 }
2184 fact(6)
2185 "#;
2186 match eval(src).unwrap() {
2187 Value::Number(n) => assert_eq!(n, 720.0),
2188 v => panic!("expected 720, got {v:?}"),
2189 }
2190 }
2191
2192 #[test]
2193 fn test_e2e_string_concat() {
2194 match eval("'hello' + ' ' + 'world'").unwrap() {
2195 Value::String(s) => assert_eq!(s, "hello world"),
2196 v => panic!("expected 'hello world', got {v:?}"),
2197 }
2198 }
2199
2200 #[test]
2201 fn test_e2e_comparison_coercion() {
2202 match eval("1 == '1'").unwrap() {
2203 Value::Boolean(b) => assert!(b),
2204 v => panic!("expected true, got {v:?}"),
2205 }
2206 match eval("1 === '1'").unwrap() {
2207 Value::Boolean(b) => assert!(!b),
2208 v => panic!("expected false, got {v:?}"),
2209 }
2210 }
2211
2212 #[test]
2213 fn test_e2e_typeof() {
2214 match eval("typeof 42").unwrap() {
2215 Value::String(s) => assert_eq!(s, "number"),
2216 v => panic!("expected 'number', got {v:?}"),
2217 }
2218 match eval("typeof 'hello'").unwrap() {
2219 Value::String(s) => assert_eq!(s, "string"),
2220 v => panic!("expected 'string', got {v:?}"),
2221 }
2222 }
2223
2224 #[test]
2225 fn test_e2e_object_literal() {
2226 match eval("var o = { x: 10, y: 20 }; o.x + o.y").unwrap() {
2227 Value::Number(n) => assert_eq!(n, 30.0),
2228 v => panic!("expected 30, got {v:?}"),
2229 }
2230 }
2231
2232 #[test]
2233 fn test_e2e_for_loop() {
2234 match eval("var s = 0; for (var i = 0; i < 5; i = i + 1) { s = s + i; } s").unwrap() {
2235 Value::Number(n) => assert_eq!(n, 10.0),
2236 v => panic!("expected 10, got {v:?}"),
2237 }
2238 }
2239
2240 #[test]
2241 fn test_e2e_nested_functions() {
2242 // Note: closures (capturing parent scope vars) not yet supported.
2243 // This test verifies nested function declarations and calls work.
2244 let src = r#"
2245 function outer(x) {
2246 function inner(y) {
2247 return y + 1;
2248 }
2249 return inner(x);
2250 }
2251 outer(5)
2252 "#;
2253 match eval(src).unwrap() {
2254 Value::Number(n) => assert_eq!(n, 6.0),
2255 v => panic!("expected 6, got {v:?}"),
2256 }
2257 }
2258
2259 #[test]
2260 fn test_e2e_logical_operators() {
2261 match eval("true && false").unwrap() {
2262 Value::Boolean(b) => assert!(!b),
2263 v => panic!("expected false, got {v:?}"),
2264 }
2265 match eval("false || true").unwrap() {
2266 Value::Boolean(b) => assert!(b),
2267 v => panic!("expected true, got {v:?}"),
2268 }
2269 }
2270
2271 #[test]
2272 fn test_e2e_unary_neg() {
2273 match eval("-(5 + 3)").unwrap() {
2274 Value::Number(n) => assert_eq!(n, -8.0),
2275 v => panic!("expected -8, got {v:?}"),
2276 }
2277 }
2278
2279 #[test]
2280 fn test_e2e_ternary() {
2281 match eval("true ? 1 : 2").unwrap() {
2282 Value::Number(n) => assert_eq!(n, 1.0),
2283 v => panic!("expected 1, got {v:?}"),
2284 }
2285 match eval("false ? 1 : 2").unwrap() {
2286 Value::Number(n) => assert_eq!(n, 2.0),
2287 v => panic!("expected 2, got {v:?}"),
2288 }
2289 }
2290
2291 #[test]
2292 fn test_e2e_fibonacci() {
2293 let src = r#"
2294 function fib(n) {
2295 if (n <= 1) return n;
2296 return fib(n - 1) + fib(n - 2);
2297 }
2298 fib(10)
2299 "#;
2300 match eval(src).unwrap() {
2301 Value::Number(n) => assert_eq!(n, 55.0),
2302 v => panic!("expected 55, got {v:?}"),
2303 }
2304 }
2305
2306 // ── GC integration tests ────────────────────────────────
2307
2308 #[test]
2309 fn test_gc_object_survives_collection() {
2310 let src = r#"
2311 var o = { x: 42 };
2312 o.x
2313 "#;
2314 match eval(src).unwrap() {
2315 Value::Number(n) => assert_eq!(n, 42.0),
2316 v => panic!("expected 42, got {v:?}"),
2317 }
2318 }
2319
2320 #[test]
2321 fn test_gc_many_objects() {
2322 // Allocate many objects to trigger GC threshold.
2323 let src = r#"
2324 var sum = 0;
2325 var i = 0;
2326 while (i < 100) {
2327 var o = { val: i };
2328 sum = sum + o.val;
2329 i = i + 1;
2330 }
2331 sum
2332 "#;
2333 match eval(src).unwrap() {
2334 Value::Number(n) => assert_eq!(n, 4950.0),
2335 v => panic!("expected 4950, got {v:?}"),
2336 }
2337 }
2338
2339 #[test]
2340 fn test_gc_reference_identity() {
2341 // With GC, object assignment is by reference.
2342 let mut gc: Gc<HeapObject> = Gc::new();
2343 let r = gc.alloc(HeapObject::Object(ObjectData::new()));
2344 let a = Value::Object(r);
2345 let b = a.clone();
2346 assert!(strict_eq(&a, &b)); // Same GcRef → strict equal.
2347 }
2348
2349 // ── Object model tests ──────────────────────────────────
2350
2351 #[test]
2352 fn test_prototype_chain_lookup() {
2353 // Property lookup walks the prototype chain.
2354 let src = r#"
2355 function Animal() {}
2356 var a = {};
2357 a.sound = "woof";
2358 a.sound
2359 "#;
2360 match eval(src).unwrap() {
2361 Value::String(s) => assert_eq!(s, "woof"),
2362 v => panic!("expected 'woof', got {v:?}"),
2363 }
2364 }
2365
2366 #[test]
2367 fn test_typeof_all_types() {
2368 // typeof returns correct strings for all types.
2369 let cases = [
2370 ("typeof undefined", "undefined"),
2371 ("typeof null", "object"),
2372 ("typeof true", "boolean"),
2373 ("typeof 42", "number"),
2374 ("typeof 'hello'", "string"),
2375 ("typeof {}", "object"),
2376 ("typeof function(){}", "function"),
2377 ];
2378 for (src, expected) in cases {
2379 match eval(src).unwrap() {
2380 Value::String(s) => assert_eq!(s, expected, "typeof failed for: {src}"),
2381 v => panic!("expected string for {src}, got {v:?}"),
2382 }
2383 }
2384 }
2385
2386 #[test]
2387 fn test_instanceof_basic() {
2388 let src = r#"
2389 function Foo() {}
2390 var f = {};
2391 f instanceof Foo
2392 "#;
2393 // Plain object without prototype link → false
2394 match eval(src).unwrap() {
2395 Value::Boolean(b) => assert!(!b),
2396 v => panic!("expected false, got {v:?}"),
2397 }
2398 }
2399
2400 #[test]
2401 fn test_in_operator() {
2402 let src = r#"
2403 var o = { x: 1, y: 2 };
2404 var r1 = "x" in o;
2405 var r2 = "z" in o;
2406 r1 === true && r2 === false
2407 "#;
2408 match eval(src).unwrap() {
2409 Value::Boolean(b) => assert!(b),
2410 v => panic!("expected true, got {v:?}"),
2411 }
2412 }
2413
2414 #[test]
2415 fn test_delete_property() {
2416 let src = r#"
2417 var o = { x: 1, y: 2 };
2418 delete o.x;
2419 typeof o.x === "undefined" && o.y === 2
2420 "#;
2421 match eval(src).unwrap() {
2422 Value::Boolean(b) => assert!(b),
2423 v => panic!("expected true, got {v:?}"),
2424 }
2425 }
2426
2427 #[test]
2428 fn test_delete_computed_property() {
2429 let src = r#"
2430 var o = { a: 10, b: 20 };
2431 var key = "a";
2432 delete o[key];
2433 typeof o.a === "undefined" && o.b === 20
2434 "#;
2435 match eval(src).unwrap() {
2436 Value::Boolean(b) => assert!(b),
2437 v => panic!("expected true, got {v:?}"),
2438 }
2439 }
2440
2441 #[test]
2442 fn test_delete_non_configurable() {
2443 // Array length is non-configurable, delete should return false.
2444 let mut gc: Gc<HeapObject> = Gc::new();
2445 let mut obj = ObjectData::new();
2446 obj.properties.insert(
2447 "x".to_string(),
2448 Property {
2449 value: Value::Number(1.0),
2450 writable: true,
2451 enumerable: true,
2452 configurable: false,
2453 },
2454 );
2455 let obj_ref = gc.alloc(HeapObject::Object(obj));
2456
2457 // Try to delete the non-configurable property.
2458 match gc.get_mut(obj_ref) {
2459 Some(HeapObject::Object(data)) => {
2460 let prop = data.properties.get("x").unwrap();
2461 assert!(!prop.configurable);
2462 // The property should still be there.
2463 assert!(data.properties.contains_key("x"));
2464 }
2465 _ => panic!("expected object"),
2466 }
2467 }
2468
2469 #[test]
2470 fn test_property_writable_flag() {
2471 // Setting a non-writable property should silently fail.
2472 let mut gc: Gc<HeapObject> = Gc::new();
2473 let mut obj = ObjectData::new();
2474 obj.properties.insert(
2475 "frozen".to_string(),
2476 Property {
2477 value: Value::Number(42.0),
2478 writable: false,
2479 enumerable: true,
2480 configurable: false,
2481 },
2482 );
2483 let obj_ref = gc.alloc(HeapObject::Object(obj));
2484
2485 // Verify the property value.
2486 match gc.get(obj_ref) {
2487 Some(HeapObject::Object(data)) => {
2488 assert_eq!(
2489 data.properties.get("frozen").unwrap().value.to_number(),
2490 42.0
2491 );
2492 }
2493 _ => panic!("expected object"),
2494 }
2495 }
2496
2497 #[test]
2498 fn test_for_in_basic() {
2499 let src = r#"
2500 var o = { a: 1, b: 2, c: 3 };
2501 var sum = 0;
2502 for (var key in o) {
2503 sum = sum + o[key];
2504 }
2505 sum
2506 "#;
2507 match eval(src).unwrap() {
2508 Value::Number(n) => assert_eq!(n, 6.0),
2509 v => panic!("expected 6, got {v:?}"),
2510 }
2511 }
2512
2513 #[test]
2514 fn test_for_in_collects_keys() {
2515 let src = r#"
2516 var o = { x: 10, y: 20 };
2517 var keys = "";
2518 for (var k in o) {
2519 keys = keys + k + ",";
2520 }
2521 keys
2522 "#;
2523 match eval(src).unwrap() {
2524 Value::String(s) => {
2525 // Both keys should appear (order may vary with HashMap).
2526 assert!(s.contains("x,"));
2527 assert!(s.contains("y,"));
2528 }
2529 v => panic!("expected string, got {v:?}"),
2530 }
2531 }
2532
2533 #[test]
2534 fn test_for_in_empty_object() {
2535 let src = r#"
2536 var o = {};
2537 var count = 0;
2538 for (var k in o) {
2539 count = count + 1;
2540 }
2541 count
2542 "#;
2543 match eval(src).unwrap() {
2544 Value::Number(n) => assert_eq!(n, 0.0),
2545 v => panic!("expected 0, got {v:?}"),
2546 }
2547 }
2548
2549 #[test]
2550 fn test_property_enumerable_flag() {
2551 // Array "length" is non-enumerable, should not appear in for-in.
2552 let src = r#"
2553 var arr = [10, 20, 30];
2554 var keys = "";
2555 for (var k in arr) {
2556 keys = keys + k + ",";
2557 }
2558 keys
2559 "#;
2560 match eval(src).unwrap() {
2561 Value::String(s) => {
2562 // "length" should NOT be in the keys (it's non-enumerable).
2563 assert!(!s.contains("length"));
2564 // Array indices should be present.
2565 assert!(s.contains("0,"));
2566 assert!(s.contains("1,"));
2567 assert!(s.contains("2,"));
2568 }
2569 v => panic!("expected string, got {v:?}"),
2570 }
2571 }
2572
2573 #[test]
2574 fn test_object_reference_semantics() {
2575 // Objects have reference semantics (shared via GcRef).
2576 let src = r#"
2577 var a = { x: 1 };
2578 var b = a;
2579 b.x = 42;
2580 a.x
2581 "#;
2582 match eval(src).unwrap() {
2583 Value::Number(n) => assert_eq!(n, 42.0),
2584 v => panic!("expected 42, got {v:?}"),
2585 }
2586 }
2587
2588 #[test]
2589 fn test_gc_enumerate_keys_order() {
2590 // Integer keys should come first, sorted numerically.
2591 let mut gc: Gc<HeapObject> = Gc::new();
2592 let mut obj = ObjectData::new();
2593 obj.properties
2594 .insert("b".to_string(), Property::data(Value::Number(2.0)));
2595 obj.properties
2596 .insert("2".to_string(), Property::data(Value::Number(3.0)));
2597 obj.properties
2598 .insert("a".to_string(), Property::data(Value::Number(1.0)));
2599 obj.properties
2600 .insert("0".to_string(), Property::data(Value::Number(0.0)));
2601 let obj_ref = gc.alloc(HeapObject::Object(obj));
2602
2603 let keys = gc_enumerate_keys(&gc, obj_ref);
2604 // Integer keys first (sorted), then string keys.
2605 let int_part: Vec<&str> = keys.iter().take(2).map(|s| s.as_str()).collect();
2606 assert_eq!(int_part, vec!["0", "2"]);
2607 // Remaining keys are string keys (order depends on HashMap iteration).
2608 let str_part: Vec<&str> = keys.iter().skip(2).map(|s| s.as_str()).collect();
2609 assert!(str_part.contains(&"a"));
2610 assert!(str_part.contains(&"b"));
2611 }
2612
2613 #[test]
2614 fn test_instanceof_with_gc() {
2615 // Direct test of gc_instanceof.
2616 let mut gc: Gc<HeapObject> = Gc::new();
2617
2618 // Create a constructor function with a .prototype object.
2619 let proto = gc.alloc(HeapObject::Object(ObjectData::new()));
2620 let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData {
2621 name: "Foo".to_string(),
2622 kind: FunctionKind::Native(NativeFunc {
2623 callback: |_, _ctx| Ok(Value::Undefined),
2624 }),
2625 prototype_obj: Some(proto),
2626 properties: HashMap::new(),
2627 upvalues: Vec::new(),
2628 })));
2629
2630 // Create an object whose [[Prototype]] is the constructor's .prototype.
2631 let mut obj_data = ObjectData::new();
2632 obj_data.prototype = Some(proto);
2633 let obj = gc.alloc(HeapObject::Object(obj_data));
2634
2635 assert!(gc_instanceof(&gc, obj, ctor));
2636
2637 // An unrelated object should not match.
2638 let other = gc.alloc(HeapObject::Object(ObjectData::new()));
2639 assert!(!gc_instanceof(&gc, other, ctor));
2640 }
2641
2642 #[test]
2643 fn test_try_catch_basic() {
2644 // Simple try/catch should catch a thrown value.
2645 let src = r#"
2646 var caught = false;
2647 try { throw "err"; } catch (e) { caught = true; }
2648 caught
2649 "#;
2650 match eval(src).unwrap() {
2651 Value::Boolean(true) => {}
2652 v => panic!("expected true, got {v:?}"),
2653 }
2654 }
2655
2656 #[test]
2657 fn test_try_catch_nested_call() {
2658 // try/catch should catch errors thrown from called functions.
2659 let src = r#"
2660 function thrower() { throw "err"; }
2661 var caught = false;
2662 try { thrower(); } catch (e) { caught = true; }
2663 caught
2664 "#;
2665 match eval(src).unwrap() {
2666 Value::Boolean(true) => {}
2667 v => panic!("expected true, got {v:?}"),
2668 }
2669 }
2670
2671 // ── Closure tests ────────────────────────────────────────
2672
2673 #[test]
2674 fn test_closure_basic() {
2675 // Basic closure: inner function reads outer variable.
2676 let src = r#"
2677 function outer() {
2678 var x = 10;
2679 function inner() {
2680 return x;
2681 }
2682 return inner();
2683 }
2684 outer()
2685 "#;
2686 match eval(src).unwrap() {
2687 Value::Number(n) => assert_eq!(n, 10.0),
2688 v => panic!("expected 10, got {v:?}"),
2689 }
2690 }
2691
2692 #[test]
2693 fn test_closure_return_function() {
2694 // Closure survives the outer function's return.
2695 let src = r#"
2696 function makeAdder(x) {
2697 return function(y) { return x + y; };
2698 }
2699 var add5 = makeAdder(5);
2700 add5(3)
2701 "#;
2702 match eval(src).unwrap() {
2703 Value::Number(n) => assert_eq!(n, 8.0),
2704 v => panic!("expected 8, got {v:?}"),
2705 }
2706 }
2707
2708 #[test]
2709 fn test_closure_mutation() {
2710 // Closures share live references — mutation is visible.
2711 let src = r#"
2712 function counter() {
2713 var n = 0;
2714 return function() { n = n + 1; return n; };
2715 }
2716 var c = counter();
2717 c();
2718 c();
2719 c()
2720 "#;
2721 match eval(src).unwrap() {
2722 Value::Number(n) => assert_eq!(n, 3.0),
2723 v => panic!("expected 3, got {v:?}"),
2724 }
2725 }
2726
2727 #[test]
2728 fn test_closure_shared_variable() {
2729 // Two closures from the same scope share the same variable.
2730 let src = r#"
2731 function make() {
2732 var x = 0;
2733 function inc() { x = x + 1; }
2734 function get() { return x; }
2735 inc();
2736 inc();
2737 return get();
2738 }
2739 make()
2740 "#;
2741 match eval(src).unwrap() {
2742 Value::Number(n) => assert_eq!(n, 2.0),
2743 v => panic!("expected 2, got {v:?}"),
2744 }
2745 }
2746
2747 #[test]
2748 fn test_closure_arrow() {
2749 // Arrow function captures outer variable.
2750 let src = r#"
2751 function outer() {
2752 var x = 42;
2753 var f = () => x;
2754 return f();
2755 }
2756 outer()
2757 "#;
2758 match eval(src).unwrap() {
2759 Value::Number(n) => assert_eq!(n, 42.0),
2760 v => panic!("expected 42, got {v:?}"),
2761 }
2762 }
2763
2764 #[test]
2765 fn test_closure_nested() {
2766 // Transitive capture: grandchild function reads grandparent variable.
2767 let src = r#"
2768 function outer() {
2769 var x = 100;
2770 function middle() {
2771 function inner() {
2772 return x;
2773 }
2774 return inner();
2775 }
2776 return middle();
2777 }
2778 outer()
2779 "#;
2780 match eval(src).unwrap() {
2781 Value::Number(n) => assert_eq!(n, 100.0),
2782 v => panic!("expected 100, got {v:?}"),
2783 }
2784 }
2785
2786 #[test]
2787 fn test_closure_param_capture() {
2788 // Closure captures a function parameter.
2789 let src = r#"
2790 function multiply(factor) {
2791 return function(x) { return x * factor; };
2792 }
2793 var double = multiply(2);
2794 double(7)
2795 "#;
2796 match eval(src).unwrap() {
2797 Value::Number(n) => assert_eq!(n, 14.0),
2798 v => panic!("expected 14, got {v:?}"),
2799 }
2800 }
2801
2802 // ── const tests ──────────────────────────────────────────
2803
2804 #[test]
2805 fn test_const_basic() {
2806 let src = "const x = 42; x";
2807 match eval(src).unwrap() {
2808 Value::Number(n) => assert_eq!(n, 42.0),
2809 v => panic!("expected 42, got {v:?}"),
2810 }
2811 }
2812
2813 #[test]
2814 fn test_const_reassignment_error() {
2815 let src = "const x = 1; x = 2;";
2816 let program = crate::parser::Parser::parse(src).expect("parse ok");
2817 let result = crate::compiler::compile(&program);
2818 assert!(
2819 result.is_err(),
2820 "const reassignment should be a compile error"
2821 );
2822 }
2823
2824 #[test]
2825 fn test_const_missing_init_error() {
2826 let src = "const x;";
2827 let program = crate::parser::Parser::parse(src).expect("parse ok");
2828 let result = crate::compiler::compile(&program);
2829 assert!(
2830 result.is_err(),
2831 "const without initializer should be a compile error"
2832 );
2833 }
2834
2835 // ── this binding tests ───────────────────────────────────
2836
2837 #[test]
2838 fn test_method_call_this() {
2839 let src = r#"
2840 var obj = {};
2841 obj.x = 10;
2842 obj.getX = function() { return this.x; };
2843 obj.getX()
2844 "#;
2845 match eval(src).unwrap() {
2846 Value::Number(n) => assert_eq!(n, 10.0),
2847 v => panic!("expected 10, got {v:?}"),
2848 }
2849 }
2850
2851 // ── Object built-in tests ────────────────────────────────
2852
2853 #[test]
2854 fn test_object_keys() {
2855 let src = r#"
2856 var obj = {};
2857 obj.a = 1;
2858 obj.b = 2;
2859 obj.c = 3;
2860 var k = Object.keys(obj);
2861 k.length
2862 "#;
2863 match eval(src).unwrap() {
2864 Value::Number(n) => assert_eq!(n, 3.0),
2865 v => panic!("expected 3, got {v:?}"),
2866 }
2867 }
2868
2869 #[test]
2870 fn test_object_values() {
2871 let src = r#"
2872 var obj = {};
2873 obj.x = 10;
2874 var v = Object.values(obj);
2875 v[0]
2876 "#;
2877 match eval(src).unwrap() {
2878 Value::Number(n) => assert_eq!(n, 10.0),
2879 v => panic!("expected 10, got {v:?}"),
2880 }
2881 }
2882
2883 #[test]
2884 fn test_object_entries() {
2885 let src = r#"
2886 var obj = {};
2887 obj.x = 42;
2888 var e = Object.entries(obj);
2889 e[0][1]
2890 "#;
2891 match eval(src).unwrap() {
2892 Value::Number(n) => assert_eq!(n, 42.0),
2893 v => panic!("expected 42, got {v:?}"),
2894 }
2895 }
2896
2897 #[test]
2898 fn test_object_assign() {
2899 let src = r#"
2900 var a = {};
2901 a.x = 1;
2902 var b = {};
2903 b.y = 2;
2904 var c = Object.assign(a, b);
2905 c.y
2906 "#;
2907 match eval(src).unwrap() {
2908 Value::Number(n) => assert_eq!(n, 2.0),
2909 v => panic!("expected 2, got {v:?}"),
2910 }
2911 }
2912
2913 #[test]
2914 fn test_object_create() {
2915 let src = r#"
2916 var proto = {};
2917 proto.greet = "hello";
2918 var child = Object.create(proto);
2919 child.greet
2920 "#;
2921 match eval(src).unwrap() {
2922 Value::String(s) => assert_eq!(s, "hello"),
2923 v => panic!("expected 'hello', got {v:?}"),
2924 }
2925 }
2926
2927 #[test]
2928 fn test_object_is() {
2929 let src = "Object.is(NaN, NaN)";
2930 match eval(src).unwrap() {
2931 Value::Boolean(b) => assert!(b),
2932 v => panic!("expected true, got {v:?}"),
2933 }
2934 }
2935
2936 #[test]
2937 fn test_object_freeze() {
2938 let src = r#"
2939 var obj = {};
2940 obj.x = 1;
2941 Object.freeze(obj);
2942 Object.isFrozen(obj)
2943 "#;
2944 match eval(src).unwrap() {
2945 Value::Boolean(b) => assert!(b),
2946 v => panic!("expected true, got {v:?}"),
2947 }
2948 }
2949
2950 #[test]
2951 fn test_object_has_own_property() {
2952 let src = r#"
2953 var obj = {};
2954 obj.x = 1;
2955 obj.hasOwnProperty("x")
2956 "#;
2957 match eval(src).unwrap() {
2958 Value::Boolean(b) => assert!(b),
2959 v => panic!("expected true, got {v:?}"),
2960 }
2961 }
2962
2963 // ── Array built-in tests ─────────────────────────────────
2964
2965 #[test]
2966 fn test_array_push_pop() {
2967 let src = r#"
2968 var arr = [1, 2, 3];
2969 arr.push(4);
2970 arr.pop()
2971 "#;
2972 match eval(src).unwrap() {
2973 Value::Number(n) => assert_eq!(n, 4.0),
2974 v => panic!("expected 4, got {v:?}"),
2975 }
2976 }
2977
2978 #[test]
2979 fn test_array_push_length() {
2980 let src = r#"
2981 var arr = [];
2982 arr.push(10);
2983 arr.push(20);
2984 arr.length
2985 "#;
2986 match eval(src).unwrap() {
2987 Value::Number(n) => assert_eq!(n, 2.0),
2988 v => panic!("expected 2, got {v:?}"),
2989 }
2990 }
2991
2992 #[test]
2993 fn test_array_shift_unshift() {
2994 let src = r#"
2995 var arr = [1, 2, 3];
2996 arr.unshift(0);
2997 arr.shift()
2998 "#;
2999 match eval(src).unwrap() {
3000 Value::Number(n) => assert_eq!(n, 0.0),
3001 v => panic!("expected 0, got {v:?}"),
3002 }
3003 }
3004
3005 #[test]
3006 fn test_array_index_of() {
3007 let src = r#"
3008 var arr = [10, 20, 30];
3009 arr.indexOf(20)
3010 "#;
3011 match eval(src).unwrap() {
3012 Value::Number(n) => assert_eq!(n, 1.0),
3013 v => panic!("expected 1, got {v:?}"),
3014 }
3015 }
3016
3017 #[test]
3018 fn test_array_includes() {
3019 let src = r#"
3020 var arr = [1, 2, 3];
3021 arr.includes(2)
3022 "#;
3023 match eval(src).unwrap() {
3024 Value::Boolean(b) => assert!(b),
3025 v => panic!("expected true, got {v:?}"),
3026 }
3027 }
3028
3029 #[test]
3030 fn test_array_join() {
3031 let src = r#"
3032 var arr = [1, 2, 3];
3033 arr.join("-")
3034 "#;
3035 match eval(src).unwrap() {
3036 Value::String(s) => assert_eq!(s, "1-2-3"),
3037 v => panic!("expected '1-2-3', got {v:?}"),
3038 }
3039 }
3040
3041 #[test]
3042 fn test_array_slice() {
3043 let src = r#"
3044 var arr = [1, 2, 3, 4, 5];
3045 var s = arr.slice(1, 3);
3046 s.length
3047 "#;
3048 match eval(src).unwrap() {
3049 Value::Number(n) => assert_eq!(n, 2.0),
3050 v => panic!("expected 2, got {v:?}"),
3051 }
3052 }
3053
3054 #[test]
3055 fn test_array_concat() {
3056 let src = r#"
3057 var a = [1, 2];
3058 var b = [3, 4];
3059 var c = a.concat(b);
3060 c.length
3061 "#;
3062 match eval(src).unwrap() {
3063 Value::Number(n) => assert_eq!(n, 4.0),
3064 v => panic!("expected 4, got {v:?}"),
3065 }
3066 }
3067
3068 #[test]
3069 fn test_array_reverse() {
3070 let src = r#"
3071 var arr = [1, 2, 3];
3072 arr.reverse();
3073 arr[0]
3074 "#;
3075 match eval(src).unwrap() {
3076 Value::Number(n) => assert_eq!(n, 3.0),
3077 v => panic!("expected 3, got {v:?}"),
3078 }
3079 }
3080
3081 #[test]
3082 fn test_array_splice() {
3083 let src = r#"
3084 var arr = [1, 2, 3, 4, 5];
3085 var removed = arr.splice(1, 2);
3086 removed.length
3087 "#;
3088 match eval(src).unwrap() {
3089 Value::Number(n) => assert_eq!(n, 2.0),
3090 v => panic!("expected 2, got {v:?}"),
3091 }
3092 }
3093
3094 #[test]
3095 fn test_array_is_array() {
3096 let src = "Array.isArray([1, 2, 3])";
3097 match eval(src).unwrap() {
3098 Value::Boolean(b) => assert!(b),
3099 v => panic!("expected true, got {v:?}"),
3100 }
3101 }
3102
3103 #[test]
3104 fn test_array_map() {
3105 let src = r#"
3106 var arr = [1, 2, 3];
3107 var doubled = arr.map(function(x) { return x * 2; });
3108 doubled[1]
3109 "#;
3110 match eval(src).unwrap() {
3111 Value::Number(n) => assert_eq!(n, 4.0),
3112 v => panic!("expected 4, got {v:?}"),
3113 }
3114 }
3115
3116 #[test]
3117 fn test_array_filter() {
3118 let src = r#"
3119 var arr = [1, 2, 3, 4, 5];
3120 var evens = arr.filter(function(x) { return x % 2 === 0; });
3121 evens.length
3122 "#;
3123 match eval(src).unwrap() {
3124 Value::Number(n) => assert_eq!(n, 2.0),
3125 v => panic!("expected 2, got {v:?}"),
3126 }
3127 }
3128
3129 #[test]
3130 fn test_array_reduce() {
3131 let src = r#"
3132 var arr = [1, 2, 3, 4];
3133 arr.reduce(function(acc, x) { return acc + x; }, 0)
3134 "#;
3135 match eval(src).unwrap() {
3136 Value::Number(n) => assert_eq!(n, 10.0),
3137 v => panic!("expected 10, got {v:?}"),
3138 }
3139 }
3140
3141 #[test]
3142 fn test_array_foreach() {
3143 let src = r#"
3144 var arr = [1, 2, 3];
3145 var sum = 0;
3146 arr.forEach(function(x) { sum = sum + x; });
3147 sum
3148 "#;
3149 match eval(src).unwrap() {
3150 Value::Number(n) => assert_eq!(n, 6.0),
3151 v => panic!("expected 6, got {v:?}"),
3152 }
3153 }
3154
3155 #[test]
3156 fn test_array_find() {
3157 let src = r#"
3158 var arr = [1, 2, 3, 4];
3159 arr.find(function(x) { return x > 2; })
3160 "#;
3161 match eval(src).unwrap() {
3162 Value::Number(n) => assert_eq!(n, 3.0),
3163 v => panic!("expected 3, got {v:?}"),
3164 }
3165 }
3166
3167 #[test]
3168 fn test_array_find_index() {
3169 let src = r#"
3170 var arr = [1, 2, 3, 4];
3171 arr.findIndex(function(x) { return x > 2; })
3172 "#;
3173 match eval(src).unwrap() {
3174 Value::Number(n) => assert_eq!(n, 2.0),
3175 v => panic!("expected 2, got {v:?}"),
3176 }
3177 }
3178
3179 #[test]
3180 fn test_array_some_every() {
3181 let src = r#"
3182 var arr = [2, 4, 6];
3183 var all_even = arr.every(function(x) { return x % 2 === 0; });
3184 var has_big = arr.some(function(x) { return x > 10; });
3185 all_even && !has_big
3186 "#;
3187 match eval(src).unwrap() {
3188 Value::Boolean(b) => assert!(b),
3189 v => panic!("expected true, got {v:?}"),
3190 }
3191 }
3192
3193 #[test]
3194 fn test_array_sort() {
3195 let src = r#"
3196 var arr = [3, 1, 2];
3197 arr.sort();
3198 arr[0]
3199 "#;
3200 match eval(src).unwrap() {
3201 Value::Number(n) => assert_eq!(n, 1.0),
3202 v => panic!("expected 1, got {v:?}"),
3203 }
3204 }
3205
3206 #[test]
3207 fn test_array_sort_custom() {
3208 let src = r#"
3209 var arr = [3, 1, 2];
3210 arr.sort(function(a, b) { return a - b; });
3211 arr[2]
3212 "#;
3213 match eval(src).unwrap() {
3214 Value::Number(n) => assert_eq!(n, 3.0),
3215 v => panic!("expected 3, got {v:?}"),
3216 }
3217 }
3218
3219 #[test]
3220 fn test_array_from() {
3221 let src = r#"
3222 var arr = Array.from("abc");
3223 arr.length
3224 "#;
3225 match eval(src).unwrap() {
3226 Value::Number(n) => assert_eq!(n, 3.0),
3227 v => panic!("expected 3, got {v:?}"),
3228 }
3229 }
3230
3231 #[test]
3232 fn test_array_from_array() {
3233 let src = r#"
3234 var orig = [10, 20, 30];
3235 var copy = Array.from(orig);
3236 copy[2]
3237 "#;
3238 match eval(src).unwrap() {
3239 Value::Number(n) => assert_eq!(n, 30.0),
3240 v => panic!("expected 30, got {v:?}"),
3241 }
3242 }
3243
3244 #[test]
3245 fn test_array_flat() {
3246 let src = r#"
3247 var arr = [[1, 2], [3, 4]];
3248 var flat = arr.flat();
3249 flat.length
3250 "#;
3251 match eval(src).unwrap() {
3252 Value::Number(n) => assert_eq!(n, 4.0),
3253 v => panic!("expected 4, got {v:?}"),
3254 }
3255 }
3256
3257 // ── Error built-in tests ─────────────────────────────────
3258
3259 #[test]
3260 fn test_error_constructor() {
3261 let src = r#"
3262 var e = new Error("oops");
3263 e.message
3264 "#;
3265 match eval(src).unwrap() {
3266 Value::String(s) => assert_eq!(s, "oops"),
3267 v => panic!("expected 'oops', got {v:?}"),
3268 }
3269 }
3270
3271 #[test]
3272 fn test_type_error_constructor() {
3273 let src = r#"
3274 var e = new TypeError("bad type");
3275 e.message
3276 "#;
3277 match eval(src).unwrap() {
3278 Value::String(s) => assert_eq!(s, "bad type"),
3279 v => panic!("expected 'bad type', got {v:?}"),
3280 }
3281 }
3282
3283 // ── Global function tests ────────────────────────────────
3284
3285 #[test]
3286 fn test_parse_int() {
3287 let src = "parseInt('42')";
3288 match eval(src).unwrap() {
3289 Value::Number(n) => assert_eq!(n, 42.0),
3290 v => panic!("expected 42, got {v:?}"),
3291 }
3292 }
3293
3294 #[test]
3295 fn test_parse_int_hex() {
3296 let src = "parseInt('0xFF', 16)";
3297 match eval(src).unwrap() {
3298 Value::Number(n) => assert_eq!(n, 255.0),
3299 v => panic!("expected 255, got {v:?}"),
3300 }
3301 }
3302
3303 #[test]
3304 fn test_is_nan() {
3305 let src = "isNaN(NaN)";
3306 match eval(src).unwrap() {
3307 Value::Boolean(b) => assert!(b),
3308 v => panic!("expected true, got {v:?}"),
3309 }
3310 }
3311
3312 #[test]
3313 fn test_is_finite() {
3314 let src = "isFinite(42)";
3315 match eval(src).unwrap() {
3316 Value::Boolean(b) => assert!(b),
3317 v => panic!("expected true, got {v:?}"),
3318 }
3319 }
3320
3321 // ── String built-in tests ─────────────────────────────────
3322
3323 #[test]
3324 fn test_string_constructor() {
3325 match eval("String(42)").unwrap() {
3326 Value::String(s) => assert_eq!(s, "42"),
3327 v => panic!("expected '42', got {v:?}"),
3328 }
3329 match eval("String(true)").unwrap() {
3330 Value::String(s) => assert_eq!(s, "true"),
3331 v => panic!("expected 'true', got {v:?}"),
3332 }
3333 match eval("String()").unwrap() {
3334 Value::String(s) => assert_eq!(s, ""),
3335 v => panic!("expected '', got {v:?}"),
3336 }
3337 }
3338
3339 #[test]
3340 fn test_string_length() {
3341 match eval("'hello'.length").unwrap() {
3342 Value::Number(n) => assert_eq!(n, 5.0),
3343 v => panic!("expected 5, got {v:?}"),
3344 }
3345 }
3346
3347 #[test]
3348 fn test_string_char_at() {
3349 match eval("'hello'.charAt(1)").unwrap() {
3350 Value::String(s) => assert_eq!(s, "e"),
3351 v => panic!("expected 'e', got {v:?}"),
3352 }
3353 match eval("'hello'.charAt(10)").unwrap() {
3354 Value::String(s) => assert_eq!(s, ""),
3355 v => panic!("expected '', got {v:?}"),
3356 }
3357 }
3358
3359 #[test]
3360 fn test_string_char_code_at() {
3361 match eval("'A'.charCodeAt(0)").unwrap() {
3362 Value::Number(n) => assert_eq!(n, 65.0),
3363 v => panic!("expected 65, got {v:?}"),
3364 }
3365 }
3366
3367 #[test]
3368 fn test_string_proto_concat() {
3369 match eval("'hello'.concat(' ', 'world')").unwrap() {
3370 Value::String(s) => assert_eq!(s, "hello world"),
3371 v => panic!("expected 'hello world', got {v:?}"),
3372 }
3373 }
3374
3375 #[test]
3376 fn test_string_slice() {
3377 match eval("'hello world'.slice(6)").unwrap() {
3378 Value::String(s) => assert_eq!(s, "world"),
3379 v => panic!("expected 'world', got {v:?}"),
3380 }
3381 match eval("'hello'.slice(1, 3)").unwrap() {
3382 Value::String(s) => assert_eq!(s, "el"),
3383 v => panic!("expected 'el', got {v:?}"),
3384 }
3385 match eval("'hello'.slice(-3)").unwrap() {
3386 Value::String(s) => assert_eq!(s, "llo"),
3387 v => panic!("expected 'llo', got {v:?}"),
3388 }
3389 }
3390
3391 #[test]
3392 fn test_string_substring() {
3393 match eval("'hello'.substring(1, 3)").unwrap() {
3394 Value::String(s) => assert_eq!(s, "el"),
3395 v => panic!("expected 'el', got {v:?}"),
3396 }
3397 // substring swaps args if start > end
3398 match eval("'hello'.substring(3, 1)").unwrap() {
3399 Value::String(s) => assert_eq!(s, "el"),
3400 v => panic!("expected 'el', got {v:?}"),
3401 }
3402 }
3403
3404 #[test]
3405 fn test_string_index_of() {
3406 match eval("'hello world'.indexOf('world')").unwrap() {
3407 Value::Number(n) => assert_eq!(n, 6.0),
3408 v => panic!("expected 6, got {v:?}"),
3409 }
3410 match eval("'hello'.indexOf('xyz')").unwrap() {
3411 Value::Number(n) => assert_eq!(n, -1.0),
3412 v => panic!("expected -1, got {v:?}"),
3413 }
3414 }
3415
3416 #[test]
3417 fn test_string_last_index_of() {
3418 match eval("'abcabc'.lastIndexOf('abc')").unwrap() {
3419 Value::Number(n) => assert_eq!(n, 3.0),
3420 v => panic!("expected 3, got {v:?}"),
3421 }
3422 }
3423
3424 #[test]
3425 fn test_string_includes() {
3426 match eval("'hello world'.includes('world')").unwrap() {
3427 Value::Boolean(b) => assert!(b),
3428 v => panic!("expected true, got {v:?}"),
3429 }
3430 match eval("'hello'.includes('xyz')").unwrap() {
3431 Value::Boolean(b) => assert!(!b),
3432 v => panic!("expected false, got {v:?}"),
3433 }
3434 }
3435
3436 #[test]
3437 fn test_string_starts_ends_with() {
3438 match eval("'hello'.startsWith('hel')").unwrap() {
3439 Value::Boolean(b) => assert!(b),
3440 v => panic!("expected true, got {v:?}"),
3441 }
3442 match eval("'hello'.endsWith('llo')").unwrap() {
3443 Value::Boolean(b) => assert!(b),
3444 v => panic!("expected true, got {v:?}"),
3445 }
3446 }
3447
3448 #[test]
3449 fn test_string_trim() {
3450 match eval("' hello '.trim()").unwrap() {
3451 Value::String(s) => assert_eq!(s, "hello"),
3452 v => panic!("expected 'hello', got {v:?}"),
3453 }
3454 match eval("' hello '.trimStart()").unwrap() {
3455 Value::String(s) => assert_eq!(s, "hello "),
3456 v => panic!("expected 'hello ', got {v:?}"),
3457 }
3458 match eval("' hello '.trimEnd()").unwrap() {
3459 Value::String(s) => assert_eq!(s, " hello"),
3460 v => panic!("expected ' hello', got {v:?}"),
3461 }
3462 }
3463
3464 #[test]
3465 fn test_string_pad() {
3466 match eval("'5'.padStart(3, '0')").unwrap() {
3467 Value::String(s) => assert_eq!(s, "005"),
3468 v => panic!("expected '005', got {v:?}"),
3469 }
3470 match eval("'5'.padEnd(3, '0')").unwrap() {
3471 Value::String(s) => assert_eq!(s, "500"),
3472 v => panic!("expected '500', got {v:?}"),
3473 }
3474 }
3475
3476 #[test]
3477 fn test_string_repeat() {
3478 match eval("'ab'.repeat(3)").unwrap() {
3479 Value::String(s) => assert_eq!(s, "ababab"),
3480 v => panic!("expected 'ababab', got {v:?}"),
3481 }
3482 }
3483
3484 #[test]
3485 fn test_string_split() {
3486 // split returns an array; verify length and elements.
3487 match eval("'a,b,c'.split(',').length").unwrap() {
3488 Value::Number(n) => assert_eq!(n, 3.0),
3489 v => panic!("expected 3, got {v:?}"),
3490 }
3491 match eval("'a,b,c'.split(',')[0]").unwrap() {
3492 Value::String(s) => assert_eq!(s, "a"),
3493 v => panic!("expected 'a', got {v:?}"),
3494 }
3495 match eval("'a,b,c'.split(',')[2]").unwrap() {
3496 Value::String(s) => assert_eq!(s, "c"),
3497 v => panic!("expected 'c', got {v:?}"),
3498 }
3499 }
3500
3501 #[test]
3502 fn test_string_replace() {
3503 match eval("'hello world'.replace('world', 'there')").unwrap() {
3504 Value::String(s) => assert_eq!(s, "hello there"),
3505 v => panic!("expected 'hello there', got {v:?}"),
3506 }
3507 }
3508
3509 #[test]
3510 fn test_string_replace_all() {
3511 match eval("'aabbcc'.replaceAll('b', 'x')").unwrap() {
3512 Value::String(s) => assert_eq!(s, "aaxxcc"),
3513 v => panic!("expected 'aaxxcc', got {v:?}"),
3514 }
3515 }
3516
3517 #[test]
3518 fn test_string_case() {
3519 match eval("'Hello'.toLowerCase()").unwrap() {
3520 Value::String(s) => assert_eq!(s, "hello"),
3521 v => panic!("expected 'hello', got {v:?}"),
3522 }
3523 match eval("'Hello'.toUpperCase()").unwrap() {
3524 Value::String(s) => assert_eq!(s, "HELLO"),
3525 v => panic!("expected 'HELLO', got {v:?}"),
3526 }
3527 }
3528
3529 #[test]
3530 fn test_string_at() {
3531 match eval("'hello'.at(0)").unwrap() {
3532 Value::String(s) => assert_eq!(s, "h"),
3533 v => panic!("expected 'h', got {v:?}"),
3534 }
3535 match eval("'hello'.at(-1)").unwrap() {
3536 Value::String(s) => assert_eq!(s, "o"),
3537 v => panic!("expected 'o', got {v:?}"),
3538 }
3539 }
3540
3541 #[test]
3542 fn test_string_from_char_code() {
3543 match eval("String.fromCharCode(72, 101, 108)").unwrap() {
3544 Value::String(s) => assert_eq!(s, "Hel"),
3545 v => panic!("expected 'Hel', got {v:?}"),
3546 }
3547 }
3548
3549 #[test]
3550 fn test_string_from_code_point() {
3551 match eval("String.fromCodePoint(65, 66, 67)").unwrap() {
3552 Value::String(s) => assert_eq!(s, "ABC"),
3553 v => panic!("expected 'ABC', got {v:?}"),
3554 }
3555 }
3556
3557 // ── Number built-in tests ─────────────────────────────────
3558
3559 #[test]
3560 fn test_number_constructor() {
3561 match eval("Number('42')").unwrap() {
3562 Value::Number(n) => assert_eq!(n, 42.0),
3563 v => panic!("expected 42, got {v:?}"),
3564 }
3565 match eval("Number(true)").unwrap() {
3566 Value::Number(n) => assert_eq!(n, 1.0),
3567 v => panic!("expected 1, got {v:?}"),
3568 }
3569 match eval("Number()").unwrap() {
3570 Value::Number(n) => assert_eq!(n, 0.0),
3571 v => panic!("expected 0, got {v:?}"),
3572 }
3573 }
3574
3575 #[test]
3576 fn test_number_is_nan() {
3577 match eval("Number.isNaN(NaN)").unwrap() {
3578 Value::Boolean(b) => assert!(b),
3579 v => panic!("expected true, got {v:?}"),
3580 }
3581 match eval("Number.isNaN(42)").unwrap() {
3582 Value::Boolean(b) => assert!(!b),
3583 v => panic!("expected false, got {v:?}"),
3584 }
3585 // Number.isNaN doesn't coerce — string "NaN" is not NaN.
3586 match eval("Number.isNaN('NaN')").unwrap() {
3587 Value::Boolean(b) => assert!(!b),
3588 v => panic!("expected false, got {v:?}"),
3589 }
3590 }
3591
3592 #[test]
3593 fn test_number_is_finite() {
3594 match eval("Number.isFinite(42)").unwrap() {
3595 Value::Boolean(b) => assert!(b),
3596 v => panic!("expected true, got {v:?}"),
3597 }
3598 match eval("Number.isFinite(Infinity)").unwrap() {
3599 Value::Boolean(b) => assert!(!b),
3600 v => panic!("expected false, got {v:?}"),
3601 }
3602 }
3603
3604 #[test]
3605 fn test_number_is_integer() {
3606 match eval("Number.isInteger(42)").unwrap() {
3607 Value::Boolean(b) => assert!(b),
3608 v => panic!("expected true, got {v:?}"),
3609 }
3610 match eval("Number.isInteger(42.5)").unwrap() {
3611 Value::Boolean(b) => assert!(!b),
3612 v => panic!("expected false, got {v:?}"),
3613 }
3614 }
3615
3616 #[test]
3617 fn test_number_is_safe_integer() {
3618 match eval("Number.isSafeInteger(42)").unwrap() {
3619 Value::Boolean(b) => assert!(b),
3620 v => panic!("expected true, got {v:?}"),
3621 }
3622 match eval("Number.isSafeInteger(9007199254740992)").unwrap() {
3623 Value::Boolean(b) => assert!(!b),
3624 v => panic!("expected false, got {v:?}"),
3625 }
3626 }
3627
3628 #[test]
3629 fn test_number_constants() {
3630 match eval("Number.MAX_SAFE_INTEGER").unwrap() {
3631 Value::Number(n) => assert_eq!(n, 9007199254740991.0),
3632 v => panic!("expected MAX_SAFE_INTEGER, got {v:?}"),
3633 }
3634 match eval("Number.EPSILON").unwrap() {
3635 Value::Number(n) => assert_eq!(n, f64::EPSILON),
3636 v => panic!("expected EPSILON, got {v:?}"),
3637 }
3638 }
3639
3640 #[test]
3641 fn test_number_to_fixed() {
3642 match eval("var n = 3.14159; n.toFixed(2)").unwrap() {
3643 Value::String(s) => assert_eq!(s, "3.14"),
3644 v => panic!("expected '3.14', got {v:?}"),
3645 }
3646 }
3647
3648 #[test]
3649 fn test_number_to_string_radix() {
3650 match eval("var n = 255; n.toString(16)").unwrap() {
3651 Value::String(s) => assert_eq!(s, "ff"),
3652 v => panic!("expected 'ff', got {v:?}"),
3653 }
3654 match eval("var n = 10; n.toString(2)").unwrap() {
3655 Value::String(s) => assert_eq!(s, "1010"),
3656 v => panic!("expected '1010', got {v:?}"),
3657 }
3658 }
3659
3660 #[test]
3661 fn test_number_parse_int() {
3662 match eval("Number.parseInt('42')").unwrap() {
3663 Value::Number(n) => assert_eq!(n, 42.0),
3664 v => panic!("expected 42, got {v:?}"),
3665 }
3666 }
3667
3668 // ── Boolean built-in tests ────────────────────────────────
3669
3670 #[test]
3671 fn test_boolean_constructor() {
3672 match eval("Boolean(1)").unwrap() {
3673 Value::Boolean(b) => assert!(b),
3674 v => panic!("expected true, got {v:?}"),
3675 }
3676 match eval("Boolean(0)").unwrap() {
3677 Value::Boolean(b) => assert!(!b),
3678 v => panic!("expected false, got {v:?}"),
3679 }
3680 match eval("Boolean('')").unwrap() {
3681 Value::Boolean(b) => assert!(!b),
3682 v => panic!("expected false, got {v:?}"),
3683 }
3684 match eval("Boolean('hello')").unwrap() {
3685 Value::Boolean(b) => assert!(b),
3686 v => panic!("expected true, got {v:?}"),
3687 }
3688 }
3689
3690 #[test]
3691 fn test_boolean_to_string() {
3692 match eval("true.toString()").unwrap() {
3693 Value::String(s) => assert_eq!(s, "true"),
3694 v => panic!("expected 'true', got {v:?}"),
3695 }
3696 match eval("false.toString()").unwrap() {
3697 Value::String(s) => assert_eq!(s, "false"),
3698 v => panic!("expected 'false', got {v:?}"),
3699 }
3700 }
3701
3702 // ── Symbol built-in tests ─────────────────────────────────
3703
3704 #[test]
3705 fn test_symbol_uniqueness() {
3706 // Each Symbol() call should produce a unique value.
3707 match eval("var a = Symbol('x'); var b = Symbol('x'); a === b").unwrap() {
3708 Value::Boolean(b) => assert!(!b),
3709 v => panic!("expected false, got {v:?}"),
3710 }
3711 }
3712
3713 #[test]
3714 fn test_symbol_well_known() {
3715 match eval("typeof Symbol.iterator").unwrap() {
3716 Value::String(s) => assert_eq!(s, "string"),
3717 v => panic!("expected 'string', got {v:?}"),
3718 }
3719 match eval("Symbol.iterator").unwrap() {
3720 Value::String(s) => assert_eq!(s, "@@iterator"),
3721 v => panic!("expected '@@iterator', got {v:?}"),
3722 }
3723 }
3724
3725 #[test]
3726 fn test_symbol_for_and_key_for() {
3727 // "for" is a keyword, so use bracket notation: Symbol["for"](...).
3728 match eval("Symbol['for']('test') === Symbol['for']('test')").unwrap() {
3729 Value::Boolean(b) => assert!(b),
3730 v => panic!("expected true, got {v:?}"),
3731 }
3732 match eval("Symbol.keyFor(Symbol['for']('mykey'))").unwrap() {
3733 Value::String(s) => assert_eq!(s, "mykey"),
3734 v => panic!("expected 'mykey', got {v:?}"),
3735 }
3736 }
3737
3738 // ── Primitive auto-boxing tests ───────────────────────────
3739
3740 #[test]
3741 fn test_string_method_chaining() {
3742 match eval("' Hello World '.trim().toLowerCase()").unwrap() {
3743 Value::String(s) => assert_eq!(s, "hello world"),
3744 v => panic!("expected 'hello world', got {v:?}"),
3745 }
3746 }
3747
3748 #[test]
3749 fn test_string_substr() {
3750 match eval("'hello world'.substr(6, 5)").unwrap() {
3751 Value::String(s) => assert_eq!(s, "world"),
3752 v => panic!("expected 'world', got {v:?}"),
3753 }
3754 }
3755
3756 // ── Math built-in ─────────────────────────────────────────
3757
3758 #[test]
3759 fn test_math_constants() {
3760 let r = eval("Math.PI").unwrap();
3761 match r {
3762 Value::Number(n) => assert!((n - std::f64::consts::PI).abs() < 1e-10),
3763 _ => panic!("Expected number"),
3764 }
3765 let r = eval("Math.E").unwrap();
3766 match r {
3767 Value::Number(n) => assert!((n - std::f64::consts::E).abs() < 1e-10),
3768 _ => panic!("Expected number"),
3769 }
3770 let r = eval("Math.SQRT2").unwrap();
3771 match r {
3772 Value::Number(n) => assert!((n - std::f64::consts::SQRT_2).abs() < 1e-10),
3773 _ => panic!("Expected number"),
3774 }
3775 }
3776
3777 #[test]
3778 fn test_math_abs() {
3779 assert_eq!(eval("Math.abs(-5)").unwrap().to_number(), 5.0);
3780 assert_eq!(eval("Math.abs(3)").unwrap().to_number(), 3.0);
3781 assert_eq!(eval("Math.abs(0)").unwrap().to_number(), 0.0);
3782 }
3783
3784 #[test]
3785 fn test_math_floor_ceil_round_trunc() {
3786 assert_eq!(eval("Math.floor(4.7)").unwrap().to_number(), 4.0);
3787 assert_eq!(eval("Math.ceil(4.2)").unwrap().to_number(), 5.0);
3788 assert_eq!(eval("Math.round(4.5)").unwrap().to_number(), 5.0);
3789 assert_eq!(eval("Math.round(4.4)").unwrap().to_number(), 4.0);
3790 assert_eq!(eval("Math.trunc(4.7)").unwrap().to_number(), 4.0);
3791 assert_eq!(eval("Math.trunc(-4.7)").unwrap().to_number(), -4.0);
3792 }
3793
3794 #[test]
3795 fn test_math_max_min() {
3796 assert_eq!(eval("Math.max(1, 3, 2)").unwrap().to_number(), 3.0);
3797 assert_eq!(eval("Math.min(1, 3, 2)").unwrap().to_number(), 1.0);
3798 assert_eq!(eval("Math.max()").unwrap().to_number(), f64::NEG_INFINITY);
3799 assert_eq!(eval("Math.min()").unwrap().to_number(), f64::INFINITY);
3800 }
3801
3802 #[test]
3803 fn test_math_pow_sqrt() {
3804 assert_eq!(eval("Math.pow(2, 10)").unwrap().to_number(), 1024.0);
3805 assert_eq!(eval("Math.sqrt(9)").unwrap().to_number(), 3.0);
3806 let cbrt = eval("Math.cbrt(27)").unwrap().to_number();
3807 assert!((cbrt - 3.0).abs() < 1e-10);
3808 }
3809
3810 #[test]
3811 fn test_math_trig() {
3812 let sin = eval("Math.sin(0)").unwrap().to_number();
3813 assert!(sin.abs() < 1e-10);
3814 let cos = eval("Math.cos(0)").unwrap().to_number();
3815 assert!((cos - 1.0).abs() < 1e-10);
3816 let atan2 = eval("Math.atan2(1, 1)").unwrap().to_number();
3817 assert!((atan2 - std::f64::consts::FRAC_PI_4).abs() < 1e-10);
3818 }
3819
3820 #[test]
3821 fn test_math_log_exp() {
3822 let exp = eval("Math.exp(1)").unwrap().to_number();
3823 assert!((exp - std::f64::consts::E).abs() < 1e-10);
3824 let log = eval("Math.log(Math.E)").unwrap().to_number();
3825 assert!((log - 1.0).abs() < 1e-10);
3826 let log2 = eval("Math.log2(8)").unwrap().to_number();
3827 assert!((log2 - 3.0).abs() < 1e-10);
3828 let log10 = eval("Math.log10(1000)").unwrap().to_number();
3829 assert!((log10 - 3.0).abs() < 1e-10);
3830 }
3831
3832 #[test]
3833 fn test_math_sign() {
3834 assert_eq!(eval("Math.sign(5)").unwrap().to_number(), 1.0);
3835 assert_eq!(eval("Math.sign(-5)").unwrap().to_number(), -1.0);
3836 assert_eq!(eval("Math.sign(0)").unwrap().to_number(), 0.0);
3837 }
3838
3839 #[test]
3840 fn test_math_clz32() {
3841 assert_eq!(eval("Math.clz32(1)").unwrap().to_number(), 31.0);
3842 assert_eq!(eval("Math.clz32(0)").unwrap().to_number(), 32.0);
3843 }
3844
3845 #[test]
3846 fn test_math_imul() {
3847 assert_eq!(eval("Math.imul(3, 4)").unwrap().to_number(), 12.0);
3848 assert_eq!(eval("Math.imul(0xffffffff, 5)").unwrap().to_number(), -5.0);
3849 }
3850
3851 #[test]
3852 fn test_math_hypot() {
3853 assert_eq!(eval("Math.hypot(3, 4)").unwrap().to_number(), 5.0);
3854 assert_eq!(eval("Math.hypot()").unwrap().to_number(), 0.0);
3855 }
3856
3857 #[test]
3858 fn test_math_random() {
3859 match eval("var r = Math.random(); r >= 0 && r < 1").unwrap() {
3860 Value::Boolean(b) => assert!(b),
3861 v => panic!("expected true, got {v:?}"),
3862 }
3863 }
3864
3865 #[test]
3866 fn test_math_fround() {
3867 let r = eval("Math.fround(5.5)").unwrap().to_number();
3868 assert_eq!(r, 5.5f32 as f64);
3869 }
3870
3871 // ── Date built-in ─────────────────────────────────────────
3872
3873 #[test]
3874 fn test_date_now() {
3875 let r = eval("Date.now()").unwrap().to_number();
3876 assert!(r > 1_577_836_800_000.0);
3877 }
3878
3879 #[test]
3880 fn test_date_utc() {
3881 let r = eval("Date.UTC(2020, 0, 1)").unwrap().to_number();
3882 assert_eq!(r, 1_577_836_800_000.0);
3883 }
3884
3885 #[test]
3886 fn test_date_parse() {
3887 let r = eval("Date.parse('2020-01-01T00:00:00.000Z')")
3888 .unwrap()
3889 .to_number();
3890 assert_eq!(r, 1_577_836_800_000.0);
3891 }
3892
3893 #[test]
3894 fn test_date_constructor_ms() {
3895 let r = eval("var d = new Date(1577836800000); d.getFullYear()")
3896 .unwrap()
3897 .to_number();
3898 assert_eq!(r, 2020.0);
3899 }
3900
3901 #[test]
3902 fn test_date_constructor_components() {
3903 let r = eval("var d = new Date(2020, 0, 1, 0, 0, 0, 0); d.getTime()")
3904 .unwrap()
3905 .to_number();
3906 assert_eq!(r, 1_577_836_800_000.0);
3907 }
3908
3909 #[test]
3910 fn test_date_getters() {
3911 let src = r#"
3912 var d = new Date(1577836800000);
3913 var results = [
3914 d.getFullYear(),
3915 d.getMonth(),
3916 d.getDate(),
3917 d.getHours(),
3918 d.getMinutes(),
3919 d.getSeconds(),
3920 d.getMilliseconds(),
3921 d.getDay()
3922 ];
3923 results[0] === 2020 && results[1] === 0 && results[2] === 1 &&
3924 results[3] === 0 && results[4] === 0 && results[5] === 0 &&
3925 results[6] === 0 && results[7] === 3
3926 "#;
3927 match eval(src).unwrap() {
3928 Value::Boolean(b) => assert!(b),
3929 v => panic!("expected true, got {v:?}"),
3930 }
3931 }
3932
3933 #[test]
3934 fn test_date_setters() {
3935 let src = r#"
3936 var d = new Date(1577836800000);
3937 d.setFullYear(2025);
3938 d.getFullYear()
3939 "#;
3940 assert_eq!(eval(src).unwrap().to_number(), 2025.0);
3941 }
3942
3943 #[test]
3944 fn test_date_to_iso_string() {
3945 match eval("var d = new Date(1577836800000); d.toISOString()").unwrap() {
3946 Value::String(s) => assert_eq!(s, "2020-01-01T00:00:00.000Z"),
3947 v => panic!("expected ISO string, got {v:?}"),
3948 }
3949 }
3950
3951 #[test]
3952 fn test_date_value_of() {
3953 let r = eval("var d = new Date(1577836800000); d.valueOf()")
3954 .unwrap()
3955 .to_number();
3956 assert_eq!(r, 1_577_836_800_000.0);
3957 }
3958
3959 #[test]
3960 fn test_date_to_string() {
3961 let r = eval("var d = new Date(1577836800000); d.toString()").unwrap();
3962 match r {
3963 Value::String(s) => assert!(s.contains("2020") && s.contains("GMT")),
3964 _ => panic!("Expected string"),
3965 }
3966 }
3967
3968 #[test]
3969 fn test_date_to_json() {
3970 match eval("var d = new Date(1577836800000); d.toJSON()").unwrap() {
3971 Value::String(s) => assert_eq!(s, "2020-01-01T00:00:00.000Z"),
3972 v => panic!("expected ISO string, got {v:?}"),
3973 }
3974 }
3975
3976 #[test]
3977 fn test_date_constructor_string() {
3978 let r = eval("var d = new Date('2020-06-15T12:30:00Z'); d.getMonth()")
3979 .unwrap()
3980 .to_number();
3981 assert_eq!(r, 5.0);
3982 }
3983
3984 // ── JSON built-in ─────────────────────────────────────────
3985
3986 #[test]
3987 fn test_json_parse_primitives() {
3988 assert!(matches!(eval("JSON.parse('null')").unwrap(), Value::Null));
3989 match eval("JSON.parse('true')").unwrap() {
3990 Value::Boolean(b) => assert!(b),
3991 v => panic!("expected true, got {v:?}"),
3992 }
3993 match eval("JSON.parse('false')").unwrap() {
3994 Value::Boolean(b) => assert!(!b),
3995 v => panic!("expected false, got {v:?}"),
3996 }
3997 assert_eq!(eval("JSON.parse('42')").unwrap().to_number(), 42.0);
3998 match eval(r#"JSON.parse('"hello"')"#).unwrap() {
3999 Value::String(s) => assert_eq!(s, "hello"),
4000 v => panic!("expected 'hello', got {v:?}"),
4001 }
4002 }
4003
4004 #[test]
4005 fn test_json_parse_array() {
4006 let src = r#"
4007 var a = JSON.parse('[1, 2, 3]');
4008 a.length === 3 && a[0] === 1 && a[1] === 2 && a[2] === 3
4009 "#;
4010 match eval(src).unwrap() {
4011 Value::Boolean(b) => assert!(b),
4012 v => panic!("expected true, got {v:?}"),
4013 }
4014 }
4015
4016 #[test]
4017 fn test_json_parse_object() {
4018 let src = r#"
4019 var o = JSON.parse('{"name":"test","value":42}');
4020 o.name === "test" && o.value === 42
4021 "#;
4022 match eval(src).unwrap() {
4023 Value::Boolean(b) => assert!(b),
4024 v => panic!("expected true, got {v:?}"),
4025 }
4026 }
4027
4028 #[test]
4029 fn test_json_parse_nested() {
4030 let src = r#"
4031 var o = JSON.parse('{"a":[1,{"b":2}]}');
4032 o.a[1].b === 2
4033 "#;
4034 match eval(src).unwrap() {
4035 Value::Boolean(b) => assert!(b),
4036 v => panic!("expected true, got {v:?}"),
4037 }
4038 }
4039
4040 #[test]
4041 fn test_json_parse_invalid() {
4042 assert!(eval("JSON.parse('{invalid}')").is_err());
4043 assert!(eval("JSON.parse('')").is_err());
4044 }
4045
4046 #[test]
4047 fn test_json_stringify_primitives() {
4048 match eval("JSON.stringify(null)").unwrap() {
4049 Value::String(s) => assert_eq!(s, "null"),
4050 v => panic!("expected 'null', got {v:?}"),
4051 }
4052 match eval("JSON.stringify(true)").unwrap() {
4053 Value::String(s) => assert_eq!(s, "true"),
4054 v => panic!("expected 'true', got {v:?}"),
4055 }
4056 match eval("JSON.stringify(42)").unwrap() {
4057 Value::String(s) => assert_eq!(s, "42"),
4058 v => panic!("expected '42', got {v:?}"),
4059 }
4060 match eval(r#"JSON.stringify("hello")"#).unwrap() {
4061 Value::String(s) => assert_eq!(s, "\"hello\""),
4062 v => panic!("expected quoted hello, got {v:?}"),
4063 }
4064 }
4065
4066 #[test]
4067 fn test_json_stringify_array() {
4068 match eval("JSON.stringify([1, 2, 3])").unwrap() {
4069 Value::String(s) => assert_eq!(s, "[1,2,3]"),
4070 v => panic!("expected '[1,2,3]', got {v:?}"),
4071 }
4072 }
4073
4074 #[test]
4075 fn test_json_stringify_object() {
4076 let src = r#"
4077 var o = {a: 1, b: "hello"};
4078 JSON.stringify(o)
4079 "#;
4080 let r = eval(src).unwrap();
4081 match r {
4082 Value::String(s) => {
4083 assert!(s.contains("\"a\"") && s.contains("\"b\""));
4084 assert!(s.contains('1') && s.contains("\"hello\""));
4085 }
4086 _ => panic!("Expected string"),
4087 }
4088 }
4089
4090 #[test]
4091 fn test_json_stringify_nested() {
4092 let src = r#"
4093 JSON.stringify({a: [1, 2], b: {c: 3}})
4094 "#;
4095 let r = eval(src).unwrap();
4096 match r {
4097 Value::String(s) => {
4098 assert!(s.contains("[1,2]"));
4099 assert!(s.contains("\"c\":3") || s.contains("\"c\": 3"));
4100 }
4101 _ => panic!("Expected string"),
4102 }
4103 }
4104
4105 #[test]
4106 fn test_json_stringify_special_values() {
4107 match eval("JSON.stringify(NaN)").unwrap() {
4108 Value::String(s) => assert_eq!(s, "null"),
4109 v => panic!("expected 'null', got {v:?}"),
4110 }
4111 match eval("JSON.stringify(Infinity)").unwrap() {
4112 Value::String(s) => assert_eq!(s, "null"),
4113 v => panic!("expected 'null', got {v:?}"),
4114 }
4115 assert!(matches!(
4116 eval("JSON.stringify(undefined)").unwrap(),
4117 Value::Undefined
4118 ));
4119 }
4120
4121 #[test]
4122 fn test_json_stringify_with_indent() {
4123 let src = r#"JSON.stringify([1, 2], null, 2)"#;
4124 let r = eval(src).unwrap();
4125 match r {
4126 Value::String(s) => {
4127 assert!(s.contains('\n'));
4128 assert!(s.contains(" 1"));
4129 }
4130 _ => panic!("Expected string"),
4131 }
4132 }
4133
4134 #[test]
4135 fn test_json_parse_escape_sequences() {
4136 let src = r#"JSON.parse('"hello\\nworld"')"#;
4137 match eval(src).unwrap() {
4138 Value::String(s) => assert_eq!(s, "hello\nworld"),
4139 v => panic!("expected escaped string, got {v:?}"),
4140 }
4141 }
4142
4143 #[test]
4144 fn test_json_roundtrip() {
4145 let src = r#"
4146 var original = {name: "test", values: [1, 2, 3], nested: {ok: true}};
4147 var json = JSON.stringify(original);
4148 var parsed = JSON.parse(json);
4149 parsed.name === "test" && parsed.values[1] === 2 && parsed.nested.ok === true
4150 "#;
4151 match eval(src).unwrap() {
4152 Value::Boolean(b) => assert!(b),
4153 v => panic!("expected true, got {v:?}"),
4154 }
4155 }
4156
4157 #[test]
4158 fn test_json_stringify_circular_detection() {
4159 let src = r#"
4160 var obj = {};
4161 obj.self = obj;
4162 try {
4163 JSON.stringify(obj);
4164 false;
4165 } catch (e) {
4166 e.message.indexOf("circular") !== -1;
4167 }
4168 "#;
4169 match eval(src).unwrap() {
4170 Value::Boolean(b) => assert!(b),
4171 v => panic!("expected true, got {v:?}"),
4172 }
4173 }
4174
4175 #[test]
4176 fn test_json_parse_unicode_escape() {
4177 let src = r#"JSON.parse('"\\u0041"')"#;
4178 match eval(src).unwrap() {
4179 Value::String(s) => assert_eq!(s, "A"),
4180 v => panic!("expected 'A', got {v:?}"),
4181 }
4182 }
4183
4184 #[test]
4185 fn test_json_stringify_empty() {
4186 match eval("JSON.stringify([])").unwrap() {
4187 Value::String(s) => assert_eq!(s, "[]"),
4188 v => panic!("expected '[]', got {v:?}"),
4189 }
4190 match eval("JSON.stringify({})").unwrap() {
4191 Value::String(s) => assert_eq!(s, "{}"),
4192 v => panic!("expected '{{}}', got {v:?}"),
4193 }
4194 }
4195}