⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/browser/
js_vm.rs

1//! JavaScript Virtual Machine
2//!
3//! A stack-based bytecode interpreter for JavaScript. Uses arena allocation
4//! for objects and 32.32 fixed-point arithmetic for numbers (no floating
5//! point).
6
7#![allow(dead_code)]
8
9use alloc::{
10    collections::BTreeMap,
11    format,
12    string::{String, ToString},
13    vec::Vec,
14};
15
16use super::{
17    js_compiler::{Chunk, Constant, FunctionTemplate, Opcode},
18    js_lexer::{JsNumber, JS_FRAC_BITS, JS_NAN, JS_ONE, JS_ZERO},
19};
20
21// ---------------------------------------------------------------------------
22// JS VM Error Type
23// ---------------------------------------------------------------------------
24
25/// Errors produced by the JavaScript virtual machine
26#[derive(Debug, Clone)]
27pub enum JsVmError {
28    /// Execution step limit exceeded
29    ExecutionLimitExceeded,
30    /// Unknown bytecode opcode
31    UnknownOpcode { byte: u8 },
32    /// Uncaught exception from user code
33    UncaughtException { message: String },
34    /// Operand stack overflow
35    StackOverflow,
36    /// Operand stack underflow (pop from empty stack)
37    StackUnderflow,
38    /// No active call frame
39    NoCallFrame,
40    /// Instruction pointer out of bounds
41    IpOutOfBounds,
42    /// Attempted to call a non-callable value
43    NotCallable,
44    /// Invalid function ID
45    InvalidFunctionId,
46}
47
48impl core::fmt::Display for JsVmError {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        match self {
51            Self::ExecutionLimitExceeded => write!(f, "Execution limit exceeded"),
52            Self::UnknownOpcode { byte } => write!(f, "Unknown opcode: {}", byte),
53            Self::UncaughtException { message } => write!(f, "Uncaught: {}", message),
54            Self::StackOverflow => write!(f, "Stack overflow"),
55            Self::StackUnderflow => write!(f, "Stack underflow"),
56            Self::NoCallFrame => write!(f, "No call frame"),
57            Self::IpOutOfBounds => write!(f, "IP out of bounds"),
58            Self::NotCallable => write!(f, "Not callable"),
59            Self::InvalidFunctionId => write!(f, "Invalid function ID"),
60        }
61    }
62}
63
64// ---------------------------------------------------------------------------
65// JS Values
66// ---------------------------------------------------------------------------
67
68/// Arena index for objects
69pub type ObjectId = usize;
70
71/// Arena index for functions
72pub type FunctionId = usize;
73
74/// JavaScript value (no floating point)
75#[derive(Debug, Clone, Default)]
76pub enum JsValue {
77    #[default]
78    Undefined,
79    Null,
80    Boolean(bool),
81    Number(JsNumber),
82    String(String),
83    Object(ObjectId),
84    Function(FunctionId),
85}
86
87impl JsValue {
88    /// Convert to boolean (JavaScript truthiness)
89    pub fn to_boolean(&self) -> bool {
90        match self {
91            Self::Undefined | Self::Null => false,
92            Self::Boolean(b) => *b,
93            Self::Number(n) => *n != JS_ZERO && *n != JS_NAN,
94            Self::String(s) => !s.is_empty(),
95            Self::Object(_) | Self::Function(_) => true,
96        }
97    }
98
99    /// Convert to number
100    pub fn to_number(&self) -> JsNumber {
101        match self {
102            Self::Undefined => JS_NAN,
103            Self::Null => JS_ZERO,
104            Self::Boolean(true) => JS_ONE,
105            Self::Boolean(false) => JS_ZERO,
106            Self::Number(n) => *n,
107            Self::String(s) => parse_js_number(s),
108            Self::Object(_) | Self::Function(_) => JS_NAN,
109        }
110    }
111
112    /// Convert to string
113    pub fn to_js_string(&self) -> String {
114        match self {
115            Self::Undefined => "undefined".to_string(),
116            Self::Null => "null".to_string(),
117            Self::Boolean(b) => if *b { "true" } else { "false" }.to_string(),
118            Self::Number(n) => format_js_number(*n),
119            Self::String(s) => s.clone(),
120            Self::Object(_) => "[object Object]".to_string(),
121            Self::Function(_) => "function".to_string(),
122        }
123    }
124
125    /// JavaScript typeof
126    pub fn js_typeof(&self) -> &'static str {
127        match self {
128            Self::Undefined => "undefined",
129            Self::Null => "object", // historic JS quirk
130            Self::Boolean(_) => "boolean",
131            Self::Number(_) => "number",
132            Self::String(_) => "string",
133            Self::Object(_) => "object",
134            Self::Function(_) => "function",
135        }
136    }
137
138    /// Strict equality (===)
139    pub fn strict_eq(&self, other: &Self) -> bool {
140        match (self, other) {
141            (Self::Undefined, Self::Undefined) => true,
142            (Self::Null, Self::Null) => true,
143            (Self::Boolean(a), Self::Boolean(b)) => a == b,
144            (Self::Number(a), Self::Number(b)) => a == b,
145            (Self::String(a), Self::String(b)) => a == b,
146            (Self::Object(a), Self::Object(b)) => a == b,
147            (Self::Function(a), Self::Function(b)) => a == b,
148            _ => false,
149        }
150    }
151
152    /// Abstract equality (==), simplified
153    pub fn abstract_eq(&self, other: &Self) -> bool {
154        if self.strict_eq(other) {
155            return true;
156        }
157        match (self, other) {
158            (Self::Null, Self::Undefined) | (Self::Undefined, Self::Null) => true,
159            (Self::Number(a), Self::String(b)) => *a == parse_js_number(b),
160            (Self::String(a), Self::Number(b)) => parse_js_number(a) == *b,
161            _ => false,
162        }
163    }
164}
165
166// ---------------------------------------------------------------------------
167// JS Object (arena-managed)
168// ---------------------------------------------------------------------------
169
170/// Object internal type
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
172pub enum ObjectType {
173    #[default]
174    Ordinary,
175    Array,
176    Function,
177    Error,
178}
179
180/// A JavaScript object stored in the arena
181#[derive(Debug, Clone, Default)]
182pub struct JsObject {
183    /// Properties
184    pub properties: BTreeMap<String, JsValue>,
185    /// Prototype (arena index)
186    pub prototype: Option<ObjectId>,
187    /// Internal type
188    pub internal_type: ObjectType,
189}
190
191impl JsObject {
192    pub fn new() -> Self {
193        Self::default()
194    }
195
196    pub fn array() -> Self {
197        Self {
198            internal_type: ObjectType::Array,
199            ..Default::default()
200        }
201    }
202
203    pub fn get(&self, key: &str) -> JsValue {
204        self.properties
205            .get(key)
206            .cloned()
207            .unwrap_or(JsValue::Undefined)
208    }
209
210    pub fn set(&mut self, key: &str, value: JsValue) {
211        self.properties.insert(key.to_string(), value);
212    }
213}
214
215// ---------------------------------------------------------------------------
216// Call frame
217// ---------------------------------------------------------------------------
218
219/// A function call frame on the call stack
220#[derive(Debug, Clone)]
221pub struct CallFrame {
222    /// Function template being executed
223    pub function_id: FunctionId,
224    /// Instruction pointer within the function's bytecode
225    pub ip: usize,
226    /// Base slot on the operand stack
227    pub base_slot: usize,
228    /// Local variables
229    pub locals: Vec<JsValue>,
230    /// Bytecode reference (copied from template for execution)
231    pub bytecode: Vec<u8>,
232    /// Constants reference
233    pub constants: Vec<Constant>,
234}
235
236// ---------------------------------------------------------------------------
237// Try/catch state
238// ---------------------------------------------------------------------------
239
240#[derive(Debug, Clone)]
241struct TryState {
242    catch_ip: usize,
243    stack_depth: usize,
244    call_depth: usize,
245}
246
247// ---------------------------------------------------------------------------
248// VM
249// ---------------------------------------------------------------------------
250
251/// JavaScript virtual machine
252pub struct JsVm {
253    /// Operand stack
254    pub stack: Vec<JsValue>,
255    /// Call stack
256    pub call_stack: Vec<CallFrame>,
257    /// Global variables
258    pub globals: BTreeMap<String, JsValue>,
259    /// Object arena
260    pub object_arena: Vec<JsObject>,
261    /// Function templates
262    pub function_arena: Vec<FunctionTemplate>,
263    /// Try/catch stack
264    try_stack: Vec<TryState>,
265    /// Output buffer (from console.log)
266    pub output: Vec<String>,
267    /// Maximum stack depth
268    max_stack: usize,
269    /// Maximum execution steps
270    max_steps: usize,
271}
272
273impl Default for JsVm {
274    fn default() -> Self {
275        Self::new()
276    }
277}
278
279impl JsVm {
280    pub fn new() -> Self {
281        let mut vm = Self {
282            stack: Vec::with_capacity(256),
283            call_stack: Vec::new(),
284            globals: BTreeMap::new(),
285            object_arena: Vec::new(),
286            function_arena: Vec::new(),
287            try_stack: Vec::new(),
288            output: Vec::new(),
289            max_stack: 1024,
290            max_steps: 100_000,
291        };
292        vm.init_builtins();
293        vm
294    }
295
296    /// Sentinel ID for native built-in objects (high value to avoid collision
297    /// with GC heap indices)
298    const NATIVE_CONSOLE_ID: ObjectId = usize::MAX;
299
300    /// Register built-in functions and objects
301    fn init_builtins(&mut self) {
302        // Use a sentinel ID for native objects so the GC's mark phase
303        // never collides with GC-heap-allocated objects at low indices.
304        self.globals.insert(
305            "console".to_string(),
306            JsValue::Object(Self::NATIVE_CONSOLE_ID),
307        );
308    }
309
310    /// Allocate an object in the arena
311    pub fn alloc_object(&mut self, obj: JsObject) -> ObjectId {
312        let id = self.object_arena.len();
313        self.object_arena.push(obj);
314        id
315    }
316
317    /// Register a function template
318    pub fn register_function(&mut self, template: FunctionTemplate) -> FunctionId {
319        let id = self.function_arena.len();
320        self.function_arena.push(template);
321        id
322    }
323
324    /// Execute a compiled chunk (top-level code)
325    pub fn run_chunk(&mut self, chunk: &Chunk) -> Result<JsValue, JsVmError> {
326        // Create a top-level function template from the chunk
327        let template = FunctionTemplate {
328            name: "<main>".to_string(),
329            param_count: 0,
330            bytecode: chunk.bytecode.clone(),
331            constants: chunk.constants.clone(),
332            local_count: 0,
333            upvalue_count: 0,
334            line_numbers: chunk.line_numbers.clone(),
335        };
336
337        let frame = CallFrame {
338            function_id: usize::MAX,
339            ip: 0,
340            base_slot: 0,
341            locals: Vec::new(),
342            bytecode: template.bytecode,
343            constants: template.constants,
344        };
345
346        self.call_stack.push(frame);
347        self.run_loop()
348    }
349
350    /// Main interpreter loop
351    fn run_loop(&mut self) -> Result<JsValue, JsVmError> {
352        let mut steps = 0usize;
353
354        loop {
355            steps += 1;
356            if steps > self.max_steps {
357                return Err(JsVmError::ExecutionLimitExceeded);
358            }
359
360            let frame = match self.call_stack.last_mut() {
361                Some(f) => f,
362                None => {
363                    return Ok(self.stack.pop().unwrap_or(JsValue::Undefined));
364                }
365            };
366
367            if frame.ip >= frame.bytecode.len() {
368                self.call_stack.pop();
369                continue;
370            }
371
372            let op_byte = frame.bytecode[frame.ip];
373            frame.ip += 1;
374
375            let op = match Opcode::from_byte(op_byte) {
376                Some(o) => o,
377                None => {
378                    return Err(JsVmError::UnknownOpcode { byte: op_byte });
379                }
380            };
381
382            match op {
383                Opcode::Halt => {
384                    return Ok(self.stack.pop().unwrap_or(JsValue::Undefined));
385                }
386
387                Opcode::LoadConst => {
388                    let idx = self.read_u16()? as usize;
389                    let frame = self.call_stack.last().unwrap();
390                    let val = match frame.constants.get(idx) {
391                        Some(Constant::Number(n)) => JsValue::Number(*n),
392                        Some(Constant::Str(s)) => JsValue::String(s.clone()),
393                        Some(Constant::Bool(b)) => JsValue::Boolean(*b),
394                        Some(Constant::Null) => JsValue::Null,
395                        Some(Constant::Undefined) => JsValue::Undefined,
396                        Some(Constant::Function(f)) => {
397                            let fid = self.register_function(f.clone());
398                            JsValue::Function(fid)
399                        }
400                        None => JsValue::Undefined,
401                    };
402                    self.push(val)?;
403                }
404
405                Opcode::LoadLocal => {
406                    let idx = self.read_u16()? as usize;
407                    let val = self
408                        .call_stack
409                        .last()
410                        .and_then(|f| f.locals.get(idx))
411                        .cloned()
412                        .unwrap_or(JsValue::Undefined);
413                    self.push(val)?;
414                }
415
416                Opcode::StoreLocal => {
417                    let idx = self.read_u16()? as usize;
418                    let val = self.pop()?;
419                    if let Some(frame) = self.call_stack.last_mut() {
420                        while frame.locals.len() <= idx {
421                            frame.locals.push(JsValue::Undefined);
422                        }
423                        frame.locals[idx] = val;
424                    }
425                }
426
427                Opcode::LoadGlobal => {
428                    let idx = self.read_u16()? as usize;
429                    let name = self.get_string_constant(idx)?;
430                    let val = self
431                        .globals
432                        .get(&name)
433                        .cloned()
434                        .unwrap_or(JsValue::Undefined);
435                    self.push(val)?;
436                }
437
438                Opcode::StoreGlobal => {
439                    let idx = self.read_u16()? as usize;
440                    let name = self.get_string_constant(idx)?;
441                    let val = self.pop()?;
442                    self.globals.insert(name, val);
443                }
444
445                Opcode::LoadProperty => {
446                    let idx = self.read_u16()? as usize;
447                    let prop = self.get_string_constant(idx)?;
448                    let obj_val = self.pop()?;
449                    let val = self.get_property(&obj_val, &prop);
450                    self.push(val)?;
451                }
452
453                Opcode::StoreProperty => {
454                    let idx = self.read_u16()? as usize;
455                    let prop = self.get_string_constant(idx)?;
456                    let val = self.pop()?;
457                    let obj_val = self.pop()?;
458                    self.set_property(&obj_val, &prop, val);
459                }
460
461                Opcode::LoadIndex => {
462                    let index = self.pop()?;
463                    let obj = self.pop()?;
464                    let key = index.to_js_string();
465                    let val = self.get_property(&obj, &key);
466                    self.push(val)?;
467                }
468
469                Opcode::StoreIndex => {
470                    let val = self.pop()?;
471                    let index = self.pop()?;
472                    let obj = self.pop()?;
473                    let key = index.to_js_string();
474                    self.set_property(&obj, &key, val);
475                }
476
477                // Arithmetic
478                Opcode::Add => {
479                    let b = self.pop()?;
480                    let a = self.pop()?;
481                    let result = self.js_add(&a, &b);
482                    self.push(result)?;
483                }
484                Opcode::Sub => {
485                    let b = self.pop()?;
486                    let a = self.pop()?;
487                    let result = JsValue::Number(a.to_number().wrapping_sub(b.to_number()));
488                    self.push(result)?;
489                }
490                Opcode::Mul => {
491                    let b = self.pop()?;
492                    let a = self.pop()?;
493                    let result = js_mul(a.to_number(), b.to_number());
494                    self.push(JsValue::Number(result))?;
495                }
496                Opcode::Div => {
497                    let b = self.pop()?;
498                    let a = self.pop()?;
499                    let result = js_div(a.to_number(), b.to_number());
500                    self.push(JsValue::Number(result))?;
501                }
502                Opcode::Mod => {
503                    let b = self.pop()?;
504                    let a = self.pop()?;
505                    let bn = b.to_number();
506                    let result = if bn == 0 {
507                        JS_NAN
508                    } else {
509                        a.to_number().wrapping_rem(bn)
510                    };
511                    self.push(JsValue::Number(result))?;
512                }
513                Opcode::Neg => {
514                    let a = self.pop()?;
515                    self.push(JsValue::Number(-a.to_number()))?;
516                }
517                Opcode::Not => {
518                    let a = self.pop()?;
519                    self.push(JsValue::Boolean(!a.to_boolean()))?;
520                }
521
522                // Bitwise
523                Opcode::BitAnd => {
524                    let b = self.pop()?.to_number() >> JS_FRAC_BITS;
525                    let a = self.pop()?.to_number() >> JS_FRAC_BITS;
526                    self.push(JsValue::Number((a & b) << JS_FRAC_BITS))?;
527                }
528                Opcode::BitOr => {
529                    let b = self.pop()?.to_number() >> JS_FRAC_BITS;
530                    let a = self.pop()?.to_number() >> JS_FRAC_BITS;
531                    self.push(JsValue::Number((a | b) << JS_FRAC_BITS))?;
532                }
533                Opcode::BitXor => {
534                    let b = self.pop()?.to_number() >> JS_FRAC_BITS;
535                    let a = self.pop()?.to_number() >> JS_FRAC_BITS;
536                    self.push(JsValue::Number((a ^ b) << JS_FRAC_BITS))?;
537                }
538                Opcode::ShiftLeft => {
539                    let b = (self.pop()?.to_number() >> JS_FRAC_BITS) as u32;
540                    let a = self.pop()?.to_number() >> JS_FRAC_BITS;
541                    let shift = b & 31;
542                    self.push(JsValue::Number((a << shift) << JS_FRAC_BITS))?;
543                }
544                Opcode::ShiftRight => {
545                    let b = (self.pop()?.to_number() >> JS_FRAC_BITS) as u32;
546                    let a = self.pop()?.to_number() >> JS_FRAC_BITS;
547                    let shift = b & 31;
548                    self.push(JsValue::Number((a >> shift) << JS_FRAC_BITS))?;
549                }
550
551                // Comparison
552                Opcode::Eq => {
553                    let b = self.pop()?;
554                    let a = self.pop()?;
555                    self.push(JsValue::Boolean(a.abstract_eq(&b)))?;
556                }
557                Opcode::StrictEq => {
558                    let b = self.pop()?;
559                    let a = self.pop()?;
560                    self.push(JsValue::Boolean(a.strict_eq(&b)))?;
561                }
562                Opcode::NotEq => {
563                    let b = self.pop()?;
564                    let a = self.pop()?;
565                    self.push(JsValue::Boolean(!a.abstract_eq(&b)))?;
566                }
567                Opcode::StrictNotEq => {
568                    let b = self.pop()?;
569                    let a = self.pop()?;
570                    self.push(JsValue::Boolean(!a.strict_eq(&b)))?;
571                }
572                Opcode::Lt => {
573                    let b = self.pop()?.to_number();
574                    let a = self.pop()?.to_number();
575                    self.push(JsValue::Boolean(a < b))?;
576                }
577                Opcode::Gt => {
578                    let b = self.pop()?.to_number();
579                    let a = self.pop()?.to_number();
580                    self.push(JsValue::Boolean(a > b))?;
581                }
582                Opcode::LtEq => {
583                    let b = self.pop()?.to_number();
584                    let a = self.pop()?.to_number();
585                    self.push(JsValue::Boolean(a <= b))?;
586                }
587                Opcode::GtEq => {
588                    let b = self.pop()?.to_number();
589                    let a = self.pop()?.to_number();
590                    self.push(JsValue::Boolean(a >= b))?;
591                }
592
593                // Logical short-circuit
594                Opcode::LogicalAnd => {
595                    let b = self.pop()?;
596                    let a = self.pop()?;
597                    if a.to_boolean() {
598                        self.push(b)?;
599                    } else {
600                        self.push(a)?;
601                    }
602                }
603                Opcode::LogicalOr => {
604                    let b = self.pop()?;
605                    let a = self.pop()?;
606                    if a.to_boolean() {
607                        self.push(a)?;
608                    } else {
609                        self.push(b)?;
610                    }
611                }
612
613                // Control flow
614                Opcode::Jump => {
615                    let target = self.read_u16()? as usize;
616                    if let Some(frame) = self.call_stack.last_mut() {
617                        frame.ip = target;
618                    }
619                }
620                Opcode::JumpIfFalse => {
621                    let target = self.read_u16()? as usize;
622                    let val = self.pop()?;
623                    if !val.to_boolean() {
624                        if let Some(frame) = self.call_stack.last_mut() {
625                            frame.ip = target;
626                        }
627                    }
628                }
629                Opcode::JumpIfTrue => {
630                    let target = self.read_u16()? as usize;
631                    let val = self.pop()?;
632                    if val.to_boolean() {
633                        if let Some(frame) = self.call_stack.last_mut() {
634                            frame.ip = target;
635                        }
636                    }
637                }
638
639                // Functions
640                Opcode::Call => {
641                    let argc = self.read_byte()? as usize;
642                    self.call_function(argc)?;
643                }
644                Opcode::Return => {
645                    let val = self.pop()?;
646                    self.call_stack.pop();
647                    self.push(val)?;
648                }
649                Opcode::CreateClosure => {
650                    let idx = self.read_u16()? as usize;
651                    let frame = self.call_stack.last().unwrap();
652                    if let Some(Constant::Function(f)) = frame.constants.get(idx) {
653                        let fid = self.register_function(f.clone());
654                        self.push(JsValue::Function(fid))?;
655                    } else {
656                        self.push(JsValue::Undefined)?;
657                    }
658                }
659
660                // Object/array
661                Opcode::CreateObject => {
662                    let oid = self.alloc_object(JsObject::new());
663                    self.push(JsValue::Object(oid))?;
664                }
665                Opcode::CreateArray => {
666                    let count = self.read_byte()? as usize;
667                    let mut arr = JsObject::array();
668                    let start = self.stack.len().saturating_sub(count);
669                    for i in 0..count {
670                        let idx = start + i;
671                        let val = if idx < self.stack.len() {
672                            self.stack[idx].clone()
673                        } else {
674                            JsValue::Undefined
675                        };
676                        arr.set(&format!("{}", i), val);
677                    }
678                    arr.set("length", JsValue::Number((count as i64) << JS_FRAC_BITS));
679                    self.stack.truncate(start);
680                    let oid = self.alloc_object(arr);
681                    self.push(JsValue::Object(oid))?;
682                }
683
684                // Misc
685                Opcode::GetThis => {
686                    self.push(JsValue::Undefined)?; // simplified
687                }
688                Opcode::Typeof => {
689                    let val = self.pop()?;
690                    self.push(JsValue::String(val.js_typeof().to_string()))?;
691                }
692                Opcode::Instanceof | Opcode::In => {
693                    let _b = self.pop()?;
694                    let _a = self.pop()?;
695                    self.push(JsValue::Boolean(false))?; // simplified
696                }
697                Opcode::Throw => {
698                    let val = self.pop()?;
699                    if let Some(try_state) = self.try_stack.pop() {
700                        while self.call_stack.len() > try_state.call_depth {
701                            self.call_stack.pop();
702                        }
703                        self.stack.truncate(try_state.stack_depth);
704                        self.push(val)?;
705                        if let Some(frame) = self.call_stack.last_mut() {
706                            frame.ip = try_state.catch_ip;
707                        }
708                    } else {
709                        return Err(JsVmError::UncaughtException {
710                            message: val.to_js_string(),
711                        });
712                    }
713                }
714                Opcode::EnterTry => {
715                    let catch_ip = self.read_u16()? as usize;
716                    self.try_stack.push(TryState {
717                        catch_ip,
718                        stack_depth: self.stack.len(),
719                        call_depth: self.call_stack.len(),
720                    });
721                }
722                Opcode::LeaveTry => {
723                    self.try_stack.pop();
724                }
725
726                // Stack
727                Opcode::Pop => {
728                    self.pop()?;
729                }
730                Opcode::Dup => {
731                    let val = self.stack.last().cloned().unwrap_or(JsValue::Undefined);
732                    self.push(val)?;
733                }
734            }
735        }
736    }
737
738    // -- Helpers --
739
740    fn push(&mut self, val: JsValue) -> Result<(), JsVmError> {
741        if self.stack.len() >= self.max_stack {
742            return Err(JsVmError::StackOverflow);
743        }
744        self.stack.push(val);
745        Ok(())
746    }
747
748    fn pop(&mut self) -> Result<JsValue, JsVmError> {
749        self.stack.pop().ok_or(JsVmError::StackUnderflow)
750    }
751
752    fn read_byte(&mut self) -> Result<u8, JsVmError> {
753        let frame = self.call_stack.last_mut().ok_or(JsVmError::NoCallFrame)?;
754        if frame.ip >= frame.bytecode.len() {
755            return Err(JsVmError::IpOutOfBounds);
756        }
757        let b = frame.bytecode[frame.ip];
758        frame.ip += 1;
759        Ok(b)
760    }
761
762    fn read_u16(&mut self) -> Result<u16, JsVmError> {
763        let hi = self.read_byte()? as u16;
764        let lo = self.read_byte()? as u16;
765        Ok((hi << 8) | lo)
766    }
767
768    fn get_string_constant(&self, idx: usize) -> Result<String, JsVmError> {
769        let frame = self.call_stack.last().ok_or(JsVmError::NoCallFrame)?;
770        match frame.constants.get(idx) {
771            Some(Constant::Str(s)) => Ok(s.clone()),
772            _ => Ok(format!("{}", idx)),
773        }
774    }
775
776    fn get_property(&self, obj: &JsValue, key: &str) -> JsValue {
777        match obj {
778            JsValue::Object(oid) => {
779                if let Some(obj) = self.object_arena.get(*oid) {
780                    if key == "length" && obj.internal_type == ObjectType::Array {
781                        return obj.get("length");
782                    }
783                    obj.get(key)
784                } else {
785                    JsValue::Undefined
786                }
787            }
788            JsValue::String(s) => {
789                if key == "length" {
790                    JsValue::Number((s.len() as i64) << JS_FRAC_BITS)
791                } else {
792                    JsValue::Undefined
793                }
794            }
795            _ => JsValue::Undefined,
796        }
797    }
798
799    fn set_property(&mut self, obj: &JsValue, key: &str, val: JsValue) {
800        if let JsValue::Object(oid) = obj {
801            if let Some(o) = self.object_arena.get_mut(*oid) {
802                if o.internal_type == ObjectType::Array {
803                    if let Ok(idx) = key.parse::<usize>() {
804                        let current_len = match o.get("length") {
805                            JsValue::Number(n) => (n >> JS_FRAC_BITS) as usize,
806                            _ => 0,
807                        };
808                        if idx >= current_len {
809                            o.set(
810                                "length",
811                                JsValue::Number(((idx + 1) as i64) << JS_FRAC_BITS),
812                            );
813                        }
814                    }
815                }
816                o.set(key, val);
817            }
818        }
819    }
820
821    /// JavaScript addition: string concatenation or numeric add
822    fn js_add(&self, a: &JsValue, b: &JsValue) -> JsValue {
823        if matches!(a, JsValue::String(_)) || matches!(b, JsValue::String(_)) {
824            let mut s = a.to_js_string();
825            s.push_str(&b.to_js_string());
826            JsValue::String(s)
827        } else {
828            JsValue::Number(a.to_number().wrapping_add(b.to_number()))
829        }
830    }
831
832    /// Call a function value
833    fn call_function(&mut self, argc: usize) -> Result<(), JsVmError> {
834        let mut args = Vec::with_capacity(argc);
835        for _ in 0..argc {
836            args.push(self.pop()?);
837        }
838        args.reverse();
839
840        let callee = self.pop()?;
841
842        match callee {
843            JsValue::Function(fid) => {
844                let template = self
845                    .function_arena
846                    .get(fid)
847                    .cloned()
848                    .ok_or(JsVmError::InvalidFunctionId)?;
849
850                let mut locals = Vec::with_capacity(template.local_count);
851                for i in 0..template.param_count {
852                    locals.push(args.get(i).cloned().unwrap_or(JsValue::Undefined));
853                }
854                while locals.len() < template.local_count {
855                    locals.push(JsValue::Undefined);
856                }
857
858                let frame = CallFrame {
859                    function_id: fid,
860                    ip: 0,
861                    base_slot: self.stack.len(),
862                    locals,
863                    bytecode: template.bytecode.clone(),
864                    constants: template.constants.clone(),
865                };
866                self.call_stack.push(frame);
867            }
868            JsValue::Object(_oid) => {
869                // Builtin object call (console.log style)
870                let msg: Vec<String> = args.iter().map(|a| a.to_js_string()).collect();
871                self.output.push(msg.join(" "));
872                self.push(JsValue::Undefined)?;
873            }
874            _ => {
875                return Err(JsVmError::NotCallable);
876            }
877        }
878
879        Ok(())
880    }
881}
882
883// ---------------------------------------------------------------------------
884// Number helpers (32.32 fixed-point, no float)
885// ---------------------------------------------------------------------------
886
887/// Multiply two 32.32 fixed-point numbers
888fn js_mul(a: JsNumber, b: JsNumber) -> JsNumber {
889    if a == JS_NAN || b == JS_NAN {
890        return JS_NAN;
891    }
892    ((a as i128 * b as i128) >> JS_FRAC_BITS) as i64
893}
894
895/// Divide two 32.32 fixed-point numbers
896fn js_div(a: JsNumber, b: JsNumber) -> JsNumber {
897    if b == JS_ZERO || a == JS_NAN || b == JS_NAN {
898        return JS_NAN;
899    }
900    (((a as i128) << JS_FRAC_BITS) / b as i128) as i64
901}
902
903/// Parse a string to JsNumber (simplified integer-only)
904fn parse_js_number(s: &str) -> JsNumber {
905    let s = s.trim();
906    if s.is_empty() {
907        return JS_ZERO;
908    }
909    let (neg, s) = if let Some(rest) = s.strip_prefix('-') {
910        (true, rest)
911    } else {
912        (false, s)
913    };
914    let mut result: i64 = 0;
915    for &b in s.as_bytes() {
916        if b.is_ascii_digit() {
917            result = result.wrapping_mul(10).wrapping_add((b - b'0') as i64);
918        } else {
919            return JS_NAN;
920        }
921    }
922    let fixed = result << JS_FRAC_BITS;
923    if neg {
924        -fixed
925    } else {
926        fixed
927    }
928}
929
930/// Format a JsNumber as a string
931fn format_js_number(n: JsNumber) -> String {
932    if n == JS_NAN {
933        return "NaN".to_string();
934    }
935    let int_part = n >> JS_FRAC_BITS;
936    let frac_mask = (1i64 << JS_FRAC_BITS) - 1;
937    let frac_part = n & frac_mask;
938
939    if frac_part == 0 {
940        format!("{}", int_part)
941    } else {
942        let frac_decimal = (frac_part.unsigned_abs() * 1_000_000) >> JS_FRAC_BITS;
943        let frac_str = format!("{:06}", frac_decimal);
944        let trimmed = frac_str.trim_end_matches('0');
945        if int_part < 0 || (int_part == 0 && n < 0) {
946            format!("-{}.{}", int_part.unsigned_abs(), trimmed)
947        } else {
948            format!("{}.{}", int_part, trimmed)
949        }
950    }
951}
952
953// ---------------------------------------------------------------------------
954// Tests
955// ---------------------------------------------------------------------------
956
957#[cfg(test)]
958mod tests {
959    use super::*;
960    use crate::browser::{js_compiler::Compiler, js_lexer::js_int, js_parser::JsParser};
961
962    fn run_js(src: &str) -> Result<JsValue, JsVmError> {
963        let mut parser = JsParser::from_source(src);
964        let root = parser.parse();
965        let mut compiler = Compiler::new();
966        let chunk = compiler.compile(&parser.arena, root);
967        let mut vm = JsVm::new();
968        vm.run_chunk(&chunk)
969    }
970
971    fn run_js_global(src: &str, name: &str) -> JsValue {
972        let mut parser = JsParser::from_source(src);
973        let root = parser.parse();
974        let mut compiler = Compiler::new();
975        let chunk = compiler.compile(&parser.arena, root);
976        let mut vm = JsVm::new();
977        let _ = vm.run_chunk(&chunk);
978        vm.globals.get(name).cloned().unwrap_or(JsValue::Undefined)
979    }
980
981    #[test]
982    fn test_number_literal() {
983        let _result = run_js("42;").unwrap();
984    }
985
986    #[test]
987    fn test_var_and_load() {
988        let val = run_js_global("let x = 10;", "x");
989        assert!(matches!(val, JsValue::Number(n) if n == js_int(10)));
990    }
991
992    #[test]
993    fn test_addition() {
994        let val = run_js_global("let x = 1 + 2;", "x");
995        assert!(matches!(val, JsValue::Number(n) if n == js_int(3)));
996    }
997
998    #[test]
999    fn test_subtraction() {
1000        let val = run_js_global("let x = 10 - 3;", "x");
1001        assert!(matches!(val, JsValue::Number(n) if n == js_int(7)));
1002    }
1003
1004    #[test]
1005    fn test_multiplication() {
1006        let val = run_js_global("let x = 3 * 4;", "x");
1007        assert!(matches!(val, JsValue::Number(n) if n == js_int(12)));
1008    }
1009
1010    #[test]
1011    fn test_division() {
1012        let val = run_js_global("let x = 10 / 2;", "x");
1013        assert!(matches!(val, JsValue::Number(n) if n == js_int(5)));
1014    }
1015
1016    #[test]
1017    fn test_string_concat() {
1018        let val = run_js_global("let x = 'hello' + ' ' + 'world';", "x");
1019        assert!(matches!(val, JsValue::String(ref s) if s == "hello world"));
1020    }
1021
1022    #[test]
1023    fn test_comparison_lt() {
1024        let val = run_js_global("let x = 1 < 2;", "x");
1025        assert!(matches!(val, JsValue::Boolean(true)));
1026    }
1027
1028    #[test]
1029    fn test_comparison_gt() {
1030        let val = run_js_global("let x = 5 > 3;", "x");
1031        assert!(matches!(val, JsValue::Boolean(true)));
1032    }
1033
1034    #[test]
1035    fn test_strict_eq() {
1036        let val = run_js_global("let x = 1 === 1;", "x");
1037        assert!(matches!(val, JsValue::Boolean(true)));
1038    }
1039
1040    #[test]
1041    fn test_strict_neq() {
1042        let val = run_js_global("let x = 1 !== 2;", "x");
1043        assert!(matches!(val, JsValue::Boolean(true)));
1044    }
1045
1046    #[test]
1047    fn test_if_true() {
1048        let val = run_js_global("let x = 0; if (true) { x = 1; }", "x");
1049        assert!(matches!(val, JsValue::Number(n) if n == js_int(1)));
1050    }
1051
1052    #[test]
1053    fn test_if_false() {
1054        let val = run_js_global("let x = 0; if (false) { x = 1; } else { x = 2; }", "x");
1055        assert!(matches!(val, JsValue::Number(n) if n == js_int(2)));
1056    }
1057
1058    #[test]
1059    fn test_while_loop() {
1060        let val = run_js_global("let x = 0; while (x < 5) { x = x + 1; }", "x");
1061        assert!(matches!(val, JsValue::Number(n) if n == js_int(5)));
1062    }
1063
1064    #[test]
1065    fn test_for_loop() {
1066        let val = run_js_global(
1067            "let sum = 0; for (let i = 1; i < 4; i = i + 1) { sum = sum + i; }",
1068            "sum",
1069        );
1070        assert!(matches!(val, JsValue::Number(n) if n == js_int(6)));
1071    }
1072
1073    #[test]
1074    fn test_function_call() {
1075        let val = run_js_global(
1076            "function add(a, b) { return a + b; } let x = add(3, 4);",
1077            "x",
1078        );
1079        assert!(matches!(val, JsValue::Number(n) if n == js_int(7)));
1080    }
1081
1082    #[test]
1083    fn test_nested_function() {
1084        let val = run_js_global(
1085            "function f(x) { return x * 2; } function g(x) { return f(x) + 1; } let r = g(5);",
1086            "r",
1087        );
1088        assert!(matches!(val, JsValue::Number(n) if n == js_int(11)));
1089    }
1090
1091    #[test]
1092    fn test_object_property() {
1093        let val = run_js_global("let o = {x: 42}; let v = o.x;", "v");
1094        assert!(matches!(val, JsValue::Number(n) if n == js_int(42)));
1095    }
1096
1097    #[test]
1098    fn test_array_access() {
1099        let val = run_js_global("let a = [10, 20, 30]; let v = a[1];", "v");
1100        assert!(matches!(val, JsValue::Number(n) if n == js_int(20)));
1101    }
1102
1103    #[test]
1104    fn test_string_length() {
1105        let val = run_js_global("let s = 'hello'; let v = s.length;", "v");
1106        assert!(matches!(val, JsValue::Number(n) if n == js_int(5)));
1107    }
1108
1109    #[test]
1110    fn test_boolean_not() {
1111        let val = run_js_global("let x = !true;", "x");
1112        assert!(matches!(val, JsValue::Boolean(false)));
1113    }
1114
1115    #[test]
1116    fn test_negation() {
1117        let val = run_js_global("let x = -5;", "x");
1118        assert!(matches!(val, JsValue::Number(n) if n == js_int(-5)));
1119    }
1120
1121    #[test]
1122    fn test_typeof() {
1123        let val = run_js_global("let x = typeof 42;", "x");
1124        assert!(matches!(val, JsValue::String(ref s) if s == "number"));
1125    }
1126
1127    #[test]
1128    fn test_null_undefined() {
1129        let val = run_js_global("let x = null;", "x");
1130        assert!(matches!(val, JsValue::Null));
1131        let val2 = run_js_global("let x = undefined;", "x");
1132        assert!(matches!(val2, JsValue::Undefined));
1133    }
1134
1135    #[test]
1136    fn test_logical_and() {
1137        let val = run_js_global("let x = true && 42;", "x");
1138        assert!(matches!(val, JsValue::Number(n) if n == js_int(42)));
1139    }
1140
1141    #[test]
1142    fn test_logical_or() {
1143        let val = run_js_global("let x = false || 'fallback';", "x");
1144        assert!(matches!(val, JsValue::String(ref s) if s == "fallback"));
1145    }
1146
1147    #[test]
1148    fn test_compound_assign() {
1149        let val = run_js_global("let x = 5; x += 3;", "x");
1150        assert!(matches!(val, JsValue::Number(n) if n == js_int(8)));
1151    }
1152
1153    #[test]
1154    fn test_js_value_truthiness() {
1155        assert!(!JsValue::Undefined.to_boolean());
1156        assert!(!JsValue::Null.to_boolean());
1157        assert!(!JsValue::Boolean(false).to_boolean());
1158        assert!(!JsValue::Number(JS_ZERO).to_boolean());
1159        assert!(!JsValue::String(String::new()).to_boolean());
1160        assert!(JsValue::Boolean(true).to_boolean());
1161        assert!(JsValue::Number(JS_ONE).to_boolean());
1162        assert!(JsValue::String("x".to_string()).to_boolean());
1163    }
1164
1165    #[test]
1166    fn test_js_value_to_string() {
1167        assert_eq!(JsValue::Undefined.to_js_string(), "undefined");
1168        assert_eq!(JsValue::Null.to_js_string(), "null");
1169        assert_eq!(JsValue::Boolean(true).to_js_string(), "true");
1170        assert_eq!(JsValue::Number(js_int(42)).to_js_string(), "42");
1171    }
1172
1173    #[test]
1174    fn test_abstract_eq_null_undefined() {
1175        assert!(JsValue::Null.abstract_eq(&JsValue::Undefined));
1176        assert!(JsValue::Undefined.abstract_eq(&JsValue::Null));
1177    }
1178
1179    #[test]
1180    fn test_js_mul_overflow_safe() {
1181        let r = js_mul(js_int(1000), js_int(1000));
1182        assert_eq!(r >> JS_FRAC_BITS, 1_000_000);
1183    }
1184
1185    #[test]
1186    fn test_js_div_by_zero() {
1187        assert_eq!(js_div(js_int(1), JS_ZERO), JS_NAN);
1188    }
1189
1190    #[test]
1191    fn test_format_js_number_integer() {
1192        assert_eq!(format_js_number(js_int(42)), "42");
1193        assert_eq!(format_js_number(js_int(-5)), "-5");
1194        assert_eq!(format_js_number(js_int(0)), "0");
1195    }
1196
1197    #[test]
1198    fn test_format_js_number_nan() {
1199        assert_eq!(format_js_number(JS_NAN), "NaN");
1200    }
1201
1202    #[test]
1203    fn test_parse_js_number() {
1204        assert_eq!(parse_js_number("42"), js_int(42));
1205        assert_eq!(parse_js_number("-10"), js_int(-10));
1206        assert_eq!(parse_js_number(""), JS_ZERO);
1207        assert_eq!(parse_js_number("abc"), JS_NAN);
1208    }
1209
1210    #[test]
1211    fn test_execution_limit() {
1212        let result = run_js("while (true) { }");
1213        assert!(result.is_err());
1214    }
1215
1216    #[test]
1217    fn test_object_type_default() {
1218        assert_eq!(ObjectType::default(), ObjectType::Ordinary);
1219    }
1220
1221    #[test]
1222    fn test_js_object_array() {
1223        let arr = JsObject::array();
1224        assert_eq!(arr.internal_type, ObjectType::Array);
1225    }
1226
1227    #[test]
1228    fn test_recursive_function() {
1229        let val = run_js_global(
1230            "function fib(n) { if (n < 2) { return n; } return fib(n - 1) + fib(n - 2); } let x = \
1231             fib(6);",
1232            "x",
1233        );
1234        assert!(matches!(val, JsValue::Number(n) if n == js_int(8)));
1235    }
1236
1237    #[test]
1238    fn test_multiple_vars() {
1239        run_js_global("let a = 1; let b = 2; let c = 3;", "c");
1240    }
1241
1242    #[test]
1243    fn test_empty_function() {
1244        run_js_global("function f() {} let x = f();", "x");
1245    }
1246
1247    #[test]
1248    fn test_return_no_value() {
1249        let val = run_js_global("function f() { return; } let x = f();", "x");
1250        assert!(matches!(val, JsValue::Undefined));
1251    }
1252
1253    #[test]
1254    fn test_bitwise_and() {
1255        let val = run_js_global("let x = 6 & 3;", "x");
1256        assert!(matches!(val, JsValue::Number(n) if n == js_int(2)));
1257    }
1258
1259    #[test]
1260    fn test_bitwise_or() {
1261        let val = run_js_global("let x = 4 | 2;", "x");
1262        assert!(matches!(val, JsValue::Number(n) if n == js_int(6)));
1263    }
1264
1265    #[test]
1266    fn test_modulo() {
1267        let val = run_js_global("let x = 10 % 3;", "x");
1268        assert!(matches!(val, JsValue::Number(n) if n == js_int(1)));
1269    }
1270}