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 /// Built-in RegExp.prototype (for RegExp constructor objects).
701 pub regexp_prototype: Option<GcRef>,
702}
703
704/// Maximum register file size.
705const MAX_REGISTERS: usize = 4096;
706/// Maximum call depth.
707const MAX_CALL_DEPTH: usize = 512;
708
709impl Vm {
710 pub fn new() -> Self {
711 let mut vm = Self {
712 registers: vec![Value::Undefined; 256],
713 frames: Vec::new(),
714 globals: HashMap::new(),
715 gc: Gc::new(),
716 instruction_limit: None,
717 instructions_executed: 0,
718 object_prototype: None,
719 array_prototype: None,
720 string_prototype: None,
721 number_prototype: None,
722 boolean_prototype: None,
723 date_prototype: None,
724 regexp_prototype: None,
725 };
726 crate::builtins::init_builtins(&mut vm);
727 vm
728 }
729
730 /// Set an instruction limit. The VM will return a RuntimeError after
731 /// executing this many instructions.
732 pub fn set_instruction_limit(&mut self, limit: u64) {
733 self.instruction_limit = Some(limit);
734 }
735
736 /// Execute a compiled top-level function and return the completion value.
737 pub fn execute(&mut self, func: &Function) -> Result<Value, RuntimeError> {
738 let reg_count = func.register_count as usize;
739 self.ensure_registers(reg_count);
740
741 self.frames.push(CallFrame {
742 func: func.clone(),
743 ip: 0,
744 base: 0,
745 return_reg: 0,
746 exception_handlers: Vec::new(),
747 upvalues: Vec::new(),
748 });
749
750 self.run()
751 }
752
753 /// Ensure the register file has at least `needed` slots.
754 fn ensure_registers(&mut self, needed: usize) {
755 if needed > self.registers.len() {
756 if needed > MAX_REGISTERS {
757 return;
758 }
759 self.registers.resize(needed, Value::Undefined);
760 }
761 }
762
763 /// Read a u8 from the current frame's bytecode and advance IP.
764 #[inline]
765 fn read_u8(frame: &mut CallFrame) -> u8 {
766 let b = frame.func.code[frame.ip];
767 frame.ip += 1;
768 b
769 }
770
771 /// Read a u16 (little-endian) from the current frame's bytecode and advance IP.
772 #[inline]
773 fn read_u16(frame: &mut CallFrame) -> u16 {
774 let lo = frame.func.code[frame.ip];
775 let hi = frame.func.code[frame.ip + 1];
776 frame.ip += 2;
777 u16::from_le_bytes([lo, hi])
778 }
779
780 /// Read an i32 (little-endian) from the current frame's bytecode and advance IP.
781 #[inline]
782 fn read_i32(frame: &mut CallFrame) -> i32 {
783 let bytes = [
784 frame.func.code[frame.ip],
785 frame.func.code[frame.ip + 1],
786 frame.func.code[frame.ip + 2],
787 frame.func.code[frame.ip + 3],
788 ];
789 frame.ip += 4;
790 i32::from_le_bytes(bytes)
791 }
792
793 /// Collect all GcRef values reachable from the mutator (roots for GC).
794 fn collect_roots(&self) -> Vec<GcRef> {
795 let mut roots = Vec::new();
796 for val in &self.registers {
797 if let Some(r) = val.gc_ref() {
798 roots.push(r);
799 }
800 }
801 for val in self.globals.values() {
802 if let Some(r) = val.gc_ref() {
803 roots.push(r);
804 }
805 }
806 for frame in &self.frames {
807 for &uv in &frame.upvalues {
808 roots.push(uv);
809 }
810 }
811 // Built-in prototype roots.
812 if let Some(r) = self.object_prototype {
813 roots.push(r);
814 }
815 if let Some(r) = self.array_prototype {
816 roots.push(r);
817 }
818 if let Some(r) = self.string_prototype {
819 roots.push(r);
820 }
821 if let Some(r) = self.number_prototype {
822 roots.push(r);
823 }
824 if let Some(r) = self.boolean_prototype {
825 roots.push(r);
826 }
827 roots
828 }
829
830 /// Main dispatch loop.
831 fn run(&mut self) -> Result<Value, RuntimeError> {
832 loop {
833 let fi = self.frames.len() - 1;
834
835 // Check if we've reached the end of bytecode.
836 if self.frames[fi].ip >= self.frames[fi].func.code.len() {
837 if self.frames.len() == 1 {
838 self.frames.pop();
839 return Ok(Value::Undefined);
840 }
841 let old = self.frames.pop().unwrap();
842 self.registers[old.return_reg] = Value::Undefined;
843 continue;
844 }
845
846 // Instruction limit check (for test harnesses).
847 if let Some(limit) = self.instruction_limit {
848 self.instructions_executed += 1;
849 if self.instructions_executed > limit {
850 return Err(RuntimeError {
851 kind: ErrorKind::Error,
852 message: "instruction limit exceeded".into(),
853 });
854 }
855 }
856
857 let opcode_byte = self.frames[fi].func.code[self.frames[fi].ip];
858 self.frames[fi].ip += 1;
859
860 let Some(op) = Op::from_byte(opcode_byte) else {
861 return Err(RuntimeError {
862 kind: ErrorKind::Error,
863 message: format!("unknown opcode: 0x{opcode_byte:02X}"),
864 });
865 };
866
867 match op {
868 // ── Register loads ──────────────────────────────
869 Op::LoadConst => {
870 let dst = Self::read_u8(&mut self.frames[fi]);
871 let idx = Self::read_u16(&mut self.frames[fi]) as usize;
872 let base = self.frames[fi].base;
873 let val = match &self.frames[fi].func.constants[idx] {
874 Constant::Number(n) => Value::Number(*n),
875 Constant::String(s) => Value::String(s.clone()),
876 };
877 self.registers[base + dst as usize] = val;
878 }
879 Op::LoadNull => {
880 let dst = Self::read_u8(&mut self.frames[fi]);
881 let base = self.frames[fi].base;
882 self.registers[base + dst as usize] = Value::Null;
883 }
884 Op::LoadUndefined => {
885 let dst = Self::read_u8(&mut self.frames[fi]);
886 let base = self.frames[fi].base;
887 self.registers[base + dst as usize] = Value::Undefined;
888 }
889 Op::LoadTrue => {
890 let dst = Self::read_u8(&mut self.frames[fi]);
891 let base = self.frames[fi].base;
892 self.registers[base + dst as usize] = Value::Boolean(true);
893 }
894 Op::LoadFalse => {
895 let dst = Self::read_u8(&mut self.frames[fi]);
896 let base = self.frames[fi].base;
897 self.registers[base + dst as usize] = Value::Boolean(false);
898 }
899 Op::LoadInt8 => {
900 let dst = Self::read_u8(&mut self.frames[fi]);
901 let val = Self::read_u8(&mut self.frames[fi]) as i8;
902 let base = self.frames[fi].base;
903 self.registers[base + dst as usize] = Value::Number(val as f64);
904 }
905 Op::Move => {
906 let dst = Self::read_u8(&mut self.frames[fi]);
907 let src = Self::read_u8(&mut self.frames[fi]);
908 let base = self.frames[fi].base;
909 let val = self.registers[base + src as usize].clone();
910 self.registers[base + dst as usize] = val;
911 }
912
913 // ── Global access ──────────────────────────────
914 Op::LoadGlobal => {
915 let dst = Self::read_u8(&mut self.frames[fi]);
916 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
917 let base = self.frames[fi].base;
918 let name = &self.frames[fi].func.names[name_idx];
919 let val = self.globals.get(name).cloned().unwrap_or(Value::Undefined);
920 self.registers[base + dst as usize] = val;
921 }
922 Op::StoreGlobal => {
923 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
924 let src = Self::read_u8(&mut self.frames[fi]);
925 let base = self.frames[fi].base;
926 let name = self.frames[fi].func.names[name_idx].clone();
927 let val = self.registers[base + src as usize].clone();
928 self.globals.insert(name, val);
929 }
930
931 // ── Arithmetic ─────────────────────────────────
932 Op::Add => {
933 let dst = Self::read_u8(&mut self.frames[fi]);
934 let lhs_r = Self::read_u8(&mut self.frames[fi]);
935 let rhs_r = Self::read_u8(&mut self.frames[fi]);
936 let base = self.frames[fi].base;
937 let result = add_values(
938 &self.registers[base + lhs_r as usize],
939 &self.registers[base + rhs_r as usize],
940 &self.gc,
941 );
942 self.registers[base + dst as usize] = result;
943 }
944 Op::Sub => {
945 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
946 let result = self.registers[base + lhs_r].to_number()
947 - self.registers[base + rhs_r].to_number();
948 self.registers[base + dst] = Value::Number(result);
949 }
950 Op::Mul => {
951 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
952 let result = self.registers[base + lhs_r].to_number()
953 * self.registers[base + rhs_r].to_number();
954 self.registers[base + dst] = Value::Number(result);
955 }
956 Op::Div => {
957 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
958 let result = self.registers[base + lhs_r].to_number()
959 / self.registers[base + rhs_r].to_number();
960 self.registers[base + dst] = Value::Number(result);
961 }
962 Op::Rem => {
963 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
964 let result = self.registers[base + lhs_r].to_number()
965 % self.registers[base + rhs_r].to_number();
966 self.registers[base + dst] = Value::Number(result);
967 }
968 Op::Exp => {
969 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
970 let result = self.registers[base + lhs_r]
971 .to_number()
972 .powf(self.registers[base + rhs_r].to_number());
973 self.registers[base + dst] = Value::Number(result);
974 }
975 Op::Neg => {
976 let dst = Self::read_u8(&mut self.frames[fi]);
977 let src = Self::read_u8(&mut self.frames[fi]);
978 let base = self.frames[fi].base;
979 let result = -self.registers[base + src as usize].to_number();
980 self.registers[base + dst as usize] = Value::Number(result);
981 }
982
983 // ── Bitwise ────────────────────────────────────
984 Op::BitAnd => {
985 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
986 let a = to_int32(&self.registers[base + lhs_r]);
987 let b = to_int32(&self.registers[base + rhs_r]);
988 self.registers[base + dst] = Value::Number((a & b) as f64);
989 }
990 Op::BitOr => {
991 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
992 let a = to_int32(&self.registers[base + lhs_r]);
993 let b = to_int32(&self.registers[base + rhs_r]);
994 self.registers[base + dst] = Value::Number((a | b) as f64);
995 }
996 Op::BitXor => {
997 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
998 let a = to_int32(&self.registers[base + lhs_r]);
999 let b = to_int32(&self.registers[base + rhs_r]);
1000 self.registers[base + dst] = Value::Number((a ^ b) as f64);
1001 }
1002 Op::ShiftLeft => {
1003 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1004 let a = to_int32(&self.registers[base + lhs_r]);
1005 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
1006 self.registers[base + dst] = Value::Number((a << b) as f64);
1007 }
1008 Op::ShiftRight => {
1009 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1010 let a = to_int32(&self.registers[base + lhs_r]);
1011 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
1012 self.registers[base + dst] = Value::Number((a >> b) as f64);
1013 }
1014 Op::UShiftRight => {
1015 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1016 let a = to_uint32(&self.registers[base + lhs_r]);
1017 let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F;
1018 self.registers[base + dst] = Value::Number((a >> b) as f64);
1019 }
1020 Op::BitNot => {
1021 let dst = Self::read_u8(&mut self.frames[fi]);
1022 let src = Self::read_u8(&mut self.frames[fi]);
1023 let base = self.frames[fi].base;
1024 let result = !to_int32(&self.registers[base + src as usize]);
1025 self.registers[base + dst as usize] = Value::Number(result as f64);
1026 }
1027
1028 // ── Comparison ─────────────────────────────────
1029 Op::Eq => {
1030 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1031 let result =
1032 abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
1033 self.registers[base + dst] = Value::Boolean(result);
1034 }
1035 Op::StrictEq => {
1036 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1037 let result =
1038 strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
1039 self.registers[base + dst] = Value::Boolean(result);
1040 }
1041 Op::NotEq => {
1042 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1043 let result =
1044 !abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
1045 self.registers[base + dst] = Value::Boolean(result);
1046 }
1047 Op::StrictNotEq => {
1048 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1049 let result =
1050 !strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]);
1051 self.registers[base + dst] = Value::Boolean(result);
1052 }
1053 Op::LessThan => {
1054 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1055 let result = abstract_relational(
1056 &self.registers[base + lhs_r],
1057 &self.registers[base + rhs_r],
1058 |ord| ord == std::cmp::Ordering::Less,
1059 );
1060 self.registers[base + dst] = Value::Boolean(result);
1061 }
1062 Op::LessEq => {
1063 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1064 let result = abstract_relational(
1065 &self.registers[base + lhs_r],
1066 &self.registers[base + rhs_r],
1067 |ord| ord != std::cmp::Ordering::Greater,
1068 );
1069 self.registers[base + dst] = Value::Boolean(result);
1070 }
1071 Op::GreaterThan => {
1072 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1073 let result = abstract_relational(
1074 &self.registers[base + lhs_r],
1075 &self.registers[base + rhs_r],
1076 |ord| ord == std::cmp::Ordering::Greater,
1077 );
1078 self.registers[base + dst] = Value::Boolean(result);
1079 }
1080 Op::GreaterEq => {
1081 let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi);
1082 let result = abstract_relational(
1083 &self.registers[base + lhs_r],
1084 &self.registers[base + rhs_r],
1085 |ord| ord != std::cmp::Ordering::Less,
1086 );
1087 self.registers[base + dst] = Value::Boolean(result);
1088 }
1089
1090 // ── Logical / unary ────────────────────────────
1091 Op::LogicalNot => {
1092 let dst = Self::read_u8(&mut self.frames[fi]);
1093 let src = Self::read_u8(&mut self.frames[fi]);
1094 let base = self.frames[fi].base;
1095 let result = !self.registers[base + src as usize].to_boolean();
1096 self.registers[base + dst as usize] = Value::Boolean(result);
1097 }
1098 Op::TypeOf => {
1099 let dst = Self::read_u8(&mut self.frames[fi]);
1100 let src = Self::read_u8(&mut self.frames[fi]);
1101 let base = self.frames[fi].base;
1102 let t = self.registers[base + src as usize].type_of();
1103 self.registers[base + dst as usize] = Value::String(t.to_string());
1104 }
1105 Op::InstanceOf => {
1106 let dst = Self::read_u8(&mut self.frames[fi]);
1107 let lhs_r = Self::read_u8(&mut self.frames[fi]);
1108 let rhs_r = Self::read_u8(&mut self.frames[fi]);
1109 let base = self.frames[fi].base;
1110 let result = match (
1111 &self.registers[base + lhs_r as usize],
1112 &self.registers[base + rhs_r as usize],
1113 ) {
1114 (Value::Object(obj_ref), Value::Function(ctor_ref)) => {
1115 gc_instanceof(&self.gc, *obj_ref, *ctor_ref)
1116 }
1117 (_, Value::Function(_)) => false,
1118 _ => {
1119 return Err(RuntimeError::type_error(
1120 "Right-hand side of instanceof is not callable",
1121 ));
1122 }
1123 };
1124 self.registers[base + dst as usize] = Value::Boolean(result);
1125 }
1126 Op::In => {
1127 let dst = Self::read_u8(&mut self.frames[fi]);
1128 let key_r = Self::read_u8(&mut self.frames[fi]);
1129 let obj_r = Self::read_u8(&mut self.frames[fi]);
1130 let base = self.frames[fi].base;
1131 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1132 let result = match self.registers[base + obj_r as usize] {
1133 Value::Object(gc_ref) => {
1134 Value::Boolean(gc_has_property(&self.gc, gc_ref, &key))
1135 }
1136 _ => {
1137 return Err(RuntimeError::type_error(
1138 "Cannot use 'in' operator to search for property in non-object",
1139 ));
1140 }
1141 };
1142 self.registers[base + dst as usize] = result;
1143 }
1144 Op::Void => {
1145 let dst = Self::read_u8(&mut self.frames[fi]);
1146 let _src = Self::read_u8(&mut self.frames[fi]);
1147 let base = self.frames[fi].base;
1148 self.registers[base + dst as usize] = Value::Undefined;
1149 }
1150
1151 // ── Control flow ───────────────────────────────
1152 Op::Jump => {
1153 let offset = Self::read_i32(&mut self.frames[fi]);
1154 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1155 }
1156 Op::JumpIfTrue => {
1157 let reg = Self::read_u8(&mut self.frames[fi]);
1158 let offset = Self::read_i32(&mut self.frames[fi]);
1159 let base = self.frames[fi].base;
1160 if self.registers[base + reg as usize].to_boolean() {
1161 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1162 }
1163 }
1164 Op::JumpIfFalse => {
1165 let reg = Self::read_u8(&mut self.frames[fi]);
1166 let offset = Self::read_i32(&mut self.frames[fi]);
1167 let base = self.frames[fi].base;
1168 if !self.registers[base + reg as usize].to_boolean() {
1169 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1170 }
1171 }
1172 Op::JumpIfNullish => {
1173 let reg = Self::read_u8(&mut self.frames[fi]);
1174 let offset = Self::read_i32(&mut self.frames[fi]);
1175 let base = self.frames[fi].base;
1176 if self.registers[base + reg as usize].is_nullish() {
1177 self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize;
1178 }
1179 }
1180
1181 // ── Functions / calls ──────────────────────────
1182 Op::Call => {
1183 let dst = Self::read_u8(&mut self.frames[fi]);
1184 let func_r = Self::read_u8(&mut self.frames[fi]);
1185 let args_start = Self::read_u8(&mut self.frames[fi]);
1186 let arg_count = Self::read_u8(&mut self.frames[fi]);
1187 let base = self.frames[fi].base;
1188
1189 // Extract function GcRef.
1190 let func_gc_ref = match self.registers[base + func_r as usize] {
1191 Value::Function(r) => r,
1192 _ => {
1193 let desc =
1194 self.registers[base + func_r as usize].to_js_string(&self.gc);
1195 let err = RuntimeError::type_error(format!("{desc} is not a function"));
1196 let err_val = err.to_value(&mut self.gc);
1197 if !self.handle_exception(err_val) {
1198 return Err(err);
1199 }
1200 continue;
1201 }
1202 };
1203
1204 // Collect arguments.
1205 let mut args = Vec::with_capacity(arg_count as usize);
1206 for i in 0..arg_count {
1207 args.push(self.registers[base + (args_start + i) as usize].clone());
1208 }
1209
1210 // Read function data from GC (scoped borrow).
1211 let call_info = {
1212 match self.gc.get(func_gc_ref) {
1213 Some(HeapObject::Function(fdata)) => match &fdata.kind {
1214 FunctionKind::Native(n) => CallInfo::Native(n.callback),
1215 FunctionKind::Bytecode(bc) => {
1216 CallInfo::Bytecode(bc.func.clone(), fdata.upvalues.clone())
1217 }
1218 },
1219 _ => {
1220 let err = RuntimeError::type_error("not a function");
1221 let err_val = err.to_value(&mut self.gc);
1222 if !self.handle_exception(err_val) {
1223 return Err(err);
1224 }
1225 continue;
1226 }
1227 }
1228 };
1229
1230 match call_info {
1231 CallInfo::Native(callback) => {
1232 let this = self
1233 .globals
1234 .get("this")
1235 .cloned()
1236 .unwrap_or(Value::Undefined);
1237 let mut ctx = NativeContext {
1238 gc: &mut self.gc,
1239 this,
1240 };
1241 match callback(&args, &mut ctx) {
1242 Ok(val) => {
1243 self.registers[base + dst as usize] = val;
1244 }
1245 Err(err) => {
1246 let err_val = err.to_value(&mut self.gc);
1247 if !self.handle_exception(err_val) {
1248 return Err(err);
1249 }
1250 }
1251 }
1252 }
1253 CallInfo::Bytecode(callee_func, callee_upvalues) => {
1254 if self.frames.len() >= MAX_CALL_DEPTH {
1255 let err =
1256 RuntimeError::range_error("Maximum call stack size exceeded");
1257 let err_val = err.to_value(&mut self.gc);
1258 if !self.handle_exception(err_val) {
1259 return Err(err);
1260 }
1261 continue;
1262 }
1263
1264 let callee_base = base + self.frames[fi].func.register_count as usize;
1265 let callee_regs = callee_func.register_count as usize;
1266 self.ensure_registers(callee_base + callee_regs);
1267
1268 // Copy arguments into callee's registers.
1269 for i in 0..callee_func.param_count.min(arg_count) {
1270 self.registers[callee_base + i as usize] = args[i as usize].clone();
1271 }
1272 // Fill remaining params with undefined.
1273 for i in arg_count..callee_func.param_count {
1274 self.registers[callee_base + i as usize] = Value::Undefined;
1275 }
1276
1277 self.frames.push(CallFrame {
1278 func: callee_func,
1279 ip: 0,
1280 base: callee_base,
1281 return_reg: base + dst as usize,
1282 exception_handlers: Vec::new(),
1283 upvalues: callee_upvalues,
1284 });
1285 }
1286 }
1287 }
1288 Op::Return => {
1289 let reg = Self::read_u8(&mut self.frames[fi]);
1290 let base = self.frames[fi].base;
1291 let val = self.registers[base + reg as usize].clone();
1292
1293 if self.frames.len() == 1 {
1294 self.frames.pop();
1295 return Ok(val);
1296 }
1297
1298 let old = self.frames.pop().unwrap();
1299 self.registers[old.return_reg] = val;
1300 }
1301 Op::Throw => {
1302 let reg = Self::read_u8(&mut self.frames[fi]);
1303 let base = self.frames[fi].base;
1304 let val = self.registers[base + reg as usize].clone();
1305
1306 if !self.handle_exception(val) {
1307 let msg = self.registers[base + reg as usize].to_js_string(&self.gc);
1308 return Err(RuntimeError {
1309 kind: ErrorKind::Error,
1310 message: msg,
1311 });
1312 }
1313 }
1314 Op::CreateClosure => {
1315 let dst = Self::read_u8(&mut self.frames[fi]);
1316 let func_idx = Self::read_u16(&mut self.frames[fi]) as usize;
1317 let base = self.frames[fi].base;
1318 let inner_func = self.frames[fi].func.functions[func_idx].clone();
1319 let name = inner_func.name.clone();
1320
1321 // Resolve upvalues from the parent scope.
1322 let mut upvalues = Vec::with_capacity(inner_func.upvalue_defs.len());
1323 for def in &inner_func.upvalue_defs {
1324 let cell_ref = if def.is_local {
1325 // Parent has a cell in register `def.index`.
1326 match &self.registers[base + def.index as usize] {
1327 Value::Object(r) => *r,
1328 _ => {
1329 return Err(RuntimeError {
1330 kind: ErrorKind::Error,
1331 message:
1332 "CreateClosure: upvalue register does not hold a cell"
1333 .into(),
1334 });
1335 }
1336 }
1337 } else {
1338 // Transitive: parent's own upvalue at `def.index`.
1339 self.frames[fi].upvalues[def.index as usize]
1340 };
1341 upvalues.push(cell_ref);
1342 }
1343
1344 // Create a .prototype object for the function (for instanceof).
1345 let proto_obj = self.gc.alloc(HeapObject::Object(ObjectData::new()));
1346 let gc_ref = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
1347 name,
1348 kind: FunctionKind::Bytecode(BytecodeFunc { func: inner_func }),
1349 prototype_obj: Some(proto_obj),
1350 properties: HashMap::new(),
1351 upvalues,
1352 })));
1353 // Set .prototype.constructor = this function.
1354 if let Some(HeapObject::Object(data)) = self.gc.get_mut(proto_obj) {
1355 data.properties.insert(
1356 "constructor".to_string(),
1357 Property {
1358 value: Value::Function(gc_ref),
1359 writable: true,
1360 enumerable: false,
1361 configurable: true,
1362 },
1363 );
1364 }
1365 self.registers[base + dst as usize] = Value::Function(gc_ref);
1366
1367 // Trigger GC if needed.
1368 if self.gc.should_collect() {
1369 let roots = self.collect_roots();
1370 self.gc.collect(&roots);
1371 }
1372 }
1373
1374 // ── Object / property ──────────────────────────
1375 Op::GetProperty => {
1376 let dst = Self::read_u8(&mut self.frames[fi]);
1377 let obj_r = Self::read_u8(&mut self.frames[fi]);
1378 let key_r = Self::read_u8(&mut self.frames[fi]);
1379 let base = self.frames[fi].base;
1380 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1381 let val = match self.registers[base + obj_r as usize] {
1382 Value::Object(gc_ref) | Value::Function(gc_ref) => {
1383 gc_get_property(&self.gc, gc_ref, &key)
1384 }
1385 Value::String(ref s) => {
1386 let v = string_get_property(s, &key);
1387 if matches!(v, Value::Undefined) {
1388 self.string_prototype
1389 .map(|p| gc_get_property(&self.gc, p, &key))
1390 .unwrap_or(Value::Undefined)
1391 } else {
1392 v
1393 }
1394 }
1395 Value::Number(_) => self
1396 .number_prototype
1397 .map(|p| gc_get_property(&self.gc, p, &key))
1398 .unwrap_or(Value::Undefined),
1399 Value::Boolean(_) => self
1400 .boolean_prototype
1401 .map(|p| gc_get_property(&self.gc, p, &key))
1402 .unwrap_or(Value::Undefined),
1403 _ => Value::Undefined,
1404 };
1405 self.registers[base + dst as usize] = val;
1406 }
1407 Op::SetProperty => {
1408 let obj_r = Self::read_u8(&mut self.frames[fi]);
1409 let key_r = Self::read_u8(&mut self.frames[fi]);
1410 let val_r = Self::read_u8(&mut self.frames[fi]);
1411 let base = self.frames[fi].base;
1412 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1413 let val = self.registers[base + val_r as usize].clone();
1414 match self.registers[base + obj_r as usize] {
1415 Value::Object(gc_ref) => {
1416 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1417 if let Some(prop) = data.properties.get_mut(&key) {
1418 if prop.writable {
1419 prop.value = val;
1420 }
1421 } else {
1422 data.properties.insert(key, Property::data(val));
1423 }
1424 }
1425 }
1426 Value::Function(gc_ref) => {
1427 if let Some(HeapObject::Function(fdata)) = self.gc.get_mut(gc_ref) {
1428 if let Some(prop) = fdata.properties.get_mut(&key) {
1429 if prop.writable {
1430 prop.value = val;
1431 }
1432 } else {
1433 fdata.properties.insert(key, Property::data(val));
1434 }
1435 }
1436 }
1437 _ => {}
1438 }
1439 }
1440 Op::CreateObject => {
1441 let dst = Self::read_u8(&mut self.frames[fi]);
1442 let base = self.frames[fi].base;
1443 let mut obj = ObjectData::new();
1444 obj.prototype = self.object_prototype;
1445 let gc_ref = self.gc.alloc(HeapObject::Object(obj));
1446 self.registers[base + dst as usize] = Value::Object(gc_ref);
1447
1448 if self.gc.should_collect() {
1449 let roots = self.collect_roots();
1450 self.gc.collect(&roots);
1451 }
1452 }
1453 Op::CreateArray => {
1454 let dst = Self::read_u8(&mut self.frames[fi]);
1455 let base = self.frames[fi].base;
1456 let mut obj = ObjectData::new();
1457 obj.prototype = self.array_prototype;
1458 obj.properties.insert(
1459 "length".to_string(),
1460 Property {
1461 value: Value::Number(0.0),
1462 writable: true,
1463 enumerable: false,
1464 configurable: false,
1465 },
1466 );
1467 let gc_ref = self.gc.alloc(HeapObject::Object(obj));
1468 self.registers[base + dst as usize] = Value::Object(gc_ref);
1469
1470 if self.gc.should_collect() {
1471 let roots = self.collect_roots();
1472 self.gc.collect(&roots);
1473 }
1474 }
1475 Op::GetPropertyByName => {
1476 let dst = Self::read_u8(&mut self.frames[fi]);
1477 let obj_r = Self::read_u8(&mut self.frames[fi]);
1478 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
1479 let base = self.frames[fi].base;
1480 let key = self.frames[fi].func.names[name_idx].clone();
1481 let val = match self.registers[base + obj_r as usize] {
1482 Value::Object(gc_ref) | Value::Function(gc_ref) => {
1483 gc_get_property(&self.gc, gc_ref, &key)
1484 }
1485 Value::String(ref s) => {
1486 let v = string_get_property(s, &key);
1487 if matches!(v, Value::Undefined) {
1488 self.string_prototype
1489 .map(|p| gc_get_property(&self.gc, p, &key))
1490 .unwrap_or(Value::Undefined)
1491 } else {
1492 v
1493 }
1494 }
1495 Value::Number(_) => self
1496 .number_prototype
1497 .map(|p| gc_get_property(&self.gc, p, &key))
1498 .unwrap_or(Value::Undefined),
1499 Value::Boolean(_) => self
1500 .boolean_prototype
1501 .map(|p| gc_get_property(&self.gc, p, &key))
1502 .unwrap_or(Value::Undefined),
1503 _ => Value::Undefined,
1504 };
1505 self.registers[base + dst as usize] = val;
1506 }
1507 Op::SetPropertyByName => {
1508 let obj_r = Self::read_u8(&mut self.frames[fi]);
1509 let name_idx = Self::read_u16(&mut self.frames[fi]) as usize;
1510 let val_r = Self::read_u8(&mut self.frames[fi]);
1511 let base = self.frames[fi].base;
1512 let key = self.frames[fi].func.names[name_idx].clone();
1513 let val = self.registers[base + val_r as usize].clone();
1514 match self.registers[base + obj_r as usize] {
1515 Value::Object(gc_ref) => {
1516 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1517 if let Some(prop) = data.properties.get_mut(&key) {
1518 if prop.writable {
1519 prop.value = val;
1520 }
1521 } else {
1522 data.properties.insert(key, Property::data(val));
1523 }
1524 }
1525 }
1526 Value::Function(gc_ref) => {
1527 if let Some(HeapObject::Function(fdata)) = self.gc.get_mut(gc_ref) {
1528 if let Some(prop) = fdata.properties.get_mut(&key) {
1529 if prop.writable {
1530 prop.value = val;
1531 }
1532 } else {
1533 fdata.properties.insert(key, Property::data(val));
1534 }
1535 }
1536 }
1537 _ => {}
1538 }
1539 }
1540
1541 // ── Misc ───────────────────────────────────────
1542 Op::Delete => {
1543 let dst = Self::read_u8(&mut self.frames[fi]);
1544 let obj_r = Self::read_u8(&mut self.frames[fi]);
1545 let key_r = Self::read_u8(&mut self.frames[fi]);
1546 let base = self.frames[fi].base;
1547 let key = self.registers[base + key_r as usize].to_js_string(&self.gc);
1548 let result =
1549 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] {
1550 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1551 match data.properties.get(&key) {
1552 Some(prop) if !prop.configurable => false,
1553 Some(_) => {
1554 data.properties.remove(&key);
1555 true
1556 }
1557 None => true,
1558 }
1559 } else {
1560 true
1561 }
1562 } else {
1563 true
1564 };
1565 self.registers[base + dst as usize] = Value::Boolean(result);
1566 }
1567 Op::ForInInit => {
1568 let dst = Self::read_u8(&mut self.frames[fi]);
1569 let obj_r = Self::read_u8(&mut self.frames[fi]);
1570 let base = self.frames[fi].base;
1571 let keys = match self.registers[base + obj_r as usize] {
1572 Value::Object(gc_ref) => gc_enumerate_keys(&self.gc, gc_ref),
1573 _ => Vec::new(),
1574 };
1575 // Store keys as an array object.
1576 let mut arr = ObjectData::new();
1577 for (i, key) in keys.iter().enumerate() {
1578 arr.properties
1579 .insert(i.to_string(), Property::data(Value::String(key.clone())));
1580 }
1581 arr.properties.insert(
1582 "length".to_string(),
1583 Property {
1584 value: Value::Number(keys.len() as f64),
1585 writable: true,
1586 enumerable: false,
1587 configurable: false,
1588 },
1589 );
1590 let gc_ref = self.gc.alloc(HeapObject::Object(arr));
1591 self.registers[base + dst as usize] = Value::Object(gc_ref);
1592 }
1593 Op::ForInNext => {
1594 let dst_val = Self::read_u8(&mut self.frames[fi]);
1595 let dst_done = Self::read_u8(&mut self.frames[fi]);
1596 let keys_r = Self::read_u8(&mut self.frames[fi]);
1597 let idx_r = Self::read_u8(&mut self.frames[fi]);
1598 let base = self.frames[fi].base;
1599 let idx = self.registers[base + idx_r as usize].to_number() as usize;
1600 let len = match self.registers[base + keys_r as usize] {
1601 Value::Object(gc_ref) => {
1602 gc_get_property(&self.gc, gc_ref, "length").to_number() as usize
1603 }
1604 _ => 0,
1605 };
1606 if idx >= len {
1607 self.registers[base + dst_done as usize] = Value::Boolean(true);
1608 self.registers[base + dst_val as usize] = Value::Undefined;
1609 } else {
1610 let key_str = idx.to_string();
1611 let key = match self.registers[base + keys_r as usize] {
1612 Value::Object(gc_ref) => gc_get_property(&self.gc, gc_ref, &key_str),
1613 _ => Value::Undefined,
1614 };
1615 self.registers[base + dst_val as usize] = key;
1616 self.registers[base + dst_done as usize] = Value::Boolean(false);
1617 }
1618 }
1619 Op::SetPrototype => {
1620 let obj_r = Self::read_u8(&mut self.frames[fi]);
1621 let proto_r = Self::read_u8(&mut self.frames[fi]);
1622 let base = self.frames[fi].base;
1623 let proto = match &self.registers[base + proto_r as usize] {
1624 Value::Object(r) => Some(*r),
1625 Value::Null => None,
1626 _ => None,
1627 };
1628 if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] {
1629 if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) {
1630 data.prototype = proto;
1631 }
1632 }
1633 }
1634 Op::GetPrototype => {
1635 let dst = Self::read_u8(&mut self.frames[fi]);
1636 let obj_r = Self::read_u8(&mut self.frames[fi]);
1637 let base = self.frames[fi].base;
1638 let proto = match self.registers[base + obj_r as usize] {
1639 Value::Object(gc_ref) => match self.gc.get(gc_ref) {
1640 Some(HeapObject::Object(data)) => {
1641 data.prototype.map(Value::Object).unwrap_or(Value::Null)
1642 }
1643 _ => Value::Null,
1644 },
1645 _ => Value::Null,
1646 };
1647 self.registers[base + dst as usize] = proto;
1648 }
1649
1650 // ── Exception handling ─────────────────────────────
1651 Op::PushExceptionHandler => {
1652 let catch_reg = Self::read_u8(&mut self.frames[fi]);
1653 let offset = Self::read_i32(&mut self.frames[fi]);
1654 let catch_ip = (self.frames[fi].ip as i32 + offset) as usize;
1655 self.frames[fi].exception_handlers.push(ExceptionHandler {
1656 catch_ip,
1657 catch_reg,
1658 });
1659 }
1660 Op::PopExceptionHandler => {
1661 self.frames[fi].exception_handlers.pop();
1662 }
1663
1664 // ── Closure / upvalue ops ─────────────────────────
1665 Op::NewCell => {
1666 let dst = Self::read_u8(&mut self.frames[fi]);
1667 let base = self.frames[fi].base;
1668 let cell = self.gc.alloc(HeapObject::Cell(Value::Undefined));
1669 self.registers[base + dst as usize] = Value::Object(cell);
1670
1671 if self.gc.should_collect() {
1672 let roots = self.collect_roots();
1673 self.gc.collect(&roots);
1674 }
1675 }
1676 Op::CellLoad => {
1677 let dst = Self::read_u8(&mut self.frames[fi]);
1678 let cell_reg = Self::read_u8(&mut self.frames[fi]);
1679 let base = self.frames[fi].base;
1680 let cell_ref = match &self.registers[base + cell_reg as usize] {
1681 Value::Object(r) => *r,
1682 _ => {
1683 return Err(RuntimeError {
1684 kind: ErrorKind::Error,
1685 message: "CellLoad: register does not hold a cell".into(),
1686 });
1687 }
1688 };
1689 let val = match self.gc.get(cell_ref) {
1690 Some(HeapObject::Cell(v)) => v.clone(),
1691 _ => Value::Undefined,
1692 };
1693 self.registers[base + dst as usize] = val;
1694 }
1695 Op::CellStore => {
1696 let cell_reg = Self::read_u8(&mut self.frames[fi]);
1697 let src = Self::read_u8(&mut self.frames[fi]);
1698 let base = self.frames[fi].base;
1699 let cell_ref = match &self.registers[base + cell_reg as usize] {
1700 Value::Object(r) => *r,
1701 _ => {
1702 return Err(RuntimeError {
1703 kind: ErrorKind::Error,
1704 message: "CellStore: register does not hold a cell".into(),
1705 });
1706 }
1707 };
1708 let val = self.registers[base + src as usize].clone();
1709 if let Some(HeapObject::Cell(cell_val)) = self.gc.get_mut(cell_ref) {
1710 *cell_val = val;
1711 }
1712 }
1713 Op::LoadUpvalue => {
1714 let dst = Self::read_u8(&mut self.frames[fi]);
1715 let idx = Self::read_u8(&mut self.frames[fi]) as usize;
1716 let base = self.frames[fi].base;
1717 let cell_ref = self.frames[fi].upvalues[idx];
1718 let val = match self.gc.get(cell_ref) {
1719 Some(HeapObject::Cell(v)) => v.clone(),
1720 _ => Value::Undefined,
1721 };
1722 self.registers[base + dst as usize] = val;
1723 }
1724 Op::StoreUpvalue => {
1725 let idx = Self::read_u8(&mut self.frames[fi]) as usize;
1726 let src = Self::read_u8(&mut self.frames[fi]);
1727 let base = self.frames[fi].base;
1728 let val = self.registers[base + src as usize].clone();
1729 let cell_ref = self.frames[fi].upvalues[idx];
1730 if let Some(HeapObject::Cell(cell_val)) = self.gc.get_mut(cell_ref) {
1731 *cell_val = val;
1732 }
1733 }
1734 }
1735 }
1736 }
1737
1738 /// Read 3 register operands and return (dst, lhs, rhs, base) as usize indices.
1739 fn read_3reg(&mut self, fi: usize) -> (usize, usize, usize, usize) {
1740 let dst = Self::read_u8(&mut self.frames[fi]) as usize;
1741 let lhs = Self::read_u8(&mut self.frames[fi]) as usize;
1742 let rhs = Self::read_u8(&mut self.frames[fi]) as usize;
1743 let base = self.frames[fi].base;
1744 (dst, lhs, rhs, base)
1745 }
1746
1747 /// Try to find an exception handler on the call stack.
1748 /// Returns true if a handler was found (execution resumes there).
1749 fn handle_exception(&mut self, value: Value) -> bool {
1750 while let Some(frame) = self.frames.last_mut() {
1751 if let Some(handler) = frame.exception_handlers.pop() {
1752 let base = frame.base;
1753 frame.ip = handler.catch_ip;
1754 self.registers[base + handler.catch_reg as usize] = value;
1755 return true;
1756 }
1757 if self.frames.len() == 1 {
1758 break;
1759 }
1760 self.frames.pop();
1761 }
1762 false
1763 }
1764
1765 /// Register a native function as a global.
1766 pub fn define_native(
1767 &mut self,
1768 name: &str,
1769 callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>,
1770 ) {
1771 let gc_ref = self.gc.alloc(HeapObject::Function(Box::new(FunctionData {
1772 name: name.to_string(),
1773 kind: FunctionKind::Native(NativeFunc { callback }),
1774 prototype_obj: None,
1775 properties: HashMap::new(),
1776 upvalues: Vec::new(),
1777 })));
1778 self.globals
1779 .insert(name.to_string(), Value::Function(gc_ref));
1780 }
1781
1782 /// Get a global variable value.
1783 pub fn get_global(&self, name: &str) -> Option<&Value> {
1784 self.globals.get(name)
1785 }
1786
1787 /// Set a global variable.
1788 pub fn set_global(&mut self, name: &str, val: Value) {
1789 self.globals.insert(name.to_string(), val);
1790 }
1791}
1792
1793impl Default for Vm {
1794 fn default() -> Self {
1795 Self::new()
1796 }
1797}
1798
1799/// Internal enum to avoid holding a GC borrow across the call setup.
1800enum CallInfo {
1801 Native(fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>),
1802 Bytecode(Function, Vec<GcRef>),
1803}
1804
1805// ── Tests ────────────────────────────────────────────────────
1806
1807#[cfg(test)]
1808mod tests {
1809 use super::*;
1810 use crate::bytecode::{BytecodeBuilder, Constant, Op};
1811 use crate::compiler;
1812 use crate::parser::Parser;
1813
1814 /// Helper: compile and execute JS source, return the completion value.
1815 fn eval(source: &str) -> Result<Value, RuntimeError> {
1816 let program = Parser::parse(source).expect("parse failed");
1817 let func = compiler::compile(&program).expect("compile failed");
1818 let mut vm = Vm::new();
1819 vm.execute(&func)
1820 }
1821
1822 // ── Value tests ─────────────────────────────────────────
1823
1824 #[test]
1825 fn test_to_boolean() {
1826 let mut gc: Gc<HeapObject> = Gc::new();
1827 assert!(!Value::Undefined.to_boolean());
1828 assert!(!Value::Null.to_boolean());
1829 assert!(!Value::Boolean(false).to_boolean());
1830 assert!(Value::Boolean(true).to_boolean());
1831 assert!(!Value::Number(0.0).to_boolean());
1832 assert!(!Value::Number(f64::NAN).to_boolean());
1833 assert!(Value::Number(1.0).to_boolean());
1834 assert!(!Value::String(String::new()).to_boolean());
1835 assert!(Value::String("hello".to_string()).to_boolean());
1836 let obj_ref = gc.alloc(HeapObject::Object(ObjectData::new()));
1837 assert!(Value::Object(obj_ref).to_boolean());
1838 }
1839
1840 #[test]
1841 fn test_to_number() {
1842 assert!(Value::Undefined.to_number().is_nan());
1843 assert_eq!(Value::Null.to_number(), 0.0);
1844 assert_eq!(Value::Boolean(true).to_number(), 1.0);
1845 assert_eq!(Value::Boolean(false).to_number(), 0.0);
1846 assert_eq!(Value::Number(42.0).to_number(), 42.0);
1847 assert_eq!(Value::String("42".to_string()).to_number(), 42.0);
1848 assert_eq!(Value::String("".to_string()).to_number(), 0.0);
1849 assert!(Value::String("abc".to_string()).to_number().is_nan());
1850 }
1851
1852 #[test]
1853 fn test_type_of() {
1854 let mut gc: Gc<HeapObject> = Gc::new();
1855 assert_eq!(Value::Undefined.type_of(), "undefined");
1856 assert_eq!(Value::Null.type_of(), "object");
1857 assert_eq!(Value::Boolean(true).type_of(), "boolean");
1858 assert_eq!(Value::Number(1.0).type_of(), "number");
1859 assert_eq!(Value::String("hi".to_string()).type_of(), "string");
1860 let obj_ref = gc.alloc(HeapObject::Object(ObjectData::new()));
1861 assert_eq!(Value::Object(obj_ref).type_of(), "object");
1862 }
1863
1864 // ── VM bytecode-level tests ─────────────────────────────
1865
1866 #[test]
1867 fn test_load_const_number() {
1868 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1869 b.func.register_count = 1;
1870 let ci = b.add_constant(Constant::Number(42.0));
1871 b.emit_reg_u16(Op::LoadConst, 0, ci);
1872 b.emit_reg(Op::Return, 0);
1873 let func = b.finish();
1874
1875 let mut vm = Vm::new();
1876 let result = vm.execute(&func).unwrap();
1877 match result {
1878 Value::Number(n) => assert_eq!(n, 42.0),
1879 _ => panic!("expected Number, got {result:?}"),
1880 }
1881 }
1882
1883 #[test]
1884 fn test_arithmetic_ops() {
1885 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1886 b.func.register_count = 3;
1887 let c10 = b.add_constant(Constant::Number(10.0));
1888 let c3 = b.add_constant(Constant::Number(3.0));
1889 b.emit_reg_u16(Op::LoadConst, 0, c10);
1890 b.emit_reg_u16(Op::LoadConst, 1, c3);
1891 b.emit_reg3(Op::Add, 2, 0, 1);
1892 b.emit_reg(Op::Return, 2);
1893 let func = b.finish();
1894
1895 let mut vm = Vm::new();
1896 match vm.execute(&func).unwrap() {
1897 Value::Number(n) => assert_eq!(n, 13.0),
1898 v => panic!("expected 13, got {v:?}"),
1899 }
1900 }
1901
1902 #[test]
1903 fn test_string_concat() {
1904 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1905 b.func.register_count = 3;
1906 let c1 = b.add_constant(Constant::String("hello".into()));
1907 let c2 = b.add_constant(Constant::String(" world".into()));
1908 b.emit_reg_u16(Op::LoadConst, 0, c1);
1909 b.emit_reg_u16(Op::LoadConst, 1, c2);
1910 b.emit_reg3(Op::Add, 2, 0, 1);
1911 b.emit_reg(Op::Return, 2);
1912 let func = b.finish();
1913
1914 let mut vm = Vm::new();
1915 match vm.execute(&func).unwrap() {
1916 Value::String(s) => assert_eq!(s, "hello world"),
1917 v => panic!("expected string, got {v:?}"),
1918 }
1919 }
1920
1921 #[test]
1922 fn test_jump_if_false() {
1923 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1924 b.func.register_count = 2;
1925 b.emit_reg(Op::LoadFalse, 0);
1926 let patch = b.emit_cond_jump(Op::JumpIfFalse, 0);
1927 b.emit_load_int8(1, 1);
1928 let skip = b.emit_jump(Op::Jump);
1929 b.patch_jump(patch);
1930 b.emit_load_int8(1, 2);
1931 b.patch_jump(skip);
1932 b.emit_reg(Op::Return, 1);
1933 let func = b.finish();
1934
1935 let mut vm = Vm::new();
1936 match vm.execute(&func).unwrap() {
1937 Value::Number(n) => assert_eq!(n, 2.0),
1938 v => panic!("expected 2, got {v:?}"),
1939 }
1940 }
1941
1942 #[test]
1943 fn test_globals() {
1944 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1945 b.func.register_count = 2;
1946 let name = b.add_name("x");
1947 b.emit_load_int8(0, 42);
1948 b.emit_store_global(name, 0);
1949 b.emit_load_global(1, name);
1950 b.emit_reg(Op::Return, 1);
1951 let func = b.finish();
1952
1953 let mut vm = Vm::new();
1954 match vm.execute(&func).unwrap() {
1955 Value::Number(n) => assert_eq!(n, 42.0),
1956 v => panic!("expected 42, got {v:?}"),
1957 }
1958 }
1959
1960 #[test]
1961 fn test_function_call() {
1962 let mut inner_b = BytecodeBuilder::new("add1".into(), 1);
1963 inner_b.func.register_count = 2;
1964 inner_b.emit_load_int8(1, 1);
1965 inner_b.emit_reg3(Op::Add, 0, 0, 1);
1966 inner_b.emit_reg(Op::Return, 0);
1967 let inner = inner_b.finish();
1968
1969 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1970 b.func.register_count = 4;
1971 let fi = b.add_function(inner);
1972 b.emit_reg_u16(Op::CreateClosure, 0, fi);
1973 b.emit_load_int8(1, 10);
1974 b.emit_call(2, 0, 1, 1);
1975 b.emit_reg(Op::Return, 2);
1976 let func = b.finish();
1977
1978 let mut vm = Vm::new();
1979 match vm.execute(&func).unwrap() {
1980 Value::Number(n) => assert_eq!(n, 11.0),
1981 v => panic!("expected 11, got {v:?}"),
1982 }
1983 }
1984
1985 #[test]
1986 fn test_native_function() {
1987 let mut b = BytecodeBuilder::new("<test>".into(), 0);
1988 b.func.register_count = 3;
1989 let name = b.add_name("double");
1990 b.emit_load_global(0, name);
1991 b.emit_load_int8(1, 21);
1992 b.emit_call(2, 0, 1, 1);
1993 b.emit_reg(Op::Return, 2);
1994 let func = b.finish();
1995
1996 let mut vm = Vm::new();
1997 vm.define_native("double", |args, _ctx| {
1998 let n = args.first().unwrap_or(&Value::Undefined).to_number();
1999 Ok(Value::Number(n * 2.0))
2000 });
2001 match vm.execute(&func).unwrap() {
2002 Value::Number(n) => assert_eq!(n, 42.0),
2003 v => panic!("expected 42, got {v:?}"),
2004 }
2005 }
2006
2007 #[test]
2008 fn test_object_property() {
2009 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2010 b.func.register_count = 3;
2011 let name = b.add_name("x");
2012 b.emit_reg(Op::CreateObject, 0);
2013 b.emit_load_int8(1, 42);
2014 b.emit_set_prop_name(0, name, 1);
2015 b.emit_get_prop_name(2, 0, name);
2016 b.emit_reg(Op::Return, 2);
2017 let func = b.finish();
2018
2019 let mut vm = Vm::new();
2020 match vm.execute(&func).unwrap() {
2021 Value::Number(n) => assert_eq!(n, 42.0),
2022 v => panic!("expected 42, got {v:?}"),
2023 }
2024 }
2025
2026 #[test]
2027 fn test_typeof_operator() {
2028 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2029 b.func.register_count = 2;
2030 b.emit_load_int8(0, 5);
2031 b.emit_reg_reg(Op::TypeOf, 1, 0);
2032 b.emit_reg(Op::Return, 1);
2033 let func = b.finish();
2034
2035 let mut vm = Vm::new();
2036 match vm.execute(&func).unwrap() {
2037 Value::String(s) => assert_eq!(s, "number"),
2038 v => panic!("expected 'number', got {v:?}"),
2039 }
2040 }
2041
2042 #[test]
2043 fn test_comparison() {
2044 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2045 b.func.register_count = 3;
2046 b.emit_load_int8(0, 5);
2047 b.emit_load_int8(1, 10);
2048 b.emit_reg3(Op::LessThan, 2, 0, 1);
2049 b.emit_reg(Op::Return, 2);
2050 let func = b.finish();
2051
2052 let mut vm = Vm::new();
2053 match vm.execute(&func).unwrap() {
2054 Value::Boolean(b) => assert!(b),
2055 v => panic!("expected true, got {v:?}"),
2056 }
2057 }
2058
2059 #[test]
2060 fn test_abstract_equality() {
2061 assert!(abstract_eq(&Value::Null, &Value::Undefined));
2062 assert!(abstract_eq(&Value::Undefined, &Value::Null));
2063 assert!(abstract_eq(
2064 &Value::Number(1.0),
2065 &Value::String("1".to_string())
2066 ));
2067 assert!(abstract_eq(&Value::Boolean(true), &Value::Number(1.0)));
2068 assert!(!abstract_eq(&Value::Number(0.0), &Value::Null));
2069 }
2070
2071 #[test]
2072 fn test_strict_equality() {
2073 assert!(strict_eq(&Value::Number(1.0), &Value::Number(1.0)));
2074 assert!(!strict_eq(
2075 &Value::Number(1.0),
2076 &Value::String("1".to_string())
2077 ));
2078 assert!(strict_eq(&Value::Null, &Value::Null));
2079 assert!(!strict_eq(&Value::Null, &Value::Undefined));
2080 }
2081
2082 #[test]
2083 fn test_bitwise_ops() {
2084 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2085 b.func.register_count = 3;
2086 b.emit_load_int8(0, 0x0F);
2087 b.emit_load_int8(1, 0x03);
2088 b.emit_reg3(Op::BitAnd, 2, 0, 1);
2089 b.emit_reg(Op::Return, 2);
2090 let func = b.finish();
2091
2092 let mut vm = Vm::new();
2093 match vm.execute(&func).unwrap() {
2094 Value::Number(n) => assert_eq!(n, 3.0),
2095 v => panic!("expected 3, got {v:?}"),
2096 }
2097 }
2098
2099 #[test]
2100 fn test_throw_uncaught() {
2101 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2102 b.func.register_count = 1;
2103 let ci = b.add_constant(Constant::String("oops".into()));
2104 b.emit_reg_u16(Op::LoadConst, 0, ci);
2105 b.emit_reg(Op::Throw, 0);
2106 let func = b.finish();
2107
2108 let mut vm = Vm::new();
2109 let err = vm.execute(&func).unwrap_err();
2110 assert_eq!(err.message, "oops");
2111 }
2112
2113 #[test]
2114 fn test_loop_counting() {
2115 let mut b = BytecodeBuilder::new("<test>".into(), 0);
2116 b.func.register_count = 3;
2117 b.emit_load_int8(0, 0);
2118 b.emit_load_int8(1, 5);
2119 let loop_start = b.offset();
2120 b.emit_reg3(Op::LessThan, 2, 0, 1);
2121 let exit_patch = b.emit_cond_jump(Op::JumpIfFalse, 2);
2122 b.emit_load_int8(2, 1);
2123 b.emit_reg3(Op::Add, 0, 0, 2);
2124 b.emit_jump_to(loop_start);
2125 b.patch_jump(exit_patch);
2126 b.emit_reg(Op::Return, 0);
2127 let func = b.finish();
2128
2129 let mut vm = Vm::new();
2130 match vm.execute(&func).unwrap() {
2131 Value::Number(n) => assert_eq!(n, 5.0),
2132 v => panic!("expected 5, got {v:?}"),
2133 }
2134 }
2135
2136 // ── End-to-end (compile + execute) tests ────────────────
2137
2138 #[test]
2139 fn test_e2e_arithmetic() {
2140 match eval("2 + 3 * 4").unwrap() {
2141 Value::Number(n) => assert_eq!(n, 14.0),
2142 v => panic!("expected 14, got {v:?}"),
2143 }
2144 }
2145
2146 #[test]
2147 fn test_e2e_variables() {
2148 match eval("var x = 10; var y = 20; x + y").unwrap() {
2149 Value::Number(n) => assert_eq!(n, 30.0),
2150 v => panic!("expected 30, got {v:?}"),
2151 }
2152 }
2153
2154 #[test]
2155 fn test_e2e_if_else() {
2156 match eval("var x = 5; if (x > 3) { x = 100; } x").unwrap() {
2157 Value::Number(n) => assert_eq!(n, 100.0),
2158 v => panic!("expected 100, got {v:?}"),
2159 }
2160 }
2161
2162 #[test]
2163 fn test_e2e_while_loop() {
2164 match eval("var i = 0; var sum = 0; while (i < 10) { sum = sum + i; i = i + 1; } sum")
2165 .unwrap()
2166 {
2167 Value::Number(n) => assert_eq!(n, 45.0),
2168 v => panic!("expected 45, got {v:?}"),
2169 }
2170 }
2171
2172 #[test]
2173 fn test_e2e_function_call() {
2174 match eval("function add(a, b) { return a + b; } add(3, 4)").unwrap() {
2175 Value::Number(n) => assert_eq!(n, 7.0),
2176 v => panic!("expected 7, got {v:?}"),
2177 }
2178 }
2179
2180 #[test]
2181 fn test_e2e_recursive_factorial() {
2182 let src = r#"
2183 function fact(n) {
2184 if (n <= 1) return 1;
2185 return n * fact(n - 1);
2186 }
2187 fact(6)
2188 "#;
2189 match eval(src).unwrap() {
2190 Value::Number(n) => assert_eq!(n, 720.0),
2191 v => panic!("expected 720, got {v:?}"),
2192 }
2193 }
2194
2195 #[test]
2196 fn test_e2e_string_concat() {
2197 match eval("'hello' + ' ' + 'world'").unwrap() {
2198 Value::String(s) => assert_eq!(s, "hello world"),
2199 v => panic!("expected 'hello world', got {v:?}"),
2200 }
2201 }
2202
2203 #[test]
2204 fn test_e2e_comparison_coercion() {
2205 match eval("1 == '1'").unwrap() {
2206 Value::Boolean(b) => assert!(b),
2207 v => panic!("expected true, got {v:?}"),
2208 }
2209 match eval("1 === '1'").unwrap() {
2210 Value::Boolean(b) => assert!(!b),
2211 v => panic!("expected false, got {v:?}"),
2212 }
2213 }
2214
2215 #[test]
2216 fn test_e2e_typeof() {
2217 match eval("typeof 42").unwrap() {
2218 Value::String(s) => assert_eq!(s, "number"),
2219 v => panic!("expected 'number', got {v:?}"),
2220 }
2221 match eval("typeof 'hello'").unwrap() {
2222 Value::String(s) => assert_eq!(s, "string"),
2223 v => panic!("expected 'string', got {v:?}"),
2224 }
2225 }
2226
2227 #[test]
2228 fn test_e2e_object_literal() {
2229 match eval("var o = { x: 10, y: 20 }; o.x + o.y").unwrap() {
2230 Value::Number(n) => assert_eq!(n, 30.0),
2231 v => panic!("expected 30, got {v:?}"),
2232 }
2233 }
2234
2235 #[test]
2236 fn test_e2e_for_loop() {
2237 match eval("var s = 0; for (var i = 0; i < 5; i = i + 1) { s = s + i; } s").unwrap() {
2238 Value::Number(n) => assert_eq!(n, 10.0),
2239 v => panic!("expected 10, got {v:?}"),
2240 }
2241 }
2242
2243 #[test]
2244 fn test_e2e_nested_functions() {
2245 // Note: closures (capturing parent scope vars) not yet supported.
2246 // This test verifies nested function declarations and calls work.
2247 let src = r#"
2248 function outer(x) {
2249 function inner(y) {
2250 return y + 1;
2251 }
2252 return inner(x);
2253 }
2254 outer(5)
2255 "#;
2256 match eval(src).unwrap() {
2257 Value::Number(n) => assert_eq!(n, 6.0),
2258 v => panic!("expected 6, got {v:?}"),
2259 }
2260 }
2261
2262 #[test]
2263 fn test_e2e_logical_operators() {
2264 match eval("true && false").unwrap() {
2265 Value::Boolean(b) => assert!(!b),
2266 v => panic!("expected false, got {v:?}"),
2267 }
2268 match eval("false || true").unwrap() {
2269 Value::Boolean(b) => assert!(b),
2270 v => panic!("expected true, got {v:?}"),
2271 }
2272 }
2273
2274 #[test]
2275 fn test_e2e_unary_neg() {
2276 match eval("-(5 + 3)").unwrap() {
2277 Value::Number(n) => assert_eq!(n, -8.0),
2278 v => panic!("expected -8, got {v:?}"),
2279 }
2280 }
2281
2282 #[test]
2283 fn test_e2e_ternary() {
2284 match eval("true ? 1 : 2").unwrap() {
2285 Value::Number(n) => assert_eq!(n, 1.0),
2286 v => panic!("expected 1, got {v:?}"),
2287 }
2288 match eval("false ? 1 : 2").unwrap() {
2289 Value::Number(n) => assert_eq!(n, 2.0),
2290 v => panic!("expected 2, got {v:?}"),
2291 }
2292 }
2293
2294 #[test]
2295 fn test_e2e_fibonacci() {
2296 let src = r#"
2297 function fib(n) {
2298 if (n <= 1) return n;
2299 return fib(n - 1) + fib(n - 2);
2300 }
2301 fib(10)
2302 "#;
2303 match eval(src).unwrap() {
2304 Value::Number(n) => assert_eq!(n, 55.0),
2305 v => panic!("expected 55, got {v:?}"),
2306 }
2307 }
2308
2309 // ── GC integration tests ────────────────────────────────
2310
2311 #[test]
2312 fn test_gc_object_survives_collection() {
2313 let src = r#"
2314 var o = { x: 42 };
2315 o.x
2316 "#;
2317 match eval(src).unwrap() {
2318 Value::Number(n) => assert_eq!(n, 42.0),
2319 v => panic!("expected 42, got {v:?}"),
2320 }
2321 }
2322
2323 #[test]
2324 fn test_gc_many_objects() {
2325 // Allocate many objects to trigger GC threshold.
2326 let src = r#"
2327 var sum = 0;
2328 var i = 0;
2329 while (i < 100) {
2330 var o = { val: i };
2331 sum = sum + o.val;
2332 i = i + 1;
2333 }
2334 sum
2335 "#;
2336 match eval(src).unwrap() {
2337 Value::Number(n) => assert_eq!(n, 4950.0),
2338 v => panic!("expected 4950, got {v:?}"),
2339 }
2340 }
2341
2342 #[test]
2343 fn test_gc_reference_identity() {
2344 // With GC, object assignment is by reference.
2345 let mut gc: Gc<HeapObject> = Gc::new();
2346 let r = gc.alloc(HeapObject::Object(ObjectData::new()));
2347 let a = Value::Object(r);
2348 let b = a.clone();
2349 assert!(strict_eq(&a, &b)); // Same GcRef → strict equal.
2350 }
2351
2352 // ── Object model tests ──────────────────────────────────
2353
2354 #[test]
2355 fn test_prototype_chain_lookup() {
2356 // Property lookup walks the prototype chain.
2357 let src = r#"
2358 function Animal() {}
2359 var a = {};
2360 a.sound = "woof";
2361 a.sound
2362 "#;
2363 match eval(src).unwrap() {
2364 Value::String(s) => assert_eq!(s, "woof"),
2365 v => panic!("expected 'woof', got {v:?}"),
2366 }
2367 }
2368
2369 #[test]
2370 fn test_typeof_all_types() {
2371 // typeof returns correct strings for all types.
2372 let cases = [
2373 ("typeof undefined", "undefined"),
2374 ("typeof null", "object"),
2375 ("typeof true", "boolean"),
2376 ("typeof 42", "number"),
2377 ("typeof 'hello'", "string"),
2378 ("typeof {}", "object"),
2379 ("typeof function(){}", "function"),
2380 ];
2381 for (src, expected) in cases {
2382 match eval(src).unwrap() {
2383 Value::String(s) => assert_eq!(s, expected, "typeof failed for: {src}"),
2384 v => panic!("expected string for {src}, got {v:?}"),
2385 }
2386 }
2387 }
2388
2389 #[test]
2390 fn test_instanceof_basic() {
2391 let src = r#"
2392 function Foo() {}
2393 var f = {};
2394 f instanceof Foo
2395 "#;
2396 // Plain object without prototype link → false
2397 match eval(src).unwrap() {
2398 Value::Boolean(b) => assert!(!b),
2399 v => panic!("expected false, got {v:?}"),
2400 }
2401 }
2402
2403 #[test]
2404 fn test_in_operator() {
2405 let src = r#"
2406 var o = { x: 1, y: 2 };
2407 var r1 = "x" in o;
2408 var r2 = "z" in o;
2409 r1 === true && r2 === false
2410 "#;
2411 match eval(src).unwrap() {
2412 Value::Boolean(b) => assert!(b),
2413 v => panic!("expected true, got {v:?}"),
2414 }
2415 }
2416
2417 #[test]
2418 fn test_delete_property() {
2419 let src = r#"
2420 var o = { x: 1, y: 2 };
2421 delete o.x;
2422 typeof o.x === "undefined" && o.y === 2
2423 "#;
2424 match eval(src).unwrap() {
2425 Value::Boolean(b) => assert!(b),
2426 v => panic!("expected true, got {v:?}"),
2427 }
2428 }
2429
2430 #[test]
2431 fn test_delete_computed_property() {
2432 let src = r#"
2433 var o = { a: 10, b: 20 };
2434 var key = "a";
2435 delete o[key];
2436 typeof o.a === "undefined" && o.b === 20
2437 "#;
2438 match eval(src).unwrap() {
2439 Value::Boolean(b) => assert!(b),
2440 v => panic!("expected true, got {v:?}"),
2441 }
2442 }
2443
2444 #[test]
2445 fn test_delete_non_configurable() {
2446 // Array length is non-configurable, delete should return false.
2447 let mut gc: Gc<HeapObject> = Gc::new();
2448 let mut obj = ObjectData::new();
2449 obj.properties.insert(
2450 "x".to_string(),
2451 Property {
2452 value: Value::Number(1.0),
2453 writable: true,
2454 enumerable: true,
2455 configurable: false,
2456 },
2457 );
2458 let obj_ref = gc.alloc(HeapObject::Object(obj));
2459
2460 // Try to delete the non-configurable property.
2461 match gc.get_mut(obj_ref) {
2462 Some(HeapObject::Object(data)) => {
2463 let prop = data.properties.get("x").unwrap();
2464 assert!(!prop.configurable);
2465 // The property should still be there.
2466 assert!(data.properties.contains_key("x"));
2467 }
2468 _ => panic!("expected object"),
2469 }
2470 }
2471
2472 #[test]
2473 fn test_property_writable_flag() {
2474 // Setting a non-writable property should silently fail.
2475 let mut gc: Gc<HeapObject> = Gc::new();
2476 let mut obj = ObjectData::new();
2477 obj.properties.insert(
2478 "frozen".to_string(),
2479 Property {
2480 value: Value::Number(42.0),
2481 writable: false,
2482 enumerable: true,
2483 configurable: false,
2484 },
2485 );
2486 let obj_ref = gc.alloc(HeapObject::Object(obj));
2487
2488 // Verify the property value.
2489 match gc.get(obj_ref) {
2490 Some(HeapObject::Object(data)) => {
2491 assert_eq!(
2492 data.properties.get("frozen").unwrap().value.to_number(),
2493 42.0
2494 );
2495 }
2496 _ => panic!("expected object"),
2497 }
2498 }
2499
2500 #[test]
2501 fn test_for_in_basic() {
2502 let src = r#"
2503 var o = { a: 1, b: 2, c: 3 };
2504 var sum = 0;
2505 for (var key in o) {
2506 sum = sum + o[key];
2507 }
2508 sum
2509 "#;
2510 match eval(src).unwrap() {
2511 Value::Number(n) => assert_eq!(n, 6.0),
2512 v => panic!("expected 6, got {v:?}"),
2513 }
2514 }
2515
2516 #[test]
2517 fn test_for_in_collects_keys() {
2518 let src = r#"
2519 var o = { x: 10, y: 20 };
2520 var keys = "";
2521 for (var k in o) {
2522 keys = keys + k + ",";
2523 }
2524 keys
2525 "#;
2526 match eval(src).unwrap() {
2527 Value::String(s) => {
2528 // Both keys should appear (order may vary with HashMap).
2529 assert!(s.contains("x,"));
2530 assert!(s.contains("y,"));
2531 }
2532 v => panic!("expected string, got {v:?}"),
2533 }
2534 }
2535
2536 #[test]
2537 fn test_for_in_empty_object() {
2538 let src = r#"
2539 var o = {};
2540 var count = 0;
2541 for (var k in o) {
2542 count = count + 1;
2543 }
2544 count
2545 "#;
2546 match eval(src).unwrap() {
2547 Value::Number(n) => assert_eq!(n, 0.0),
2548 v => panic!("expected 0, got {v:?}"),
2549 }
2550 }
2551
2552 #[test]
2553 fn test_property_enumerable_flag() {
2554 // Array "length" is non-enumerable, should not appear in for-in.
2555 let src = r#"
2556 var arr = [10, 20, 30];
2557 var keys = "";
2558 for (var k in arr) {
2559 keys = keys + k + ",";
2560 }
2561 keys
2562 "#;
2563 match eval(src).unwrap() {
2564 Value::String(s) => {
2565 // "length" should NOT be in the keys (it's non-enumerable).
2566 assert!(!s.contains("length"));
2567 // Array indices should be present.
2568 assert!(s.contains("0,"));
2569 assert!(s.contains("1,"));
2570 assert!(s.contains("2,"));
2571 }
2572 v => panic!("expected string, got {v:?}"),
2573 }
2574 }
2575
2576 #[test]
2577 fn test_object_reference_semantics() {
2578 // Objects have reference semantics (shared via GcRef).
2579 let src = r#"
2580 var a = { x: 1 };
2581 var b = a;
2582 b.x = 42;
2583 a.x
2584 "#;
2585 match eval(src).unwrap() {
2586 Value::Number(n) => assert_eq!(n, 42.0),
2587 v => panic!("expected 42, got {v:?}"),
2588 }
2589 }
2590
2591 #[test]
2592 fn test_gc_enumerate_keys_order() {
2593 // Integer keys should come first, sorted numerically.
2594 let mut gc: Gc<HeapObject> = Gc::new();
2595 let mut obj = ObjectData::new();
2596 obj.properties
2597 .insert("b".to_string(), Property::data(Value::Number(2.0)));
2598 obj.properties
2599 .insert("2".to_string(), Property::data(Value::Number(3.0)));
2600 obj.properties
2601 .insert("a".to_string(), Property::data(Value::Number(1.0)));
2602 obj.properties
2603 .insert("0".to_string(), Property::data(Value::Number(0.0)));
2604 let obj_ref = gc.alloc(HeapObject::Object(obj));
2605
2606 let keys = gc_enumerate_keys(&gc, obj_ref);
2607 // Integer keys first (sorted), then string keys.
2608 let int_part: Vec<&str> = keys.iter().take(2).map(|s| s.as_str()).collect();
2609 assert_eq!(int_part, vec!["0", "2"]);
2610 // Remaining keys are string keys (order depends on HashMap iteration).
2611 let str_part: Vec<&str> = keys.iter().skip(2).map(|s| s.as_str()).collect();
2612 assert!(str_part.contains(&"a"));
2613 assert!(str_part.contains(&"b"));
2614 }
2615
2616 #[test]
2617 fn test_instanceof_with_gc() {
2618 // Direct test of gc_instanceof.
2619 let mut gc: Gc<HeapObject> = Gc::new();
2620
2621 // Create a constructor function with a .prototype object.
2622 let proto = gc.alloc(HeapObject::Object(ObjectData::new()));
2623 let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData {
2624 name: "Foo".to_string(),
2625 kind: FunctionKind::Native(NativeFunc {
2626 callback: |_, _ctx| Ok(Value::Undefined),
2627 }),
2628 prototype_obj: Some(proto),
2629 properties: HashMap::new(),
2630 upvalues: Vec::new(),
2631 })));
2632
2633 // Create an object whose [[Prototype]] is the constructor's .prototype.
2634 let mut obj_data = ObjectData::new();
2635 obj_data.prototype = Some(proto);
2636 let obj = gc.alloc(HeapObject::Object(obj_data));
2637
2638 assert!(gc_instanceof(&gc, obj, ctor));
2639
2640 // An unrelated object should not match.
2641 let other = gc.alloc(HeapObject::Object(ObjectData::new()));
2642 assert!(!gc_instanceof(&gc, other, ctor));
2643 }
2644
2645 #[test]
2646 fn test_try_catch_basic() {
2647 // Simple try/catch should catch a thrown value.
2648 let src = r#"
2649 var caught = false;
2650 try { throw "err"; } catch (e) { caught = true; }
2651 caught
2652 "#;
2653 match eval(src).unwrap() {
2654 Value::Boolean(true) => {}
2655 v => panic!("expected true, got {v:?}"),
2656 }
2657 }
2658
2659 #[test]
2660 fn test_try_catch_nested_call() {
2661 // try/catch should catch errors thrown from called functions.
2662 let src = r#"
2663 function thrower() { throw "err"; }
2664 var caught = false;
2665 try { thrower(); } catch (e) { caught = true; }
2666 caught
2667 "#;
2668 match eval(src).unwrap() {
2669 Value::Boolean(true) => {}
2670 v => panic!("expected true, got {v:?}"),
2671 }
2672 }
2673
2674 // ── Closure tests ────────────────────────────────────────
2675
2676 #[test]
2677 fn test_closure_basic() {
2678 // Basic closure: inner function reads outer variable.
2679 let src = r#"
2680 function outer() {
2681 var x = 10;
2682 function inner() {
2683 return x;
2684 }
2685 return inner();
2686 }
2687 outer()
2688 "#;
2689 match eval(src).unwrap() {
2690 Value::Number(n) => assert_eq!(n, 10.0),
2691 v => panic!("expected 10, got {v:?}"),
2692 }
2693 }
2694
2695 #[test]
2696 fn test_closure_return_function() {
2697 // Closure survives the outer function's return.
2698 let src = r#"
2699 function makeAdder(x) {
2700 return function(y) { return x + y; };
2701 }
2702 var add5 = makeAdder(5);
2703 add5(3)
2704 "#;
2705 match eval(src).unwrap() {
2706 Value::Number(n) => assert_eq!(n, 8.0),
2707 v => panic!("expected 8, got {v:?}"),
2708 }
2709 }
2710
2711 #[test]
2712 fn test_closure_mutation() {
2713 // Closures share live references — mutation is visible.
2714 let src = r#"
2715 function counter() {
2716 var n = 0;
2717 return function() { n = n + 1; return n; };
2718 }
2719 var c = counter();
2720 c();
2721 c();
2722 c()
2723 "#;
2724 match eval(src).unwrap() {
2725 Value::Number(n) => assert_eq!(n, 3.0),
2726 v => panic!("expected 3, got {v:?}"),
2727 }
2728 }
2729
2730 #[test]
2731 fn test_closure_shared_variable() {
2732 // Two closures from the same scope share the same variable.
2733 let src = r#"
2734 function make() {
2735 var x = 0;
2736 function inc() { x = x + 1; }
2737 function get() { return x; }
2738 inc();
2739 inc();
2740 return get();
2741 }
2742 make()
2743 "#;
2744 match eval(src).unwrap() {
2745 Value::Number(n) => assert_eq!(n, 2.0),
2746 v => panic!("expected 2, got {v:?}"),
2747 }
2748 }
2749
2750 #[test]
2751 fn test_closure_arrow() {
2752 // Arrow function captures outer variable.
2753 let src = r#"
2754 function outer() {
2755 var x = 42;
2756 var f = () => x;
2757 return f();
2758 }
2759 outer()
2760 "#;
2761 match eval(src).unwrap() {
2762 Value::Number(n) => assert_eq!(n, 42.0),
2763 v => panic!("expected 42, got {v:?}"),
2764 }
2765 }
2766
2767 #[test]
2768 fn test_closure_nested() {
2769 // Transitive capture: grandchild function reads grandparent variable.
2770 let src = r#"
2771 function outer() {
2772 var x = 100;
2773 function middle() {
2774 function inner() {
2775 return x;
2776 }
2777 return inner();
2778 }
2779 return middle();
2780 }
2781 outer()
2782 "#;
2783 match eval(src).unwrap() {
2784 Value::Number(n) => assert_eq!(n, 100.0),
2785 v => panic!("expected 100, got {v:?}"),
2786 }
2787 }
2788
2789 #[test]
2790 fn test_closure_param_capture() {
2791 // Closure captures a function parameter.
2792 let src = r#"
2793 function multiply(factor) {
2794 return function(x) { return x * factor; };
2795 }
2796 var double = multiply(2);
2797 double(7)
2798 "#;
2799 match eval(src).unwrap() {
2800 Value::Number(n) => assert_eq!(n, 14.0),
2801 v => panic!("expected 14, got {v:?}"),
2802 }
2803 }
2804
2805 // ── const tests ──────────────────────────────────────────
2806
2807 #[test]
2808 fn test_const_basic() {
2809 let src = "const x = 42; x";
2810 match eval(src).unwrap() {
2811 Value::Number(n) => assert_eq!(n, 42.0),
2812 v => panic!("expected 42, got {v:?}"),
2813 }
2814 }
2815
2816 #[test]
2817 fn test_const_reassignment_error() {
2818 let src = "const x = 1; x = 2;";
2819 let program = crate::parser::Parser::parse(src).expect("parse ok");
2820 let result = crate::compiler::compile(&program);
2821 assert!(
2822 result.is_err(),
2823 "const reassignment should be a compile error"
2824 );
2825 }
2826
2827 #[test]
2828 fn test_const_missing_init_error() {
2829 let src = "const x;";
2830 let program = crate::parser::Parser::parse(src).expect("parse ok");
2831 let result = crate::compiler::compile(&program);
2832 assert!(
2833 result.is_err(),
2834 "const without initializer should be a compile error"
2835 );
2836 }
2837
2838 // ── this binding tests ───────────────────────────────────
2839
2840 #[test]
2841 fn test_method_call_this() {
2842 let src = r#"
2843 var obj = {};
2844 obj.x = 10;
2845 obj.getX = function() { return this.x; };
2846 obj.getX()
2847 "#;
2848 match eval(src).unwrap() {
2849 Value::Number(n) => assert_eq!(n, 10.0),
2850 v => panic!("expected 10, got {v:?}"),
2851 }
2852 }
2853
2854 // ── Object built-in tests ────────────────────────────────
2855
2856 #[test]
2857 fn test_object_keys() {
2858 let src = r#"
2859 var obj = {};
2860 obj.a = 1;
2861 obj.b = 2;
2862 obj.c = 3;
2863 var k = Object.keys(obj);
2864 k.length
2865 "#;
2866 match eval(src).unwrap() {
2867 Value::Number(n) => assert_eq!(n, 3.0),
2868 v => panic!("expected 3, got {v:?}"),
2869 }
2870 }
2871
2872 #[test]
2873 fn test_object_values() {
2874 let src = r#"
2875 var obj = {};
2876 obj.x = 10;
2877 var v = Object.values(obj);
2878 v[0]
2879 "#;
2880 match eval(src).unwrap() {
2881 Value::Number(n) => assert_eq!(n, 10.0),
2882 v => panic!("expected 10, got {v:?}"),
2883 }
2884 }
2885
2886 #[test]
2887 fn test_object_entries() {
2888 let src = r#"
2889 var obj = {};
2890 obj.x = 42;
2891 var e = Object.entries(obj);
2892 e[0][1]
2893 "#;
2894 match eval(src).unwrap() {
2895 Value::Number(n) => assert_eq!(n, 42.0),
2896 v => panic!("expected 42, got {v:?}"),
2897 }
2898 }
2899
2900 #[test]
2901 fn test_object_assign() {
2902 let src = r#"
2903 var a = {};
2904 a.x = 1;
2905 var b = {};
2906 b.y = 2;
2907 var c = Object.assign(a, b);
2908 c.y
2909 "#;
2910 match eval(src).unwrap() {
2911 Value::Number(n) => assert_eq!(n, 2.0),
2912 v => panic!("expected 2, got {v:?}"),
2913 }
2914 }
2915
2916 #[test]
2917 fn test_object_create() {
2918 let src = r#"
2919 var proto = {};
2920 proto.greet = "hello";
2921 var child = Object.create(proto);
2922 child.greet
2923 "#;
2924 match eval(src).unwrap() {
2925 Value::String(s) => assert_eq!(s, "hello"),
2926 v => panic!("expected 'hello', got {v:?}"),
2927 }
2928 }
2929
2930 #[test]
2931 fn test_object_is() {
2932 let src = "Object.is(NaN, NaN)";
2933 match eval(src).unwrap() {
2934 Value::Boolean(b) => assert!(b),
2935 v => panic!("expected true, got {v:?}"),
2936 }
2937 }
2938
2939 #[test]
2940 fn test_object_freeze() {
2941 let src = r#"
2942 var obj = {};
2943 obj.x = 1;
2944 Object.freeze(obj);
2945 Object.isFrozen(obj)
2946 "#;
2947 match eval(src).unwrap() {
2948 Value::Boolean(b) => assert!(b),
2949 v => panic!("expected true, got {v:?}"),
2950 }
2951 }
2952
2953 #[test]
2954 fn test_object_has_own_property() {
2955 let src = r#"
2956 var obj = {};
2957 obj.x = 1;
2958 obj.hasOwnProperty("x")
2959 "#;
2960 match eval(src).unwrap() {
2961 Value::Boolean(b) => assert!(b),
2962 v => panic!("expected true, got {v:?}"),
2963 }
2964 }
2965
2966 // ── Array built-in tests ─────────────────────────────────
2967
2968 #[test]
2969 fn test_array_push_pop() {
2970 let src = r#"
2971 var arr = [1, 2, 3];
2972 arr.push(4);
2973 arr.pop()
2974 "#;
2975 match eval(src).unwrap() {
2976 Value::Number(n) => assert_eq!(n, 4.0),
2977 v => panic!("expected 4, got {v:?}"),
2978 }
2979 }
2980
2981 #[test]
2982 fn test_array_push_length() {
2983 let src = r#"
2984 var arr = [];
2985 arr.push(10);
2986 arr.push(20);
2987 arr.length
2988 "#;
2989 match eval(src).unwrap() {
2990 Value::Number(n) => assert_eq!(n, 2.0),
2991 v => panic!("expected 2, got {v:?}"),
2992 }
2993 }
2994
2995 #[test]
2996 fn test_array_shift_unshift() {
2997 let src = r#"
2998 var arr = [1, 2, 3];
2999 arr.unshift(0);
3000 arr.shift()
3001 "#;
3002 match eval(src).unwrap() {
3003 Value::Number(n) => assert_eq!(n, 0.0),
3004 v => panic!("expected 0, got {v:?}"),
3005 }
3006 }
3007
3008 #[test]
3009 fn test_array_index_of() {
3010 let src = r#"
3011 var arr = [10, 20, 30];
3012 arr.indexOf(20)
3013 "#;
3014 match eval(src).unwrap() {
3015 Value::Number(n) => assert_eq!(n, 1.0),
3016 v => panic!("expected 1, got {v:?}"),
3017 }
3018 }
3019
3020 #[test]
3021 fn test_array_includes() {
3022 let src = r#"
3023 var arr = [1, 2, 3];
3024 arr.includes(2)
3025 "#;
3026 match eval(src).unwrap() {
3027 Value::Boolean(b) => assert!(b),
3028 v => panic!("expected true, got {v:?}"),
3029 }
3030 }
3031
3032 #[test]
3033 fn test_array_join() {
3034 let src = r#"
3035 var arr = [1, 2, 3];
3036 arr.join("-")
3037 "#;
3038 match eval(src).unwrap() {
3039 Value::String(s) => assert_eq!(s, "1-2-3"),
3040 v => panic!("expected '1-2-3', got {v:?}"),
3041 }
3042 }
3043
3044 #[test]
3045 fn test_array_slice() {
3046 let src = r#"
3047 var arr = [1, 2, 3, 4, 5];
3048 var s = arr.slice(1, 3);
3049 s.length
3050 "#;
3051 match eval(src).unwrap() {
3052 Value::Number(n) => assert_eq!(n, 2.0),
3053 v => panic!("expected 2, got {v:?}"),
3054 }
3055 }
3056
3057 #[test]
3058 fn test_array_concat() {
3059 let src = r#"
3060 var a = [1, 2];
3061 var b = [3, 4];
3062 var c = a.concat(b);
3063 c.length
3064 "#;
3065 match eval(src).unwrap() {
3066 Value::Number(n) => assert_eq!(n, 4.0),
3067 v => panic!("expected 4, got {v:?}"),
3068 }
3069 }
3070
3071 #[test]
3072 fn test_array_reverse() {
3073 let src = r#"
3074 var arr = [1, 2, 3];
3075 arr.reverse();
3076 arr[0]
3077 "#;
3078 match eval(src).unwrap() {
3079 Value::Number(n) => assert_eq!(n, 3.0),
3080 v => panic!("expected 3, got {v:?}"),
3081 }
3082 }
3083
3084 #[test]
3085 fn test_array_splice() {
3086 let src = r#"
3087 var arr = [1, 2, 3, 4, 5];
3088 var removed = arr.splice(1, 2);
3089 removed.length
3090 "#;
3091 match eval(src).unwrap() {
3092 Value::Number(n) => assert_eq!(n, 2.0),
3093 v => panic!("expected 2, got {v:?}"),
3094 }
3095 }
3096
3097 #[test]
3098 fn test_array_is_array() {
3099 let src = "Array.isArray([1, 2, 3])";
3100 match eval(src).unwrap() {
3101 Value::Boolean(b) => assert!(b),
3102 v => panic!("expected true, got {v:?}"),
3103 }
3104 }
3105
3106 #[test]
3107 fn test_array_map() {
3108 let src = r#"
3109 var arr = [1, 2, 3];
3110 var doubled = arr.map(function(x) { return x * 2; });
3111 doubled[1]
3112 "#;
3113 match eval(src).unwrap() {
3114 Value::Number(n) => assert_eq!(n, 4.0),
3115 v => panic!("expected 4, got {v:?}"),
3116 }
3117 }
3118
3119 #[test]
3120 fn test_array_filter() {
3121 let src = r#"
3122 var arr = [1, 2, 3, 4, 5];
3123 var evens = arr.filter(function(x) { return x % 2 === 0; });
3124 evens.length
3125 "#;
3126 match eval(src).unwrap() {
3127 Value::Number(n) => assert_eq!(n, 2.0),
3128 v => panic!("expected 2, got {v:?}"),
3129 }
3130 }
3131
3132 #[test]
3133 fn test_array_reduce() {
3134 let src = r#"
3135 var arr = [1, 2, 3, 4];
3136 arr.reduce(function(acc, x) { return acc + x; }, 0)
3137 "#;
3138 match eval(src).unwrap() {
3139 Value::Number(n) => assert_eq!(n, 10.0),
3140 v => panic!("expected 10, got {v:?}"),
3141 }
3142 }
3143
3144 #[test]
3145 fn test_array_foreach() {
3146 let src = r#"
3147 var arr = [1, 2, 3];
3148 var sum = 0;
3149 arr.forEach(function(x) { sum = sum + x; });
3150 sum
3151 "#;
3152 match eval(src).unwrap() {
3153 Value::Number(n) => assert_eq!(n, 6.0),
3154 v => panic!("expected 6, got {v:?}"),
3155 }
3156 }
3157
3158 #[test]
3159 fn test_array_find() {
3160 let src = r#"
3161 var arr = [1, 2, 3, 4];
3162 arr.find(function(x) { return x > 2; })
3163 "#;
3164 match eval(src).unwrap() {
3165 Value::Number(n) => assert_eq!(n, 3.0),
3166 v => panic!("expected 3, got {v:?}"),
3167 }
3168 }
3169
3170 #[test]
3171 fn test_array_find_index() {
3172 let src = r#"
3173 var arr = [1, 2, 3, 4];
3174 arr.findIndex(function(x) { return x > 2; })
3175 "#;
3176 match eval(src).unwrap() {
3177 Value::Number(n) => assert_eq!(n, 2.0),
3178 v => panic!("expected 2, got {v:?}"),
3179 }
3180 }
3181
3182 #[test]
3183 fn test_array_some_every() {
3184 let src = r#"
3185 var arr = [2, 4, 6];
3186 var all_even = arr.every(function(x) { return x % 2 === 0; });
3187 var has_big = arr.some(function(x) { return x > 10; });
3188 all_even && !has_big
3189 "#;
3190 match eval(src).unwrap() {
3191 Value::Boolean(b) => assert!(b),
3192 v => panic!("expected true, got {v:?}"),
3193 }
3194 }
3195
3196 #[test]
3197 fn test_array_sort() {
3198 let src = r#"
3199 var arr = [3, 1, 2];
3200 arr.sort();
3201 arr[0]
3202 "#;
3203 match eval(src).unwrap() {
3204 Value::Number(n) => assert_eq!(n, 1.0),
3205 v => panic!("expected 1, got {v:?}"),
3206 }
3207 }
3208
3209 #[test]
3210 fn test_array_sort_custom() {
3211 let src = r#"
3212 var arr = [3, 1, 2];
3213 arr.sort(function(a, b) { return a - b; });
3214 arr[2]
3215 "#;
3216 match eval(src).unwrap() {
3217 Value::Number(n) => assert_eq!(n, 3.0),
3218 v => panic!("expected 3, got {v:?}"),
3219 }
3220 }
3221
3222 #[test]
3223 fn test_array_from() {
3224 let src = r#"
3225 var arr = Array.from("abc");
3226 arr.length
3227 "#;
3228 match eval(src).unwrap() {
3229 Value::Number(n) => assert_eq!(n, 3.0),
3230 v => panic!("expected 3, got {v:?}"),
3231 }
3232 }
3233
3234 #[test]
3235 fn test_array_from_array() {
3236 let src = r#"
3237 var orig = [10, 20, 30];
3238 var copy = Array.from(orig);
3239 copy[2]
3240 "#;
3241 match eval(src).unwrap() {
3242 Value::Number(n) => assert_eq!(n, 30.0),
3243 v => panic!("expected 30, got {v:?}"),
3244 }
3245 }
3246
3247 #[test]
3248 fn test_array_flat() {
3249 let src = r#"
3250 var arr = [[1, 2], [3, 4]];
3251 var flat = arr.flat();
3252 flat.length
3253 "#;
3254 match eval(src).unwrap() {
3255 Value::Number(n) => assert_eq!(n, 4.0),
3256 v => panic!("expected 4, got {v:?}"),
3257 }
3258 }
3259
3260 // ── Error built-in tests ─────────────────────────────────
3261
3262 #[test]
3263 fn test_error_constructor() {
3264 let src = r#"
3265 var e = new Error("oops");
3266 e.message
3267 "#;
3268 match eval(src).unwrap() {
3269 Value::String(s) => assert_eq!(s, "oops"),
3270 v => panic!("expected 'oops', got {v:?}"),
3271 }
3272 }
3273
3274 #[test]
3275 fn test_type_error_constructor() {
3276 let src = r#"
3277 var e = new TypeError("bad type");
3278 e.message
3279 "#;
3280 match eval(src).unwrap() {
3281 Value::String(s) => assert_eq!(s, "bad type"),
3282 v => panic!("expected 'bad type', got {v:?}"),
3283 }
3284 }
3285
3286 // ── Global function tests ────────────────────────────────
3287
3288 #[test]
3289 fn test_parse_int() {
3290 let src = "parseInt('42')";
3291 match eval(src).unwrap() {
3292 Value::Number(n) => assert_eq!(n, 42.0),
3293 v => panic!("expected 42, got {v:?}"),
3294 }
3295 }
3296
3297 #[test]
3298 fn test_parse_int_hex() {
3299 let src = "parseInt('0xFF', 16)";
3300 match eval(src).unwrap() {
3301 Value::Number(n) => assert_eq!(n, 255.0),
3302 v => panic!("expected 255, got {v:?}"),
3303 }
3304 }
3305
3306 #[test]
3307 fn test_is_nan() {
3308 let src = "isNaN(NaN)";
3309 match eval(src).unwrap() {
3310 Value::Boolean(b) => assert!(b),
3311 v => panic!("expected true, got {v:?}"),
3312 }
3313 }
3314
3315 #[test]
3316 fn test_is_finite() {
3317 let src = "isFinite(42)";
3318 match eval(src).unwrap() {
3319 Value::Boolean(b) => assert!(b),
3320 v => panic!("expected true, got {v:?}"),
3321 }
3322 }
3323
3324 // ── String built-in tests ─────────────────────────────────
3325
3326 #[test]
3327 fn test_string_constructor() {
3328 match eval("String(42)").unwrap() {
3329 Value::String(s) => assert_eq!(s, "42"),
3330 v => panic!("expected '42', got {v:?}"),
3331 }
3332 match eval("String(true)").unwrap() {
3333 Value::String(s) => assert_eq!(s, "true"),
3334 v => panic!("expected 'true', got {v:?}"),
3335 }
3336 match eval("String()").unwrap() {
3337 Value::String(s) => assert_eq!(s, ""),
3338 v => panic!("expected '', got {v:?}"),
3339 }
3340 }
3341
3342 #[test]
3343 fn test_string_length() {
3344 match eval("'hello'.length").unwrap() {
3345 Value::Number(n) => assert_eq!(n, 5.0),
3346 v => panic!("expected 5, got {v:?}"),
3347 }
3348 }
3349
3350 #[test]
3351 fn test_string_char_at() {
3352 match eval("'hello'.charAt(1)").unwrap() {
3353 Value::String(s) => assert_eq!(s, "e"),
3354 v => panic!("expected 'e', got {v:?}"),
3355 }
3356 match eval("'hello'.charAt(10)").unwrap() {
3357 Value::String(s) => assert_eq!(s, ""),
3358 v => panic!("expected '', got {v:?}"),
3359 }
3360 }
3361
3362 #[test]
3363 fn test_string_char_code_at() {
3364 match eval("'A'.charCodeAt(0)").unwrap() {
3365 Value::Number(n) => assert_eq!(n, 65.0),
3366 v => panic!("expected 65, got {v:?}"),
3367 }
3368 }
3369
3370 #[test]
3371 fn test_string_proto_concat() {
3372 match eval("'hello'.concat(' ', 'world')").unwrap() {
3373 Value::String(s) => assert_eq!(s, "hello world"),
3374 v => panic!("expected 'hello world', got {v:?}"),
3375 }
3376 }
3377
3378 #[test]
3379 fn test_string_slice() {
3380 match eval("'hello world'.slice(6)").unwrap() {
3381 Value::String(s) => assert_eq!(s, "world"),
3382 v => panic!("expected 'world', got {v:?}"),
3383 }
3384 match eval("'hello'.slice(1, 3)").unwrap() {
3385 Value::String(s) => assert_eq!(s, "el"),
3386 v => panic!("expected 'el', got {v:?}"),
3387 }
3388 match eval("'hello'.slice(-3)").unwrap() {
3389 Value::String(s) => assert_eq!(s, "llo"),
3390 v => panic!("expected 'llo', got {v:?}"),
3391 }
3392 }
3393
3394 #[test]
3395 fn test_string_substring() {
3396 match eval("'hello'.substring(1, 3)").unwrap() {
3397 Value::String(s) => assert_eq!(s, "el"),
3398 v => panic!("expected 'el', got {v:?}"),
3399 }
3400 // substring swaps args if start > end
3401 match eval("'hello'.substring(3, 1)").unwrap() {
3402 Value::String(s) => assert_eq!(s, "el"),
3403 v => panic!("expected 'el', got {v:?}"),
3404 }
3405 }
3406
3407 #[test]
3408 fn test_string_index_of() {
3409 match eval("'hello world'.indexOf('world')").unwrap() {
3410 Value::Number(n) => assert_eq!(n, 6.0),
3411 v => panic!("expected 6, got {v:?}"),
3412 }
3413 match eval("'hello'.indexOf('xyz')").unwrap() {
3414 Value::Number(n) => assert_eq!(n, -1.0),
3415 v => panic!("expected -1, got {v:?}"),
3416 }
3417 }
3418
3419 #[test]
3420 fn test_string_last_index_of() {
3421 match eval("'abcabc'.lastIndexOf('abc')").unwrap() {
3422 Value::Number(n) => assert_eq!(n, 3.0),
3423 v => panic!("expected 3, got {v:?}"),
3424 }
3425 }
3426
3427 #[test]
3428 fn test_string_includes() {
3429 match eval("'hello world'.includes('world')").unwrap() {
3430 Value::Boolean(b) => assert!(b),
3431 v => panic!("expected true, got {v:?}"),
3432 }
3433 match eval("'hello'.includes('xyz')").unwrap() {
3434 Value::Boolean(b) => assert!(!b),
3435 v => panic!("expected false, got {v:?}"),
3436 }
3437 }
3438
3439 #[test]
3440 fn test_string_starts_ends_with() {
3441 match eval("'hello'.startsWith('hel')").unwrap() {
3442 Value::Boolean(b) => assert!(b),
3443 v => panic!("expected true, got {v:?}"),
3444 }
3445 match eval("'hello'.endsWith('llo')").unwrap() {
3446 Value::Boolean(b) => assert!(b),
3447 v => panic!("expected true, got {v:?}"),
3448 }
3449 }
3450
3451 #[test]
3452 fn test_string_trim() {
3453 match eval("' hello '.trim()").unwrap() {
3454 Value::String(s) => assert_eq!(s, "hello"),
3455 v => panic!("expected 'hello', got {v:?}"),
3456 }
3457 match eval("' hello '.trimStart()").unwrap() {
3458 Value::String(s) => assert_eq!(s, "hello "),
3459 v => panic!("expected 'hello ', got {v:?}"),
3460 }
3461 match eval("' hello '.trimEnd()").unwrap() {
3462 Value::String(s) => assert_eq!(s, " hello"),
3463 v => panic!("expected ' hello', got {v:?}"),
3464 }
3465 }
3466
3467 #[test]
3468 fn test_string_pad() {
3469 match eval("'5'.padStart(3, '0')").unwrap() {
3470 Value::String(s) => assert_eq!(s, "005"),
3471 v => panic!("expected '005', got {v:?}"),
3472 }
3473 match eval("'5'.padEnd(3, '0')").unwrap() {
3474 Value::String(s) => assert_eq!(s, "500"),
3475 v => panic!("expected '500', got {v:?}"),
3476 }
3477 }
3478
3479 #[test]
3480 fn test_string_repeat() {
3481 match eval("'ab'.repeat(3)").unwrap() {
3482 Value::String(s) => assert_eq!(s, "ababab"),
3483 v => panic!("expected 'ababab', got {v:?}"),
3484 }
3485 }
3486
3487 #[test]
3488 fn test_string_split() {
3489 // split returns an array; verify length and elements.
3490 match eval("'a,b,c'.split(',').length").unwrap() {
3491 Value::Number(n) => assert_eq!(n, 3.0),
3492 v => panic!("expected 3, got {v:?}"),
3493 }
3494 match eval("'a,b,c'.split(',')[0]").unwrap() {
3495 Value::String(s) => assert_eq!(s, "a"),
3496 v => panic!("expected 'a', got {v:?}"),
3497 }
3498 match eval("'a,b,c'.split(',')[2]").unwrap() {
3499 Value::String(s) => assert_eq!(s, "c"),
3500 v => panic!("expected 'c', got {v:?}"),
3501 }
3502 }
3503
3504 #[test]
3505 fn test_string_replace() {
3506 match eval("'hello world'.replace('world', 'there')").unwrap() {
3507 Value::String(s) => assert_eq!(s, "hello there"),
3508 v => panic!("expected 'hello there', got {v:?}"),
3509 }
3510 }
3511
3512 #[test]
3513 fn test_string_replace_all() {
3514 match eval("'aabbcc'.replaceAll('b', 'x')").unwrap() {
3515 Value::String(s) => assert_eq!(s, "aaxxcc"),
3516 v => panic!("expected 'aaxxcc', got {v:?}"),
3517 }
3518 }
3519
3520 #[test]
3521 fn test_string_case() {
3522 match eval("'Hello'.toLowerCase()").unwrap() {
3523 Value::String(s) => assert_eq!(s, "hello"),
3524 v => panic!("expected 'hello', got {v:?}"),
3525 }
3526 match eval("'Hello'.toUpperCase()").unwrap() {
3527 Value::String(s) => assert_eq!(s, "HELLO"),
3528 v => panic!("expected 'HELLO', got {v:?}"),
3529 }
3530 }
3531
3532 #[test]
3533 fn test_string_at() {
3534 match eval("'hello'.at(0)").unwrap() {
3535 Value::String(s) => assert_eq!(s, "h"),
3536 v => panic!("expected 'h', got {v:?}"),
3537 }
3538 match eval("'hello'.at(-1)").unwrap() {
3539 Value::String(s) => assert_eq!(s, "o"),
3540 v => panic!("expected 'o', got {v:?}"),
3541 }
3542 }
3543
3544 #[test]
3545 fn test_string_from_char_code() {
3546 match eval("String.fromCharCode(72, 101, 108)").unwrap() {
3547 Value::String(s) => assert_eq!(s, "Hel"),
3548 v => panic!("expected 'Hel', got {v:?}"),
3549 }
3550 }
3551
3552 #[test]
3553 fn test_string_from_code_point() {
3554 match eval("String.fromCodePoint(65, 66, 67)").unwrap() {
3555 Value::String(s) => assert_eq!(s, "ABC"),
3556 v => panic!("expected 'ABC', got {v:?}"),
3557 }
3558 }
3559
3560 // ── Number built-in tests ─────────────────────────────────
3561
3562 #[test]
3563 fn test_number_constructor() {
3564 match eval("Number('42')").unwrap() {
3565 Value::Number(n) => assert_eq!(n, 42.0),
3566 v => panic!("expected 42, got {v:?}"),
3567 }
3568 match eval("Number(true)").unwrap() {
3569 Value::Number(n) => assert_eq!(n, 1.0),
3570 v => panic!("expected 1, got {v:?}"),
3571 }
3572 match eval("Number()").unwrap() {
3573 Value::Number(n) => assert_eq!(n, 0.0),
3574 v => panic!("expected 0, got {v:?}"),
3575 }
3576 }
3577
3578 #[test]
3579 fn test_number_is_nan() {
3580 match eval("Number.isNaN(NaN)").unwrap() {
3581 Value::Boolean(b) => assert!(b),
3582 v => panic!("expected true, got {v:?}"),
3583 }
3584 match eval("Number.isNaN(42)").unwrap() {
3585 Value::Boolean(b) => assert!(!b),
3586 v => panic!("expected false, got {v:?}"),
3587 }
3588 // Number.isNaN doesn't coerce — string "NaN" is not NaN.
3589 match eval("Number.isNaN('NaN')").unwrap() {
3590 Value::Boolean(b) => assert!(!b),
3591 v => panic!("expected false, got {v:?}"),
3592 }
3593 }
3594
3595 #[test]
3596 fn test_number_is_finite() {
3597 match eval("Number.isFinite(42)").unwrap() {
3598 Value::Boolean(b) => assert!(b),
3599 v => panic!("expected true, got {v:?}"),
3600 }
3601 match eval("Number.isFinite(Infinity)").unwrap() {
3602 Value::Boolean(b) => assert!(!b),
3603 v => panic!("expected false, got {v:?}"),
3604 }
3605 }
3606
3607 #[test]
3608 fn test_number_is_integer() {
3609 match eval("Number.isInteger(42)").unwrap() {
3610 Value::Boolean(b) => assert!(b),
3611 v => panic!("expected true, got {v:?}"),
3612 }
3613 match eval("Number.isInteger(42.5)").unwrap() {
3614 Value::Boolean(b) => assert!(!b),
3615 v => panic!("expected false, got {v:?}"),
3616 }
3617 }
3618
3619 #[test]
3620 fn test_number_is_safe_integer() {
3621 match eval("Number.isSafeInteger(42)").unwrap() {
3622 Value::Boolean(b) => assert!(b),
3623 v => panic!("expected true, got {v:?}"),
3624 }
3625 match eval("Number.isSafeInteger(9007199254740992)").unwrap() {
3626 Value::Boolean(b) => assert!(!b),
3627 v => panic!("expected false, got {v:?}"),
3628 }
3629 }
3630
3631 #[test]
3632 fn test_number_constants() {
3633 match eval("Number.MAX_SAFE_INTEGER").unwrap() {
3634 Value::Number(n) => assert_eq!(n, 9007199254740991.0),
3635 v => panic!("expected MAX_SAFE_INTEGER, got {v:?}"),
3636 }
3637 match eval("Number.EPSILON").unwrap() {
3638 Value::Number(n) => assert_eq!(n, f64::EPSILON),
3639 v => panic!("expected EPSILON, got {v:?}"),
3640 }
3641 }
3642
3643 #[test]
3644 fn test_number_to_fixed() {
3645 match eval("var n = 3.14159; n.toFixed(2)").unwrap() {
3646 Value::String(s) => assert_eq!(s, "3.14"),
3647 v => panic!("expected '3.14', got {v:?}"),
3648 }
3649 }
3650
3651 #[test]
3652 fn test_number_to_string_radix() {
3653 match eval("var n = 255; n.toString(16)").unwrap() {
3654 Value::String(s) => assert_eq!(s, "ff"),
3655 v => panic!("expected 'ff', got {v:?}"),
3656 }
3657 match eval("var n = 10; n.toString(2)").unwrap() {
3658 Value::String(s) => assert_eq!(s, "1010"),
3659 v => panic!("expected '1010', got {v:?}"),
3660 }
3661 }
3662
3663 #[test]
3664 fn test_number_parse_int() {
3665 match eval("Number.parseInt('42')").unwrap() {
3666 Value::Number(n) => assert_eq!(n, 42.0),
3667 v => panic!("expected 42, got {v:?}"),
3668 }
3669 }
3670
3671 // ── Boolean built-in tests ────────────────────────────────
3672
3673 #[test]
3674 fn test_boolean_constructor() {
3675 match eval("Boolean(1)").unwrap() {
3676 Value::Boolean(b) => assert!(b),
3677 v => panic!("expected true, got {v:?}"),
3678 }
3679 match eval("Boolean(0)").unwrap() {
3680 Value::Boolean(b) => assert!(!b),
3681 v => panic!("expected false, got {v:?}"),
3682 }
3683 match eval("Boolean('')").unwrap() {
3684 Value::Boolean(b) => assert!(!b),
3685 v => panic!("expected false, got {v:?}"),
3686 }
3687 match eval("Boolean('hello')").unwrap() {
3688 Value::Boolean(b) => assert!(b),
3689 v => panic!("expected true, got {v:?}"),
3690 }
3691 }
3692
3693 #[test]
3694 fn test_boolean_to_string() {
3695 match eval("true.toString()").unwrap() {
3696 Value::String(s) => assert_eq!(s, "true"),
3697 v => panic!("expected 'true', got {v:?}"),
3698 }
3699 match eval("false.toString()").unwrap() {
3700 Value::String(s) => assert_eq!(s, "false"),
3701 v => panic!("expected 'false', got {v:?}"),
3702 }
3703 }
3704
3705 // ── Symbol built-in tests ─────────────────────────────────
3706
3707 #[test]
3708 fn test_symbol_uniqueness() {
3709 // Each Symbol() call should produce a unique value.
3710 match eval("var a = Symbol('x'); var b = Symbol('x'); a === b").unwrap() {
3711 Value::Boolean(b) => assert!(!b),
3712 v => panic!("expected false, got {v:?}"),
3713 }
3714 }
3715
3716 #[test]
3717 fn test_symbol_well_known() {
3718 match eval("typeof Symbol.iterator").unwrap() {
3719 Value::String(s) => assert_eq!(s, "string"),
3720 v => panic!("expected 'string', got {v:?}"),
3721 }
3722 match eval("Symbol.iterator").unwrap() {
3723 Value::String(s) => assert_eq!(s, "@@iterator"),
3724 v => panic!("expected '@@iterator', got {v:?}"),
3725 }
3726 }
3727
3728 #[test]
3729 fn test_symbol_for_and_key_for() {
3730 // "for" is a keyword, so use bracket notation: Symbol["for"](...).
3731 match eval("Symbol['for']('test') === Symbol['for']('test')").unwrap() {
3732 Value::Boolean(b) => assert!(b),
3733 v => panic!("expected true, got {v:?}"),
3734 }
3735 match eval("Symbol.keyFor(Symbol['for']('mykey'))").unwrap() {
3736 Value::String(s) => assert_eq!(s, "mykey"),
3737 v => panic!("expected 'mykey', got {v:?}"),
3738 }
3739 }
3740
3741 // ── Primitive auto-boxing tests ───────────────────────────
3742
3743 #[test]
3744 fn test_string_method_chaining() {
3745 match eval("' Hello World '.trim().toLowerCase()").unwrap() {
3746 Value::String(s) => assert_eq!(s, "hello world"),
3747 v => panic!("expected 'hello world', got {v:?}"),
3748 }
3749 }
3750
3751 #[test]
3752 fn test_string_substr() {
3753 match eval("'hello world'.substr(6, 5)").unwrap() {
3754 Value::String(s) => assert_eq!(s, "world"),
3755 v => panic!("expected 'world', got {v:?}"),
3756 }
3757 }
3758
3759 // ── Math built-in ─────────────────────────────────────────
3760
3761 #[test]
3762 fn test_math_constants() {
3763 let r = eval("Math.PI").unwrap();
3764 match r {
3765 Value::Number(n) => assert!((n - std::f64::consts::PI).abs() < 1e-10),
3766 _ => panic!("Expected number"),
3767 }
3768 let r = eval("Math.E").unwrap();
3769 match r {
3770 Value::Number(n) => assert!((n - std::f64::consts::E).abs() < 1e-10),
3771 _ => panic!("Expected number"),
3772 }
3773 let r = eval("Math.SQRT2").unwrap();
3774 match r {
3775 Value::Number(n) => assert!((n - std::f64::consts::SQRT_2).abs() < 1e-10),
3776 _ => panic!("Expected number"),
3777 }
3778 }
3779
3780 #[test]
3781 fn test_math_abs() {
3782 assert_eq!(eval("Math.abs(-5)").unwrap().to_number(), 5.0);
3783 assert_eq!(eval("Math.abs(3)").unwrap().to_number(), 3.0);
3784 assert_eq!(eval("Math.abs(0)").unwrap().to_number(), 0.0);
3785 }
3786
3787 #[test]
3788 fn test_math_floor_ceil_round_trunc() {
3789 assert_eq!(eval("Math.floor(4.7)").unwrap().to_number(), 4.0);
3790 assert_eq!(eval("Math.ceil(4.2)").unwrap().to_number(), 5.0);
3791 assert_eq!(eval("Math.round(4.5)").unwrap().to_number(), 5.0);
3792 assert_eq!(eval("Math.round(4.4)").unwrap().to_number(), 4.0);
3793 assert_eq!(eval("Math.trunc(4.7)").unwrap().to_number(), 4.0);
3794 assert_eq!(eval("Math.trunc(-4.7)").unwrap().to_number(), -4.0);
3795 }
3796
3797 #[test]
3798 fn test_math_max_min() {
3799 assert_eq!(eval("Math.max(1, 3, 2)").unwrap().to_number(), 3.0);
3800 assert_eq!(eval("Math.min(1, 3, 2)").unwrap().to_number(), 1.0);
3801 assert_eq!(eval("Math.max()").unwrap().to_number(), f64::NEG_INFINITY);
3802 assert_eq!(eval("Math.min()").unwrap().to_number(), f64::INFINITY);
3803 }
3804
3805 #[test]
3806 fn test_math_pow_sqrt() {
3807 assert_eq!(eval("Math.pow(2, 10)").unwrap().to_number(), 1024.0);
3808 assert_eq!(eval("Math.sqrt(9)").unwrap().to_number(), 3.0);
3809 let cbrt = eval("Math.cbrt(27)").unwrap().to_number();
3810 assert!((cbrt - 3.0).abs() < 1e-10);
3811 }
3812
3813 #[test]
3814 fn test_math_trig() {
3815 let sin = eval("Math.sin(0)").unwrap().to_number();
3816 assert!(sin.abs() < 1e-10);
3817 let cos = eval("Math.cos(0)").unwrap().to_number();
3818 assert!((cos - 1.0).abs() < 1e-10);
3819 let atan2 = eval("Math.atan2(1, 1)").unwrap().to_number();
3820 assert!((atan2 - std::f64::consts::FRAC_PI_4).abs() < 1e-10);
3821 }
3822
3823 #[test]
3824 fn test_math_log_exp() {
3825 let exp = eval("Math.exp(1)").unwrap().to_number();
3826 assert!((exp - std::f64::consts::E).abs() < 1e-10);
3827 let log = eval("Math.log(Math.E)").unwrap().to_number();
3828 assert!((log - 1.0).abs() < 1e-10);
3829 let log2 = eval("Math.log2(8)").unwrap().to_number();
3830 assert!((log2 - 3.0).abs() < 1e-10);
3831 let log10 = eval("Math.log10(1000)").unwrap().to_number();
3832 assert!((log10 - 3.0).abs() < 1e-10);
3833 }
3834
3835 #[test]
3836 fn test_math_sign() {
3837 assert_eq!(eval("Math.sign(5)").unwrap().to_number(), 1.0);
3838 assert_eq!(eval("Math.sign(-5)").unwrap().to_number(), -1.0);
3839 assert_eq!(eval("Math.sign(0)").unwrap().to_number(), 0.0);
3840 }
3841
3842 #[test]
3843 fn test_math_clz32() {
3844 assert_eq!(eval("Math.clz32(1)").unwrap().to_number(), 31.0);
3845 assert_eq!(eval("Math.clz32(0)").unwrap().to_number(), 32.0);
3846 }
3847
3848 #[test]
3849 fn test_math_imul() {
3850 assert_eq!(eval("Math.imul(3, 4)").unwrap().to_number(), 12.0);
3851 assert_eq!(eval("Math.imul(0xffffffff, 5)").unwrap().to_number(), -5.0);
3852 }
3853
3854 #[test]
3855 fn test_math_hypot() {
3856 assert_eq!(eval("Math.hypot(3, 4)").unwrap().to_number(), 5.0);
3857 assert_eq!(eval("Math.hypot()").unwrap().to_number(), 0.0);
3858 }
3859
3860 #[test]
3861 fn test_math_random() {
3862 match eval("var r = Math.random(); r >= 0 && r < 1").unwrap() {
3863 Value::Boolean(b) => assert!(b),
3864 v => panic!("expected true, got {v:?}"),
3865 }
3866 }
3867
3868 #[test]
3869 fn test_math_fround() {
3870 let r = eval("Math.fround(5.5)").unwrap().to_number();
3871 assert_eq!(r, 5.5f32 as f64);
3872 }
3873
3874 // ── Date built-in ─────────────────────────────────────────
3875
3876 #[test]
3877 fn test_date_now() {
3878 let r = eval("Date.now()").unwrap().to_number();
3879 assert!(r > 1_577_836_800_000.0);
3880 }
3881
3882 #[test]
3883 fn test_date_utc() {
3884 let r = eval("Date.UTC(2020, 0, 1)").unwrap().to_number();
3885 assert_eq!(r, 1_577_836_800_000.0);
3886 }
3887
3888 #[test]
3889 fn test_date_parse() {
3890 let r = eval("Date.parse('2020-01-01T00:00:00.000Z')")
3891 .unwrap()
3892 .to_number();
3893 assert_eq!(r, 1_577_836_800_000.0);
3894 }
3895
3896 #[test]
3897 fn test_date_constructor_ms() {
3898 let r = eval("var d = new Date(1577836800000); d.getFullYear()")
3899 .unwrap()
3900 .to_number();
3901 assert_eq!(r, 2020.0);
3902 }
3903
3904 #[test]
3905 fn test_date_constructor_components() {
3906 let r = eval("var d = new Date(2020, 0, 1, 0, 0, 0, 0); d.getTime()")
3907 .unwrap()
3908 .to_number();
3909 assert_eq!(r, 1_577_836_800_000.0);
3910 }
3911
3912 #[test]
3913 fn test_date_getters() {
3914 let src = r#"
3915 var d = new Date(1577836800000);
3916 var results = [
3917 d.getFullYear(),
3918 d.getMonth(),
3919 d.getDate(),
3920 d.getHours(),
3921 d.getMinutes(),
3922 d.getSeconds(),
3923 d.getMilliseconds(),
3924 d.getDay()
3925 ];
3926 results[0] === 2020 && results[1] === 0 && results[2] === 1 &&
3927 results[3] === 0 && results[4] === 0 && results[5] === 0 &&
3928 results[6] === 0 && results[7] === 3
3929 "#;
3930 match eval(src).unwrap() {
3931 Value::Boolean(b) => assert!(b),
3932 v => panic!("expected true, got {v:?}"),
3933 }
3934 }
3935
3936 #[test]
3937 fn test_date_setters() {
3938 let src = r#"
3939 var d = new Date(1577836800000);
3940 d.setFullYear(2025);
3941 d.getFullYear()
3942 "#;
3943 assert_eq!(eval(src).unwrap().to_number(), 2025.0);
3944 }
3945
3946 #[test]
3947 fn test_date_to_iso_string() {
3948 match eval("var d = new Date(1577836800000); d.toISOString()").unwrap() {
3949 Value::String(s) => assert_eq!(s, "2020-01-01T00:00:00.000Z"),
3950 v => panic!("expected ISO string, got {v:?}"),
3951 }
3952 }
3953
3954 #[test]
3955 fn test_date_value_of() {
3956 let r = eval("var d = new Date(1577836800000); d.valueOf()")
3957 .unwrap()
3958 .to_number();
3959 assert_eq!(r, 1_577_836_800_000.0);
3960 }
3961
3962 #[test]
3963 fn test_date_to_string() {
3964 let r = eval("var d = new Date(1577836800000); d.toString()").unwrap();
3965 match r {
3966 Value::String(s) => assert!(s.contains("2020") && s.contains("GMT")),
3967 _ => panic!("Expected string"),
3968 }
3969 }
3970
3971 #[test]
3972 fn test_date_to_json() {
3973 match eval("var d = new Date(1577836800000); d.toJSON()").unwrap() {
3974 Value::String(s) => assert_eq!(s, "2020-01-01T00:00:00.000Z"),
3975 v => panic!("expected ISO string, got {v:?}"),
3976 }
3977 }
3978
3979 #[test]
3980 fn test_date_constructor_string() {
3981 let r = eval("var d = new Date('2020-06-15T12:30:00Z'); d.getMonth()")
3982 .unwrap()
3983 .to_number();
3984 assert_eq!(r, 5.0);
3985 }
3986
3987 // ── JSON built-in ─────────────────────────────────────────
3988
3989 #[test]
3990 fn test_json_parse_primitives() {
3991 assert!(matches!(eval("JSON.parse('null')").unwrap(), Value::Null));
3992 match eval("JSON.parse('true')").unwrap() {
3993 Value::Boolean(b) => assert!(b),
3994 v => panic!("expected true, got {v:?}"),
3995 }
3996 match eval("JSON.parse('false')").unwrap() {
3997 Value::Boolean(b) => assert!(!b),
3998 v => panic!("expected false, got {v:?}"),
3999 }
4000 assert_eq!(eval("JSON.parse('42')").unwrap().to_number(), 42.0);
4001 match eval(r#"JSON.parse('"hello"')"#).unwrap() {
4002 Value::String(s) => assert_eq!(s, "hello"),
4003 v => panic!("expected 'hello', got {v:?}"),
4004 }
4005 }
4006
4007 #[test]
4008 fn test_json_parse_array() {
4009 let src = r#"
4010 var a = JSON.parse('[1, 2, 3]');
4011 a.length === 3 && a[0] === 1 && a[1] === 2 && a[2] === 3
4012 "#;
4013 match eval(src).unwrap() {
4014 Value::Boolean(b) => assert!(b),
4015 v => panic!("expected true, got {v:?}"),
4016 }
4017 }
4018
4019 #[test]
4020 fn test_json_parse_object() {
4021 let src = r#"
4022 var o = JSON.parse('{"name":"test","value":42}');
4023 o.name === "test" && o.value === 42
4024 "#;
4025 match eval(src).unwrap() {
4026 Value::Boolean(b) => assert!(b),
4027 v => panic!("expected true, got {v:?}"),
4028 }
4029 }
4030
4031 #[test]
4032 fn test_json_parse_nested() {
4033 let src = r#"
4034 var o = JSON.parse('{"a":[1,{"b":2}]}');
4035 o.a[1].b === 2
4036 "#;
4037 match eval(src).unwrap() {
4038 Value::Boolean(b) => assert!(b),
4039 v => panic!("expected true, got {v:?}"),
4040 }
4041 }
4042
4043 #[test]
4044 fn test_json_parse_invalid() {
4045 assert!(eval("JSON.parse('{invalid}')").is_err());
4046 assert!(eval("JSON.parse('')").is_err());
4047 }
4048
4049 #[test]
4050 fn test_json_stringify_primitives() {
4051 match eval("JSON.stringify(null)").unwrap() {
4052 Value::String(s) => assert_eq!(s, "null"),
4053 v => panic!("expected 'null', got {v:?}"),
4054 }
4055 match eval("JSON.stringify(true)").unwrap() {
4056 Value::String(s) => assert_eq!(s, "true"),
4057 v => panic!("expected 'true', got {v:?}"),
4058 }
4059 match eval("JSON.stringify(42)").unwrap() {
4060 Value::String(s) => assert_eq!(s, "42"),
4061 v => panic!("expected '42', got {v:?}"),
4062 }
4063 match eval(r#"JSON.stringify("hello")"#).unwrap() {
4064 Value::String(s) => assert_eq!(s, "\"hello\""),
4065 v => panic!("expected quoted hello, got {v:?}"),
4066 }
4067 }
4068
4069 #[test]
4070 fn test_json_stringify_array() {
4071 match eval("JSON.stringify([1, 2, 3])").unwrap() {
4072 Value::String(s) => assert_eq!(s, "[1,2,3]"),
4073 v => panic!("expected '[1,2,3]', got {v:?}"),
4074 }
4075 }
4076
4077 #[test]
4078 fn test_json_stringify_object() {
4079 let src = r#"
4080 var o = {a: 1, b: "hello"};
4081 JSON.stringify(o)
4082 "#;
4083 let r = eval(src).unwrap();
4084 match r {
4085 Value::String(s) => {
4086 assert!(s.contains("\"a\"") && s.contains("\"b\""));
4087 assert!(s.contains('1') && s.contains("\"hello\""));
4088 }
4089 _ => panic!("Expected string"),
4090 }
4091 }
4092
4093 #[test]
4094 fn test_json_stringify_nested() {
4095 let src = r#"
4096 JSON.stringify({a: [1, 2], b: {c: 3}})
4097 "#;
4098 let r = eval(src).unwrap();
4099 match r {
4100 Value::String(s) => {
4101 assert!(s.contains("[1,2]"));
4102 assert!(s.contains("\"c\":3") || s.contains("\"c\": 3"));
4103 }
4104 _ => panic!("Expected string"),
4105 }
4106 }
4107
4108 #[test]
4109 fn test_json_stringify_special_values() {
4110 match eval("JSON.stringify(NaN)").unwrap() {
4111 Value::String(s) => assert_eq!(s, "null"),
4112 v => panic!("expected 'null', got {v:?}"),
4113 }
4114 match eval("JSON.stringify(Infinity)").unwrap() {
4115 Value::String(s) => assert_eq!(s, "null"),
4116 v => panic!("expected 'null', got {v:?}"),
4117 }
4118 assert!(matches!(
4119 eval("JSON.stringify(undefined)").unwrap(),
4120 Value::Undefined
4121 ));
4122 }
4123
4124 #[test]
4125 fn test_json_stringify_with_indent() {
4126 let src = r#"JSON.stringify([1, 2], null, 2)"#;
4127 let r = eval(src).unwrap();
4128 match r {
4129 Value::String(s) => {
4130 assert!(s.contains('\n'));
4131 assert!(s.contains(" 1"));
4132 }
4133 _ => panic!("Expected string"),
4134 }
4135 }
4136
4137 #[test]
4138 fn test_json_parse_escape_sequences() {
4139 let src = r#"JSON.parse('"hello\\nworld"')"#;
4140 match eval(src).unwrap() {
4141 Value::String(s) => assert_eq!(s, "hello\nworld"),
4142 v => panic!("expected escaped string, got {v:?}"),
4143 }
4144 }
4145
4146 #[test]
4147 fn test_json_roundtrip() {
4148 let src = r#"
4149 var original = {name: "test", values: [1, 2, 3], nested: {ok: true}};
4150 var json = JSON.stringify(original);
4151 var parsed = JSON.parse(json);
4152 parsed.name === "test" && parsed.values[1] === 2 && parsed.nested.ok === true
4153 "#;
4154 match eval(src).unwrap() {
4155 Value::Boolean(b) => assert!(b),
4156 v => panic!("expected true, got {v:?}"),
4157 }
4158 }
4159
4160 #[test]
4161 fn test_json_stringify_circular_detection() {
4162 let src = r#"
4163 var obj = {};
4164 obj.self = obj;
4165 try {
4166 JSON.stringify(obj);
4167 false;
4168 } catch (e) {
4169 e.message.indexOf("circular") !== -1;
4170 }
4171 "#;
4172 match eval(src).unwrap() {
4173 Value::Boolean(b) => assert!(b),
4174 v => panic!("expected true, got {v:?}"),
4175 }
4176 }
4177
4178 #[test]
4179 fn test_json_parse_unicode_escape() {
4180 let src = r#"JSON.parse('"\\u0041"')"#;
4181 match eval(src).unwrap() {
4182 Value::String(s) => assert_eq!(s, "A"),
4183 v => panic!("expected 'A', got {v:?}"),
4184 }
4185 }
4186
4187 #[test]
4188 fn test_json_stringify_empty() {
4189 match eval("JSON.stringify([])").unwrap() {
4190 Value::String(s) => assert_eq!(s, "[]"),
4191 v => panic!("expected '[]', got {v:?}"),
4192 }
4193 match eval("JSON.stringify({})").unwrap() {
4194 Value::String(s) => assert_eq!(s, "{}"),
4195 v => panic!("expected '{{}}', got {v:?}"),
4196 }
4197 }
4198
4199 // ── RegExp tests ────────────────────────────────────────
4200
4201 #[test]
4202 fn test_regexp_constructor() {
4203 match eval("var r = new RegExp('abc', 'g'); r.source").unwrap() {
4204 Value::String(s) => assert_eq!(s, "abc"),
4205 v => panic!("expected 'abc', got {v:?}"),
4206 }
4207 match eval("var r = new RegExp('abc', 'gi'); r.flags").unwrap() {
4208 Value::String(s) => assert_eq!(s, "gi"),
4209 v => panic!("expected 'gi', got {v:?}"),
4210 }
4211 match eval("var r = new RegExp('abc'); r.global").unwrap() {
4212 Value::Boolean(b) => assert!(!b),
4213 v => panic!("expected false, got {v:?}"),
4214 }
4215 match eval("var r = new RegExp('abc', 'g'); r.global").unwrap() {
4216 Value::Boolean(b) => assert!(b),
4217 v => panic!("expected true, got {v:?}"),
4218 }
4219 }
4220
4221 #[test]
4222 fn test_regexp_test() {
4223 match eval("var r = new RegExp('abc'); r.test('xabcx')").unwrap() {
4224 Value::Boolean(b) => assert!(b),
4225 v => panic!("expected true, got {v:?}"),
4226 }
4227 match eval("var r = new RegExp('abc'); r.test('xyz')").unwrap() {
4228 Value::Boolean(b) => assert!(!b),
4229 v => panic!("expected false, got {v:?}"),
4230 }
4231 match eval("var r = new RegExp('\\\\d+'); r.test('abc123')").unwrap() {
4232 Value::Boolean(b) => assert!(b),
4233 v => panic!("expected true, got {v:?}"),
4234 }
4235 }
4236
4237 #[test]
4238 fn test_regexp_exec() {
4239 match eval("var r = new RegExp('(a)(b)(c)'); var m = r.exec('abc'); m[0]").unwrap() {
4240 Value::String(s) => assert_eq!(s, "abc"),
4241 v => panic!("expected 'abc', got {v:?}"),
4242 }
4243 match eval("var r = new RegExp('(a)(b)(c)'); var m = r.exec('abc'); m[1]").unwrap() {
4244 Value::String(s) => assert_eq!(s, "a"),
4245 v => panic!("expected 'a', got {v:?}"),
4246 }
4247 match eval("var r = new RegExp('b+'); var m = r.exec('aabbc'); m[0]").unwrap() {
4248 Value::String(s) => assert_eq!(s, "bb"),
4249 v => panic!("expected 'bb', got {v:?}"),
4250 }
4251 match eval("var r = new RegExp('xyz'); r.exec('abc')").unwrap() {
4252 Value::Null => {}
4253 v => panic!("expected null, got {v:?}"),
4254 }
4255 }
4256
4257 #[test]
4258 fn test_regexp_exec_global() {
4259 let src = "var r = new RegExp('a', 'g'); r.exec('aba')[0]";
4260 match eval(src).unwrap() {
4261 Value::String(s) => assert_eq!(s, "a"),
4262 v => panic!("expected 'a', got {v:?}"),
4263 }
4264 let src = r#"
4265 var r = new RegExp('a', 'g');
4266 r.exec('aba');
4267 var m = r.exec('aba');
4268 m[0] + ',' + m.index
4269 "#;
4270 match eval(src).unwrap() {
4271 Value::String(s) => assert_eq!(s, "a,2"),
4272 v => panic!("expected 'a,2', got {v:?}"),
4273 }
4274 }
4275
4276 #[test]
4277 fn test_regexp_to_string() {
4278 match eval("var r = new RegExp('abc', 'gi'); r.toString()").unwrap() {
4279 Value::String(s) => assert_eq!(s, "/abc/gi"),
4280 v => panic!("expected '/abc/gi', got {v:?}"),
4281 }
4282 match eval("/hello\\d+/.toString()").unwrap() {
4283 Value::String(s) => assert_eq!(s, "/hello\\d+/"),
4284 v => panic!("expected '/hello\\d+/', got {v:?}"),
4285 }
4286 }
4287
4288 #[test]
4289 fn test_regexp_literal() {
4290 match eval("/abc/.test('abc')").unwrap() {
4291 Value::Boolean(b) => assert!(b),
4292 v => panic!("expected true, got {v:?}"),
4293 }
4294 match eval("/abc/.test('xyz')").unwrap() {
4295 Value::Boolean(b) => assert!(!b),
4296 v => panic!("expected false, got {v:?}"),
4297 }
4298 match eval("/\\d+/.test('123')").unwrap() {
4299 Value::Boolean(b) => assert!(b),
4300 v => panic!("expected true, got {v:?}"),
4301 }
4302 match eval("/abc/i.test('ABC')").unwrap() {
4303 Value::Boolean(b) => assert!(b),
4304 v => panic!("expected true, got {v:?}"),
4305 }
4306 }
4307
4308 #[test]
4309 fn test_regexp_literal_exec() {
4310 match eval("var m = /([a-z]+)(\\d+)/.exec('abc123'); m[0]").unwrap() {
4311 Value::String(s) => assert_eq!(s, "abc123"),
4312 v => panic!("expected 'abc123', got {v:?}"),
4313 }
4314 match eval("var m = /([a-z]+)(\\d+)/.exec('abc123'); m[1]").unwrap() {
4315 Value::String(s) => assert_eq!(s, "abc"),
4316 v => panic!("expected 'abc', got {v:?}"),
4317 }
4318 match eval("var m = /([a-z]+)(\\d+)/.exec('abc123'); m[2]").unwrap() {
4319 Value::String(s) => assert_eq!(s, "123"),
4320 v => panic!("expected '123', got {v:?}"),
4321 }
4322 }
4323
4324 #[test]
4325 fn test_string_match_regexp() {
4326 match eval("'hello world'.match(/world/)[0]").unwrap() {
4327 Value::String(s) => assert_eq!(s, "world"),
4328 v => panic!("expected 'world', got {v:?}"),
4329 }
4330 match eval("'aaa'.match(/a/g).length").unwrap() {
4331 Value::Number(n) => assert_eq!(n, 3.0),
4332 v => panic!("expected 3, got {v:?}"),
4333 }
4334 match eval("'abc'.match(/xyz/)").unwrap() {
4335 Value::Null => {}
4336 v => panic!("expected null, got {v:?}"),
4337 }
4338 }
4339
4340 #[test]
4341 fn test_string_search_regexp() {
4342 match eval("'hello world'.search(/world/)").unwrap() {
4343 Value::Number(n) => assert_eq!(n, 6.0),
4344 v => panic!("expected 6, got {v:?}"),
4345 }
4346 match eval("'abc'.search(/xyz/)").unwrap() {
4347 Value::Number(n) => assert_eq!(n, -1.0),
4348 v => panic!("expected -1, got {v:?}"),
4349 }
4350 match eval("'abc123'.search(/\\d/)").unwrap() {
4351 Value::Number(n) => assert_eq!(n, 3.0),
4352 v => panic!("expected 3, got {v:?}"),
4353 }
4354 }
4355
4356 #[test]
4357 fn test_string_replace_regexp() {
4358 match eval("'hello world'.replace(/world/, 'rust')").unwrap() {
4359 Value::String(s) => assert_eq!(s, "hello rust"),
4360 v => panic!("expected 'hello rust', got {v:?}"),
4361 }
4362 match eval("'aaa'.replace(/a/, 'b')").unwrap() {
4363 Value::String(s) => assert_eq!(s, "baa"),
4364 v => panic!("expected 'baa', got {v:?}"),
4365 }
4366 match eval("'aaa'.replace(/a/g, 'b')").unwrap() {
4367 Value::String(s) => assert_eq!(s, "bbb"),
4368 v => panic!("expected 'bbb', got {v:?}"),
4369 }
4370 }
4371
4372 #[test]
4373 fn test_string_replace_capture_groups() {
4374 let src = r#"'John Smith'.replace(/(\w+) (\w+)/, '$2, $1')"#;
4375 match eval(src).unwrap() {
4376 Value::String(s) => assert_eq!(s, "Smith, John"),
4377 v => panic!("expected 'Smith, John', got {v:?}"),
4378 }
4379 match eval("'abc'.replace(/(b)/, '[$1]')").unwrap() {
4380 Value::String(s) => assert_eq!(s, "a[b]c"),
4381 v => panic!("expected 'a[b]c', got {v:?}"),
4382 }
4383 }
4384
4385 #[test]
4386 fn test_string_split_regexp() {
4387 match eval("'a1b2c3'.split(/\\d/).length").unwrap() {
4388 Value::Number(n) => assert_eq!(n, 4.0),
4389 v => panic!("expected 4, got {v:?}"),
4390 }
4391 match eval("'a1b2c3'.split(/\\d/)[0]").unwrap() {
4392 Value::String(s) => assert_eq!(s, "a"),
4393 v => panic!("expected 'a', got {v:?}"),
4394 }
4395 }
4396
4397 #[test]
4398 fn test_regexp_ignore_case() {
4399 match eval("/abc/i.exec('XAbCx')[0]").unwrap() {
4400 Value::String(s) => assert_eq!(s, "AbC"),
4401 v => panic!("expected 'AbC', got {v:?}"),
4402 }
4403 }
4404
4405 #[test]
4406 fn test_regexp_multiline() {
4407 match eval("/^b/m.test('a\\nb')").unwrap() {
4408 Value::Boolean(b) => assert!(b),
4409 v => panic!("expected true, got {v:?}"),
4410 }
4411 match eval("/^b/.test('a\\nb')").unwrap() {
4412 Value::Boolean(b) => assert!(!b),
4413 v => panic!("expected false, got {v:?}"),
4414 }
4415 }
4416
4417 #[test]
4418 fn test_regexp_dot_all() {
4419 match eval("/a.b/s.test('a\\nb')").unwrap() {
4420 Value::Boolean(b) => assert!(b),
4421 v => panic!("expected true, got {v:?}"),
4422 }
4423 match eval("/a.b/.test('a\\nb')").unwrap() {
4424 Value::Boolean(b) => assert!(!b),
4425 v => panic!("expected false, got {v:?}"),
4426 }
4427 }
4428
4429 #[test]
4430 fn test_regexp_word_boundary() {
4431 match eval("/\\bfoo\\b/.test('a foo b')").unwrap() {
4432 Value::Boolean(b) => assert!(b),
4433 v => panic!("expected true, got {v:?}"),
4434 }
4435 match eval("/\\bfoo\\b/.test('foobar')").unwrap() {
4436 Value::Boolean(b) => assert!(!b),
4437 v => panic!("expected false, got {v:?}"),
4438 }
4439 }
4440
4441 #[test]
4442 fn test_regexp_quantifiers_vm() {
4443 match eval("/a{3}/.test('aaa')").unwrap() {
4444 Value::Boolean(b) => assert!(b),
4445 v => panic!("expected true, got {v:?}"),
4446 }
4447 match eval("/a{3}/.test('aa')").unwrap() {
4448 Value::Boolean(b) => assert!(!b),
4449 v => panic!("expected false, got {v:?}"),
4450 }
4451 match eval("/a+?/.exec('aaa')[0]").unwrap() {
4452 Value::String(s) => assert_eq!(s, "a"),
4453 v => panic!("expected 'a', got {v:?}"),
4454 }
4455 }
4456
4457 #[test]
4458 fn test_regexp_alternation_vm() {
4459 match eval("/cat|dog/.exec('I have a dog')[0]").unwrap() {
4460 Value::String(s) => assert_eq!(s, "dog"),
4461 v => panic!("expected 'dog', got {v:?}"),
4462 }
4463 }
4464
4465 #[test]
4466 fn test_regexp_lookahead_vm() {
4467 match eval("/a(?=b)/.test('ab')").unwrap() {
4468 Value::Boolean(b) => assert!(b),
4469 v => panic!("expected true, got {v:?}"),
4470 }
4471 match eval("/a(?=b)/.test('ac')").unwrap() {
4472 Value::Boolean(b) => assert!(!b),
4473 v => panic!("expected false, got {v:?}"),
4474 }
4475 match eval("/a(?!b)/.test('ac')").unwrap() {
4476 Value::Boolean(b) => assert!(b),
4477 v => panic!("expected true, got {v:?}"),
4478 }
4479 }
4480
4481 #[test]
4482 fn test_regexp_char_class_vm() {
4483 match eval("/[abc]/.test('b')").unwrap() {
4484 Value::Boolean(b) => assert!(b),
4485 v => panic!("expected true, got {v:?}"),
4486 }
4487 match eval("/[a-z]+/.exec('Hello')[0]").unwrap() {
4488 Value::String(s) => assert_eq!(s, "ello"),
4489 v => panic!("expected 'ello', got {v:?}"),
4490 }
4491 }
4492
4493 #[test]
4494 fn test_regexp_backreference_vm() {
4495 match eval("/(a)\\1/.test('aa')").unwrap() {
4496 Value::Boolean(b) => assert!(b),
4497 v => panic!("expected true, got {v:?}"),
4498 }
4499 match eval("/(a)\\1/.test('ab')").unwrap() {
4500 Value::Boolean(b) => assert!(!b),
4501 v => panic!("expected false, got {v:?}"),
4502 }
4503 }
4504
4505 #[test]
4506 fn test_regexp_properties() {
4507 match eval("var r = /abc/gim; r.global").unwrap() {
4508 Value::Boolean(b) => assert!(b),
4509 v => panic!("expected true, got {v:?}"),
4510 }
4511 match eval("/abc/.lastIndex").unwrap() {
4512 Value::Number(n) => assert_eq!(n, 0.0),
4513 v => panic!("expected 0, got {v:?}"),
4514 }
4515 }
4516
4517 #[test]
4518 fn test_string_replace_all_regexp() {
4519 match eval("'aba'.replaceAll(/a/g, 'x')").unwrap() {
4520 Value::String(s) => assert_eq!(s, "xbx"),
4521 v => panic!("expected 'xbx', got {v:?}"),
4522 }
4523 }
4524
4525 // ── Map tests ─────────────────────────────────────────────
4526
4527 #[test]
4528 fn test_map_basic() {
4529 match eval("var m = new Map(); m.set('a', 1); m.get('a')").unwrap() {
4530 Value::Number(n) => assert_eq!(n, 1.0),
4531 v => panic!("expected 1, got {v:?}"),
4532 }
4533 }
4534
4535 #[test]
4536 fn test_map_size() {
4537 match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.size").unwrap() {
4538 Value::Number(n) => assert_eq!(n, 2.0),
4539 v => panic!("expected 2, got {v:?}"),
4540 }
4541 }
4542
4543 #[test]
4544 fn test_map_has() {
4545 match eval("var m = new Map(); m.set('x', 10); m.has('x')").unwrap() {
4546 Value::Boolean(b) => assert!(b),
4547 v => panic!("expected true, got {v:?}"),
4548 }
4549 match eval("var m = new Map(); m.has('x')").unwrap() {
4550 Value::Boolean(b) => assert!(!b),
4551 v => panic!("expected false, got {v:?}"),
4552 }
4553 }
4554
4555 #[test]
4556 fn test_map_delete() {
4557 match eval("var m = new Map(); m.set('a', 1); m['delete']('a'); m.has('a')").unwrap() {
4558 Value::Boolean(b) => assert!(!b),
4559 v => panic!("expected false, got {v:?}"),
4560 }
4561 match eval("var m = new Map(); m.set('a', 1); m['delete']('a'); m.size").unwrap() {
4562 Value::Number(n) => assert_eq!(n, 0.0),
4563 v => panic!("expected 0, got {v:?}"),
4564 }
4565 }
4566
4567 #[test]
4568 fn test_map_clear() {
4569 match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.clear(); m.size").unwrap() {
4570 Value::Number(n) => assert_eq!(n, 0.0),
4571 v => panic!("expected 0, got {v:?}"),
4572 }
4573 }
4574
4575 #[test]
4576 fn test_map_overwrite() {
4577 match eval("var m = new Map(); m.set('a', 1); m.set('a', 2); m.get('a')").unwrap() {
4578 Value::Number(n) => assert_eq!(n, 2.0),
4579 v => panic!("expected 2, got {v:?}"),
4580 }
4581 // Size should still be 1 after overwriting.
4582 match eval("var m = new Map(); m.set('a', 1); m.set('a', 2); m.size").unwrap() {
4583 Value::Number(n) => assert_eq!(n, 1.0),
4584 v => panic!("expected 1, got {v:?}"),
4585 }
4586 }
4587
4588 #[test]
4589 fn test_map_get_missing() {
4590 match eval("var m = new Map(); m.get('missing')").unwrap() {
4591 Value::Undefined => {}
4592 v => panic!("expected undefined, got {v:?}"),
4593 }
4594 }
4595
4596 #[test]
4597 fn test_map_chaining() {
4598 // set() returns the Map for chaining.
4599 match eval("var m = new Map(); m.set('a', 1).set('b', 2); m.size").unwrap() {
4600 Value::Number(n) => assert_eq!(n, 2.0),
4601 v => panic!("expected 2, got {v:?}"),
4602 }
4603 }
4604
4605 #[test]
4606 fn test_map_nan_key() {
4607 // NaN === NaN for Map keys (SameValueZero).
4608 match eval("var m = new Map(); m.set(NaN, 'nan'); m.get(NaN)").unwrap() {
4609 Value::String(s) => assert_eq!(s, "nan"),
4610 v => panic!("expected 'nan', got {v:?}"),
4611 }
4612 }
4613
4614 #[test]
4615 fn test_map_object_key() {
4616 match eval("var m = new Map(); var o = {}; m.set(o, 'val'); m.get(o)").unwrap() {
4617 Value::String(s) => assert_eq!(s, "val"),
4618 v => panic!("expected 'val', got {v:?}"),
4619 }
4620 }
4621
4622 #[test]
4623 fn test_map_constructor_with_pairs() {
4624 match eval("var m = new Map([['a', 1], ['b', 2]]); m.get('b')").unwrap() {
4625 Value::Number(n) => assert_eq!(n, 2.0),
4626 v => panic!("expected 2, got {v:?}"),
4627 }
4628 }
4629
4630 #[test]
4631 fn test_map_keys_values_entries() {
4632 match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.keys().length").unwrap() {
4633 Value::Number(n) => assert_eq!(n, 2.0),
4634 v => panic!("expected 2, got {v:?}"),
4635 }
4636 match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.values()[1]").unwrap() {
4637 Value::Number(n) => assert_eq!(n, 2.0),
4638 v => panic!("expected 2, got {v:?}"),
4639 }
4640 match eval("var m = new Map(); m.set('a', 1); m.entries()[0][0]").unwrap() {
4641 Value::String(s) => assert_eq!(s, "a"),
4642 v => panic!("expected 'a', got {v:?}"),
4643 }
4644 }
4645
4646 #[test]
4647 fn test_map_insertion_order() {
4648 match eval("var m = new Map(); m.set('c', 3); m.set('a', 1); m.set('b', 2); m.keys()[0]")
4649 .unwrap()
4650 {
4651 Value::String(s) => assert_eq!(s, "c"),
4652 v => panic!("expected 'c', got {v:?}"),
4653 }
4654 }
4655
4656 // ── Set tests ─────────────────────────────────────────────
4657
4658 #[test]
4659 fn test_set_basic() {
4660 match eval("var s = new Set(); s.add(1); s.add(2); s.size").unwrap() {
4661 Value::Number(n) => assert_eq!(n, 2.0),
4662 v => panic!("expected 2, got {v:?}"),
4663 }
4664 }
4665
4666 #[test]
4667 fn test_set_has() {
4668 match eval("var s = new Set(); s.add(42); s.has(42)").unwrap() {
4669 Value::Boolean(b) => assert!(b),
4670 v => panic!("expected true, got {v:?}"),
4671 }
4672 match eval("var s = new Set(); s.has(42)").unwrap() {
4673 Value::Boolean(b) => assert!(!b),
4674 v => panic!("expected false, got {v:?}"),
4675 }
4676 }
4677
4678 #[test]
4679 fn test_set_delete() {
4680 match eval("var s = new Set(); s.add(1); s['delete'](1); s.has(1)").unwrap() {
4681 Value::Boolean(b) => assert!(!b),
4682 v => panic!("expected false, got {v:?}"),
4683 }
4684 match eval("var s = new Set(); s.add(1); s['delete'](1); s.size").unwrap() {
4685 Value::Number(n) => assert_eq!(n, 0.0),
4686 v => panic!("expected 0, got {v:?}"),
4687 }
4688 }
4689
4690 #[test]
4691 fn test_set_clear() {
4692 match eval("var s = new Set(); s.add(1); s.add(2); s.clear(); s.size").unwrap() {
4693 Value::Number(n) => assert_eq!(n, 0.0),
4694 v => panic!("expected 0, got {v:?}"),
4695 }
4696 }
4697
4698 #[test]
4699 fn test_set_uniqueness() {
4700 match eval("var s = new Set(); s.add(1); s.add(1); s.add(1); s.size").unwrap() {
4701 Value::Number(n) => assert_eq!(n, 1.0),
4702 v => panic!("expected 1, got {v:?}"),
4703 }
4704 }
4705
4706 #[test]
4707 fn test_set_chaining() {
4708 match eval("var s = new Set(); s.add(1).add(2).add(3); s.size").unwrap() {
4709 Value::Number(n) => assert_eq!(n, 3.0),
4710 v => panic!("expected 3, got {v:?}"),
4711 }
4712 }
4713
4714 #[test]
4715 fn test_set_nan() {
4716 match eval("var s = new Set(); s.add(NaN); s.add(NaN); s.size").unwrap() {
4717 Value::Number(n) => assert_eq!(n, 1.0),
4718 v => panic!("expected 1, got {v:?}"),
4719 }
4720 match eval("var s = new Set(); s.add(NaN); s.has(NaN)").unwrap() {
4721 Value::Boolean(b) => assert!(b),
4722 v => panic!("expected true, got {v:?}"),
4723 }
4724 }
4725
4726 #[test]
4727 fn test_set_constructor_from_array() {
4728 match eval("var s = new Set([1, 2, 3, 2, 1]); s.size").unwrap() {
4729 Value::Number(n) => assert_eq!(n, 3.0),
4730 v => panic!("expected 3, got {v:?}"),
4731 }
4732 }
4733
4734 #[test]
4735 fn test_set_values() {
4736 match eval("var s = new Set(); s.add('a'); s.add('b'); s.values().length").unwrap() {
4737 Value::Number(n) => assert_eq!(n, 2.0),
4738 v => panic!("expected 2, got {v:?}"),
4739 }
4740 }
4741
4742 #[test]
4743 fn test_set_entries() {
4744 // Set.entries() returns [value, value] pairs.
4745 match eval("var s = new Set(); s.add('x'); s.entries()[0][0]").unwrap() {
4746 Value::String(s) => assert_eq!(s, "x"),
4747 v => panic!("expected 'x', got {v:?}"),
4748 }
4749 match eval("var s = new Set(); s.add('x'); s.entries()[0][1]").unwrap() {
4750 Value::String(s) => assert_eq!(s, "x"),
4751 v => panic!("expected 'x', got {v:?}"),
4752 }
4753 }
4754
4755 #[test]
4756 fn test_set_insertion_order() {
4757 match eval("var s = new Set(); s.add('c'); s.add('a'); s.add('b'); s.values()[0]").unwrap()
4758 {
4759 Value::String(s) => assert_eq!(s, "c"),
4760 v => panic!("expected 'c', got {v:?}"),
4761 }
4762 }
4763
4764 // ── WeakMap tests ─────────────────────────────────────────
4765
4766 #[test]
4767 fn test_weakmap_basic() {
4768 match eval("var wm = new WeakMap(); var o = {}; wm.set(o, 'val'); wm.get(o)").unwrap() {
4769 Value::String(s) => assert_eq!(s, "val"),
4770 v => panic!("expected 'val', got {v:?}"),
4771 }
4772 }
4773
4774 #[test]
4775 fn test_weakmap_has_delete() {
4776 match eval("var wm = new WeakMap(); var o = {}; wm.set(o, 1); wm.has(o)").unwrap() {
4777 Value::Boolean(b) => assert!(b),
4778 v => panic!("expected true, got {v:?}"),
4779 }
4780 match eval("var wm = new WeakMap(); var o = {}; wm.set(o, 1); wm['delete'](o); wm.has(o)")
4781 .unwrap()
4782 {
4783 Value::Boolean(b) => assert!(!b),
4784 v => panic!("expected false, got {v:?}"),
4785 }
4786 }
4787
4788 #[test]
4789 fn test_weakmap_rejects_primitive_key() {
4790 assert!(eval("var wm = new WeakMap(); wm.set('str', 1)").is_err());
4791 assert!(eval("var wm = new WeakMap(); wm.set(42, 1)").is_err());
4792 }
4793
4794 // ── WeakSet tests ─────────────────────────────────────────
4795
4796 #[test]
4797 fn test_weakset_basic() {
4798 match eval("var ws = new WeakSet(); var o = {}; ws.add(o); ws.has(o)").unwrap() {
4799 Value::Boolean(b) => assert!(b),
4800 v => panic!("expected true, got {v:?}"),
4801 }
4802 }
4803
4804 #[test]
4805 fn test_weakset_delete() {
4806 match eval("var ws = new WeakSet(); var o = {}; ws.add(o); ws['delete'](o); ws.has(o)")
4807 .unwrap()
4808 {
4809 Value::Boolean(b) => assert!(!b),
4810 v => panic!("expected false, got {v:?}"),
4811 }
4812 }
4813
4814 #[test]
4815 fn test_weakset_rejects_primitive() {
4816 assert!(eval("var ws = new WeakSet(); ws.add('str')").is_err());
4817 assert!(eval("var ws = new WeakSet(); ws.add(42)").is_err());
4818 }
4819}