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

veridian_kernel/browser/
js_compiler.rs

1//! JavaScript Bytecode Compiler
2//!
3//! Compiles AST nodes into bytecode for the JS virtual machine. Uses a
4//! stack-based instruction set with ~40 opcodes. Functions are compiled
5//! into FunctionTemplates with their own constant pools.
6
7#![allow(dead_code)]
8
9use alloc::{
10    string::{String, ToString},
11    vec::Vec,
12};
13
14use super::{
15    js_lexer::JsNumber,
16    js_parser::{AstArena, AstNode, AstNodeId, BinOp, UnaryOp},
17};
18
19// ---------------------------------------------------------------------------
20// Opcodes
21// ---------------------------------------------------------------------------
22
23/// Bytecode opcodes (single-byte encoding)
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[repr(u8)]
26pub enum Opcode {
27    /// Push constant from pool onto stack
28    LoadConst = 0,
29    /// Push local variable onto stack
30    LoadLocal = 1,
31    /// Pop stack into local variable
32    StoreLocal = 2,
33    /// Push global variable onto stack
34    LoadGlobal = 3,
35    /// Pop stack into global variable
36    StoreGlobal = 4,
37    /// Pop object+key, push property value
38    LoadProperty = 5,
39    /// Pop object+key+value, set property
40    StoreProperty = 6,
41    /// Pop array+index, push element
42    LoadIndex = 7,
43    /// Pop array+index+value, set element
44    StoreIndex = 8,
45
46    // Arithmetic
47    Add = 10,
48    Sub = 11,
49    Mul = 12,
50    Div = 13,
51    Mod = 14,
52    Neg = 15,
53
54    // Logic / bitwise
55    Not = 20,
56    BitAnd = 21,
57    BitOr = 22,
58    BitXor = 23,
59    ShiftLeft = 24,
60    ShiftRight = 25,
61
62    // Comparison
63    Eq = 30,
64    StrictEq = 31,
65    NotEq = 32,
66    StrictNotEq = 33,
67    Lt = 34,
68    Gt = 35,
69    LtEq = 36,
70    GtEq = 37,
71
72    // Logical short-circuit
73    LogicalAnd = 38,
74    LogicalOr = 39,
75
76    // Control flow
77    Jump = 40,
78    JumpIfFalse = 41,
79    JumpIfTrue = 42,
80
81    // Functions
82    Call = 50,
83    Return = 51,
84    CreateClosure = 52,
85
86    // Object/array
87    CreateObject = 60,
88    CreateArray = 61,
89
90    // Misc
91    GetThis = 70,
92    Typeof = 71,
93    Instanceof = 72,
94    In = 73,
95    Throw = 74,
96    EnterTry = 75,
97    LeaveTry = 76,
98
99    // Stack
100    Pop = 80,
101    Dup = 81,
102
103    // Halt
104    Halt = 255,
105}
106
107impl Opcode {
108    /// Decode from byte
109    pub fn from_byte(b: u8) -> Option<Self> {
110        match b {
111            0 => Some(Self::LoadConst),
112            1 => Some(Self::LoadLocal),
113            2 => Some(Self::StoreLocal),
114            3 => Some(Self::LoadGlobal),
115            4 => Some(Self::StoreGlobal),
116            5 => Some(Self::LoadProperty),
117            6 => Some(Self::StoreProperty),
118            7 => Some(Self::LoadIndex),
119            8 => Some(Self::StoreIndex),
120            10 => Some(Self::Add),
121            11 => Some(Self::Sub),
122            12 => Some(Self::Mul),
123            13 => Some(Self::Div),
124            14 => Some(Self::Mod),
125            15 => Some(Self::Neg),
126            20 => Some(Self::Not),
127            21 => Some(Self::BitAnd),
128            22 => Some(Self::BitOr),
129            23 => Some(Self::BitXor),
130            24 => Some(Self::ShiftLeft),
131            25 => Some(Self::ShiftRight),
132            30 => Some(Self::Eq),
133            31 => Some(Self::StrictEq),
134            32 => Some(Self::NotEq),
135            33 => Some(Self::StrictNotEq),
136            34 => Some(Self::Lt),
137            35 => Some(Self::Gt),
138            36 => Some(Self::LtEq),
139            37 => Some(Self::GtEq),
140            38 => Some(Self::LogicalAnd),
141            39 => Some(Self::LogicalOr),
142            40 => Some(Self::Jump),
143            41 => Some(Self::JumpIfFalse),
144            42 => Some(Self::JumpIfTrue),
145            50 => Some(Self::Call),
146            51 => Some(Self::Return),
147            52 => Some(Self::CreateClosure),
148            60 => Some(Self::CreateObject),
149            61 => Some(Self::CreateArray),
150            70 => Some(Self::GetThis),
151            71 => Some(Self::Typeof),
152            72 => Some(Self::Instanceof),
153            73 => Some(Self::In),
154            74 => Some(Self::Throw),
155            75 => Some(Self::EnterTry),
156            76 => Some(Self::LeaveTry),
157            80 => Some(Self::Pop),
158            81 => Some(Self::Dup),
159            255 => Some(Self::Halt),
160            _ => None,
161        }
162    }
163}
164
165// ---------------------------------------------------------------------------
166// Constants and templates
167// ---------------------------------------------------------------------------
168
169/// Constant pool entry
170#[derive(Debug, Clone)]
171pub enum Constant {
172    Number(JsNumber),
173    Str(String),
174    Bool(bool),
175    Null,
176    Undefined,
177    Function(FunctionTemplate),
178}
179
180/// Compiled function template
181#[derive(Debug, Clone, Default)]
182pub struct FunctionTemplate {
183    /// Function name (empty for anonymous)
184    pub name: String,
185    /// Number of parameters
186    pub param_count: usize,
187    /// Bytecode
188    pub bytecode: Vec<u8>,
189    /// Constant pool
190    pub constants: Vec<Constant>,
191    /// Number of local variables
192    pub local_count: usize,
193    /// Number of upvalues (captured variables)
194    pub upvalue_count: usize,
195    /// Source line numbers (one per bytecode byte, for debugging)
196    pub line_numbers: Vec<u32>,
197}
198
199/// Bytecode chunk (top-level compilation unit)
200#[derive(Debug, Clone, Default)]
201pub struct Chunk {
202    pub bytecode: Vec<u8>,
203    pub constants: Vec<Constant>,
204    pub line_numbers: Vec<u32>,
205}
206
207// ---------------------------------------------------------------------------
208// Local variable tracking
209// ---------------------------------------------------------------------------
210
211#[derive(Debug, Clone)]
212struct Local {
213    name: String,
214    depth: u32,
215    index: u16,
216}
217
218// ---------------------------------------------------------------------------
219// Compiler
220// ---------------------------------------------------------------------------
221
222/// Bytecode compiler
223pub struct Compiler {
224    /// Current chunk being compiled
225    chunk: Chunk,
226    /// Scope depth (0 = global)
227    scope_depth: u32,
228    /// Local variables
229    locals: Vec<Local>,
230    /// Next local index
231    next_local: u16,
232    /// Current source line
233    current_line: u32,
234    /// Nested function templates
235    functions: Vec<FunctionTemplate>,
236    /// Loop break patch points
237    break_patches: Vec<Vec<usize>>,
238    /// Loop continue targets
239    continue_targets: Vec<usize>,
240}
241
242impl Default for Compiler {
243    fn default() -> Self {
244        Self::new()
245    }
246}
247
248impl Compiler {
249    pub fn new() -> Self {
250        Self {
251            chunk: Chunk::default(),
252            scope_depth: 0,
253            locals: Vec::new(),
254            next_local: 0,
255            current_line: 1,
256            functions: Vec::new(),
257            break_patches: Vec::new(),
258            continue_targets: Vec::new(),
259        }
260    }
261
262    /// Compile an AST into a Chunk
263    pub fn compile(&mut self, arena: &AstArena, root: AstNodeId) -> Chunk {
264        self.compile_node(arena, root);
265        self.emit_op(Opcode::Halt);
266        core::mem::take(&mut self.chunk)
267    }
268
269    /// Compile an AST into a FunctionTemplate
270    pub fn compile_function(
271        &mut self,
272        arena: &AstArena,
273        name: &str,
274        params: &[String],
275        body: AstNodeId,
276    ) -> FunctionTemplate {
277        let prev_chunk = core::mem::take(&mut self.chunk);
278        let prev_locals = core::mem::take(&mut self.locals);
279        let prev_next_local = self.next_local;
280        let prev_depth = self.scope_depth;
281
282        self.scope_depth = 1;
283        self.next_local = 0;
284        self.locals.clear();
285
286        // Define parameters as locals
287        for param in params {
288            self.define_local(param);
289        }
290
291        self.compile_node(arena, body);
292        // Implicit return undefined
293        let undef_idx = self.add_constant(Constant::Undefined);
294        self.emit_op(Opcode::LoadConst);
295        self.emit_u16(undef_idx);
296        self.emit_op(Opcode::Return);
297
298        let func = FunctionTemplate {
299            name: name.to_string(),
300            param_count: params.len(),
301            bytecode: core::mem::take(&mut self.chunk.bytecode),
302            constants: core::mem::take(&mut self.chunk.constants),
303            local_count: self.next_local as usize,
304            upvalue_count: 0,
305            line_numbers: core::mem::take(&mut self.chunk.line_numbers),
306        };
307
308        self.chunk = prev_chunk;
309        self.locals = prev_locals;
310        self.next_local = prev_next_local;
311        self.scope_depth = prev_depth;
312
313        func
314    }
315
316    // -- Compilation --
317
318    fn compile_node(&mut self, arena: &AstArena, id: AstNodeId) {
319        let node = match arena.get(id) {
320            Some(n) => n.clone(),
321            None => return,
322        };
323
324        match node {
325            AstNode::Program(stmts) => {
326                for stmt in &stmts {
327                    self.compile_node(arena, *stmt);
328                }
329            }
330
331            AstNode::VarDecl {
332                name,
333                init,
334                kind: _,
335            } => {
336                if self.scope_depth > 0 {
337                    let idx = self.define_local(&name);
338                    if let Some(init_id) = init {
339                        self.compile_node(arena, init_id);
340                        self.emit_op(Opcode::StoreLocal);
341                        self.emit_u16(idx);
342                    }
343                } else {
344                    if let Some(init_id) = init {
345                        self.compile_node(arena, init_id);
346                    } else {
347                        let c = self.add_constant(Constant::Undefined);
348                        self.emit_op(Opcode::LoadConst);
349                        self.emit_u16(c);
350                    }
351                    let name_idx = self.add_constant(Constant::Str(name));
352                    self.emit_op(Opcode::StoreGlobal);
353                    self.emit_u16(name_idx);
354                }
355            }
356
357            AstNode::FuncDecl { name, params, body } => {
358                let func = self.compile_function(arena, &name, &params, body);
359                let func_idx = self.add_constant(Constant::Function(func));
360                self.emit_op(Opcode::LoadConst);
361                self.emit_u16(func_idx);
362                if self.scope_depth > 0 {
363                    let local = self.define_local(&name);
364                    self.emit_op(Opcode::StoreLocal);
365                    self.emit_u16(local);
366                } else {
367                    let name_idx = self.add_constant(Constant::Str(name));
368                    self.emit_op(Opcode::StoreGlobal);
369                    self.emit_u16(name_idx);
370                }
371            }
372
373            AstNode::Return(value) => {
374                if let Some(val_id) = value {
375                    self.compile_node(arena, val_id);
376                } else {
377                    let c = self.add_constant(Constant::Undefined);
378                    self.emit_op(Opcode::LoadConst);
379                    self.emit_u16(c);
380                }
381                self.emit_op(Opcode::Return);
382            }
383
384            AstNode::If {
385                condition,
386                then_branch,
387                else_branch,
388            } => {
389                self.compile_node(arena, condition);
390                let else_jump = self.emit_jump(Opcode::JumpIfFalse);
391                self.compile_node(arena, then_branch);
392                if let Some(else_id) = else_branch {
393                    let end_jump = self.emit_jump(Opcode::Jump);
394                    self.patch_jump(else_jump);
395                    self.compile_node(arena, else_id);
396                    self.patch_jump(end_jump);
397                } else {
398                    self.patch_jump(else_jump);
399                }
400            }
401
402            AstNode::While { condition, body } => {
403                let loop_start = self.chunk.bytecode.len();
404                self.continue_targets.push(loop_start);
405                self.break_patches.push(Vec::new());
406
407                self.compile_node(arena, condition);
408                let exit_jump = self.emit_jump(Opcode::JumpIfFalse);
409                self.compile_node(arena, body);
410                self.emit_loop(loop_start);
411                self.patch_jump(exit_jump);
412
413                let breaks = self.break_patches.pop().unwrap_or_default();
414                for bp in breaks {
415                    self.patch_jump(bp);
416                }
417                self.continue_targets.pop();
418            }
419
420            AstNode::For {
421                init,
422                condition,
423                update,
424                body,
425            } => {
426                if let Some(init_id) = init {
427                    self.compile_node(arena, init_id);
428                }
429                let loop_start = self.chunk.bytecode.len();
430                self.continue_targets.push(loop_start);
431                self.break_patches.push(Vec::new());
432
433                let exit_jump = if let Some(cond_id) = condition {
434                    self.compile_node(arena, cond_id);
435                    Some(self.emit_jump(Opcode::JumpIfFalse))
436                } else {
437                    None
438                };
439
440                self.compile_node(arena, body);
441
442                if let Some(upd_id) = update {
443                    self.compile_node(arena, upd_id);
444                    self.emit_op(Opcode::Pop);
445                }
446
447                self.emit_loop(loop_start);
448
449                if let Some(ej) = exit_jump {
450                    self.patch_jump(ej);
451                }
452
453                let breaks = self.break_patches.pop().unwrap_or_default();
454                for bp in breaks {
455                    self.patch_jump(bp);
456                }
457                self.continue_targets.pop();
458            }
459
460            AstNode::Block(stmts) => {
461                self.scope_depth += 1;
462                for stmt in &stmts {
463                    self.compile_node(arena, *stmt);
464                }
465                self.scope_depth -= 1;
466                // Pop locals from this scope
467                while let Some(local) = self.locals.last() {
468                    if local.depth > self.scope_depth {
469                        self.locals.pop();
470                    } else {
471                        break;
472                    }
473                }
474            }
475
476            AstNode::ExprStatement(expr) => {
477                self.compile_node(arena, expr);
478                self.emit_op(Opcode::Pop);
479            }
480
481            AstNode::BinaryExpr { op, left, right } => {
482                self.compile_node(arena, left);
483                self.compile_node(arena, right);
484                let opcode = match op {
485                    BinOp::Add => Opcode::Add,
486                    BinOp::Sub => Opcode::Sub,
487                    BinOp::Mul => Opcode::Mul,
488                    BinOp::Div => Opcode::Div,
489                    BinOp::Mod => Opcode::Mod,
490                    BinOp::Eq => Opcode::Eq,
491                    BinOp::StrictEq => Opcode::StrictEq,
492                    BinOp::NotEq => Opcode::NotEq,
493                    BinOp::StrictNotEq => Opcode::StrictNotEq,
494                    BinOp::Lt => Opcode::Lt,
495                    BinOp::Gt => Opcode::Gt,
496                    BinOp::LtEq => Opcode::LtEq,
497                    BinOp::GtEq => Opcode::GtEq,
498                    BinOp::And => Opcode::LogicalAnd,
499                    BinOp::Or => Opcode::LogicalOr,
500                    BinOp::BitAnd => Opcode::BitAnd,
501                    BinOp::BitOr => Opcode::BitOr,
502                    BinOp::BitXor => Opcode::BitXor,
503                    BinOp::ShiftLeft => Opcode::ShiftLeft,
504                    BinOp::ShiftRight => Opcode::ShiftRight,
505                    BinOp::Instanceof => Opcode::Instanceof,
506                    BinOp::In => Opcode::In,
507                };
508                self.emit_op(opcode);
509            }
510
511            AstNode::UnaryExpr { op, operand } => {
512                self.compile_node(arena, operand);
513                match op {
514                    UnaryOp::Neg => self.emit_op(Opcode::Neg),
515                    UnaryOp::Not => self.emit_op(Opcode::Not),
516                    UnaryOp::Typeof => self.emit_op(Opcode::Typeof),
517                    UnaryOp::Void => {
518                        self.emit_op(Opcode::Pop);
519                        let c = self.add_constant(Constant::Undefined);
520                        self.emit_op(Opcode::LoadConst);
521                        self.emit_u16(c);
522                    }
523                    UnaryOp::Delete => self.emit_op(Opcode::Pop), // simplified
524                    UnaryOp::BitNot => self.emit_op(Opcode::BitXor), // simplified
525                }
526            }
527
528            AstNode::AssignExpr { target, value } => {
529                self.compile_node(arena, value);
530                self.emit_op(Opcode::Dup);
531                self.compile_store(arena, target);
532            }
533
534            AstNode::CompoundAssign { op, target, value } => {
535                self.compile_load(arena, target);
536                self.compile_node(arena, value);
537                let opcode = match op {
538                    BinOp::Add => Opcode::Add,
539                    BinOp::Sub => Opcode::Sub,
540                    BinOp::Mul => Opcode::Mul,
541                    BinOp::Div => Opcode::Div,
542                    _ => Opcode::Add,
543                };
544                self.emit_op(opcode);
545                self.emit_op(Opcode::Dup);
546                self.compile_store(arena, target);
547            }
548
549            AstNode::CallExpr { callee, args } => {
550                self.compile_node(arena, callee);
551                let argc = args.len();
552                for arg in &args {
553                    self.compile_node(arena, *arg);
554                }
555                self.emit_op(Opcode::Call);
556                self.emit_byte(argc as u8);
557            }
558
559            AstNode::MemberExpr { object, property } => {
560                self.compile_node(arena, object);
561                let prop_idx = self.add_constant(Constant::Str(property));
562                self.emit_op(Opcode::LoadProperty);
563                self.emit_u16(prop_idx);
564            }
565
566            AstNode::IndexExpr { object, index } => {
567                self.compile_node(arena, object);
568                self.compile_node(arena, index);
569                self.emit_op(Opcode::LoadIndex);
570            }
571
572            AstNode::ObjectLiteral(props) => {
573                self.emit_op(Opcode::CreateObject);
574                for (key, val_id) in &props {
575                    self.emit_op(Opcode::Dup);
576                    self.compile_node(arena, *val_id);
577                    let key_idx = self.add_constant(Constant::Str(key.clone()));
578                    self.emit_op(Opcode::StoreProperty);
579                    self.emit_u16(key_idx);
580                }
581            }
582
583            AstNode::ArrayLiteral(elems) => {
584                let count = elems.len();
585                for elem in &elems {
586                    self.compile_node(arena, *elem);
587                }
588                self.emit_op(Opcode::CreateArray);
589                self.emit_byte(count as u8);
590            }
591
592            AstNode::NumberLit(n) => {
593                let idx = self.add_constant(Constant::Number(n));
594                self.emit_op(Opcode::LoadConst);
595                self.emit_u16(idx);
596            }
597            AstNode::StringLit(s) => {
598                let idx = self.add_constant(Constant::Str(s));
599                self.emit_op(Opcode::LoadConst);
600                self.emit_u16(idx);
601            }
602            AstNode::BoolLit(b) => {
603                let idx = self.add_constant(Constant::Bool(b));
604                self.emit_op(Opcode::LoadConst);
605                self.emit_u16(idx);
606            }
607            AstNode::NullLit => {
608                let idx = self.add_constant(Constant::Null);
609                self.emit_op(Opcode::LoadConst);
610                self.emit_u16(idx);
611            }
612            AstNode::UndefinedLit => {
613                let idx = self.add_constant(Constant::Undefined);
614                self.emit_op(Opcode::LoadConst);
615                self.emit_u16(idx);
616            }
617
618            AstNode::Identifier(name) => {
619                if let Some(idx) = self.resolve_local(&name) {
620                    self.emit_op(Opcode::LoadLocal);
621                    self.emit_u16(idx);
622                } else {
623                    let name_idx = self.add_constant(Constant::Str(name));
624                    self.emit_op(Opcode::LoadGlobal);
625                    self.emit_u16(name_idx);
626                }
627            }
628
629            AstNode::This => {
630                self.emit_op(Opcode::GetThis);
631            }
632
633            AstNode::FuncExpr { params, body } | AstNode::ArrowFunc { params, body } => {
634                let func = self.compile_function(arena, "", &params, body);
635                let idx = self.add_constant(Constant::Function(func));
636                self.emit_op(Opcode::CreateClosure);
637                self.emit_u16(idx);
638            }
639
640            AstNode::NewExpr { callee, args } => {
641                self.compile_node(arena, callee);
642                let argc = args.len();
643                for arg in &args {
644                    self.compile_node(arena, *arg);
645                }
646                self.emit_op(Opcode::Call);
647                self.emit_byte(argc as u8);
648            }
649
650            AstNode::TypeofExpr(operand) => {
651                self.compile_node(arena, operand);
652                self.emit_op(Opcode::Typeof);
653            }
654
655            AstNode::Throw(expr) => {
656                self.compile_node(arena, expr);
657                self.emit_op(Opcode::Throw);
658            }
659
660            AstNode::TryCatch {
661                try_body,
662                catch_body,
663                finally_body,
664                catch_param: _,
665            } => {
666                let try_jump = self.emit_jump(Opcode::EnterTry);
667                self.compile_node(arena, try_body);
668                self.emit_op(Opcode::LeaveTry);
669                let end_jump = self.emit_jump(Opcode::Jump);
670                self.patch_jump(try_jump);
671                if let Some(catch_id) = catch_body {
672                    self.compile_node(arena, catch_id);
673                }
674                self.patch_jump(end_jump);
675                if let Some(finally_id) = finally_body {
676                    self.compile_node(arena, finally_id);
677                }
678            }
679
680            AstNode::Break => {
681                let jump = self.emit_jump(Opcode::Jump);
682                if let Some(patches) = self.break_patches.last_mut() {
683                    patches.push(jump);
684                }
685            }
686
687            AstNode::Continue => {
688                if let Some(&target) = self.continue_targets.last() {
689                    self.emit_loop(target);
690                }
691            }
692
693            AstNode::Empty => {}
694        }
695    }
696
697    /// Compile a load from an assignment target
698    fn compile_load(&mut self, arena: &AstArena, id: AstNodeId) {
699        self.compile_node(arena, id);
700    }
701
702    /// Compile a store to an assignment target
703    fn compile_store(&mut self, arena: &AstArena, target: AstNodeId) {
704        match arena.get(target) {
705            Some(AstNode::Identifier(name)) => {
706                if let Some(idx) = self.resolve_local(name) {
707                    self.emit_op(Opcode::StoreLocal);
708                    self.emit_u16(idx);
709                } else {
710                    let name_idx = self.add_constant(Constant::Str(name.clone()));
711                    self.emit_op(Opcode::StoreGlobal);
712                    self.emit_u16(name_idx);
713                }
714            }
715            Some(AstNode::MemberExpr { object, property }) => {
716                let object = *object;
717                let property = property.clone();
718                self.compile_node(arena, object);
719                let prop_idx = self.add_constant(Constant::Str(property));
720                self.emit_op(Opcode::StoreProperty);
721                self.emit_u16(prop_idx);
722            }
723            _ => {
724                // Cannot store to this target; discard value
725                self.emit_op(Opcode::Pop);
726            }
727        }
728    }
729
730    // -- Local variable management --
731
732    fn define_local(&mut self, name: &str) -> u16 {
733        let idx = self.next_local;
734        self.locals.push(Local {
735            name: name.to_string(),
736            depth: self.scope_depth,
737            index: idx,
738        });
739        self.next_local += 1;
740        idx
741    }
742
743    fn resolve_local(&self, name: &str) -> Option<u16> {
744        for local in self.locals.iter().rev() {
745            if local.name == name {
746                return Some(local.index);
747            }
748        }
749        None
750    }
751
752    // -- Bytecode emission --
753
754    fn emit_op(&mut self, op: Opcode) {
755        self.chunk.bytecode.push(op as u8);
756        self.chunk.line_numbers.push(self.current_line);
757    }
758
759    fn emit_byte(&mut self, byte: u8) {
760        self.chunk.bytecode.push(byte);
761        self.chunk.line_numbers.push(self.current_line);
762    }
763
764    fn emit_u16(&mut self, val: u16) {
765        self.chunk.bytecode.push((val >> 8) as u8);
766        self.chunk.bytecode.push(val as u8);
767        self.chunk.line_numbers.push(self.current_line);
768        self.chunk.line_numbers.push(self.current_line);
769    }
770
771    fn emit_jump(&mut self, op: Opcode) -> usize {
772        self.emit_op(op);
773        let offset = self.chunk.bytecode.len();
774        self.emit_u16(0xFFFF); // placeholder
775        offset
776    }
777
778    fn patch_jump(&mut self, offset: usize) {
779        let target = self.chunk.bytecode.len() as u16;
780        self.chunk.bytecode[offset] = (target >> 8) as u8;
781        self.chunk.bytecode[offset + 1] = target as u8;
782    }
783
784    fn emit_loop(&mut self, target: usize) {
785        self.emit_op(Opcode::Jump);
786        self.emit_u16(target as u16);
787    }
788
789    fn add_constant(&mut self, constant: Constant) -> u16 {
790        let idx = self.chunk.constants.len();
791        self.chunk.constants.push(constant);
792        idx as u16
793    }
794}
795
796// ---------------------------------------------------------------------------
797// Tests
798// ---------------------------------------------------------------------------
799
800#[cfg(test)]
801mod tests {
802    use super::*;
803    use crate::browser::{js_lexer::js_int, js_parser::JsParser};
804
805    fn compile_src(src: &str) -> Chunk {
806        let mut parser = JsParser::from_source(src);
807        let root = parser.parse();
808        let mut compiler = Compiler::new();
809        compiler.compile(&parser.arena, root)
810    }
811
812    #[test]
813    fn test_empty_program() {
814        let chunk = compile_src("");
815        // Should just have Halt
816        assert_eq!(*chunk.bytecode.last().unwrap(), Opcode::Halt as u8);
817    }
818
819    #[test]
820    fn test_number_literal() {
821        let chunk = compile_src("42;");
822        assert!(chunk.bytecode.contains(&(Opcode::LoadConst as u8)));
823        assert!(chunk
824            .constants
825            .iter()
826            .any(|c| matches!(c, Constant::Number(n) if *n == js_int(42))));
827    }
828
829    #[test]
830    fn test_string_literal() {
831        let chunk = compile_src("\"hello\";");
832        assert!(chunk
833            .constants
834            .iter()
835            .any(|c| matches!(c, Constant::Str(s) if s == "hello")));
836    }
837
838    #[test]
839    fn test_var_decl_global() {
840        let chunk = compile_src("let x = 10;");
841        assert!(chunk.bytecode.contains(&(Opcode::StoreGlobal as u8)));
842    }
843
844    #[test]
845    fn test_binary_add() {
846        let chunk = compile_src("1 + 2;");
847        assert!(chunk.bytecode.contains(&(Opcode::Add as u8)));
848    }
849
850    #[test]
851    fn test_binary_mul() {
852        let chunk = compile_src("3 * 4;");
853        assert!(chunk.bytecode.contains(&(Opcode::Mul as u8)));
854    }
855
856    #[test]
857    fn test_unary_neg() {
858        let chunk = compile_src("-5;");
859        assert!(chunk.bytecode.contains(&(Opcode::Neg as u8)));
860    }
861
862    #[test]
863    fn test_if_statement() {
864        let chunk = compile_src("if (true) { 1; }");
865        assert!(chunk.bytecode.contains(&(Opcode::JumpIfFalse as u8)));
866    }
867
868    #[test]
869    fn test_if_else() {
870        let chunk = compile_src("if (false) { 1; } else { 2; }");
871        let jumps = chunk
872            .bytecode
873            .iter()
874            .filter(|&&b| b == Opcode::JumpIfFalse as u8 || b == Opcode::Jump as u8)
875            .count();
876        assert!(jumps >= 2);
877    }
878
879    #[test]
880    fn test_while_loop() {
881        let chunk = compile_src("while (true) { 1; }");
882        assert!(chunk.bytecode.contains(&(Opcode::JumpIfFalse as u8)));
883        assert!(chunk.bytecode.contains(&(Opcode::Jump as u8)));
884    }
885
886    #[test]
887    fn test_function_decl() {
888        let chunk = compile_src("function add(a, b) { return a + b; }");
889        let has_func = chunk
890            .constants
891            .iter()
892            .any(|c| matches!(c, Constant::Function(f) if f.name == "add" && f.param_count == 2));
893        assert!(has_func);
894    }
895
896    #[test]
897    fn test_function_call() {
898        let chunk = compile_src("foo(1);");
899        assert!(chunk.bytecode.contains(&(Opcode::Call as u8)));
900    }
901
902    #[test]
903    fn test_member_access() {
904        let chunk = compile_src("obj.x;");
905        assert!(chunk.bytecode.contains(&(Opcode::LoadProperty as u8)));
906    }
907
908    #[test]
909    fn test_index_access() {
910        let chunk = compile_src("arr[0];");
911        assert!(chunk.bytecode.contains(&(Opcode::LoadIndex as u8)));
912    }
913
914    #[test]
915    fn test_object_literal() {
916        let chunk = compile_src("let o = {a: 1};");
917        assert!(chunk.bytecode.contains(&(Opcode::CreateObject as u8)));
918    }
919
920    #[test]
921    fn test_array_literal() {
922        let chunk = compile_src("let a = [1, 2];");
923        assert!(chunk.bytecode.contains(&(Opcode::CreateArray as u8)));
924    }
925
926    #[test]
927    fn test_assignment() {
928        let chunk = compile_src("x = 5;");
929        assert!(chunk.bytecode.contains(&(Opcode::StoreGlobal as u8)));
930    }
931
932    #[test]
933    fn test_return_value() {
934        let chunk = compile_src("function f() { return 42; }");
935        let has_func = chunk.constants.iter().any(
936            |c| matches!(c, Constant::Function(f) if f.bytecode.contains(&(Opcode::Return as u8))),
937        );
938        assert!(has_func);
939    }
940
941    #[test]
942    fn test_comparison() {
943        let chunk = compile_src("1 < 2;");
944        assert!(chunk.bytecode.contains(&(Opcode::Lt as u8)));
945    }
946
947    #[test]
948    fn test_strict_eq() {
949        let chunk = compile_src("a === b;");
950        assert!(chunk.bytecode.contains(&(Opcode::StrictEq as u8)));
951    }
952
953    #[test]
954    fn test_logical_and() {
955        let chunk = compile_src("a && b;");
956        assert!(chunk.bytecode.contains(&(Opcode::LogicalAnd as u8)));
957    }
958
959    #[test]
960    fn test_typeof() {
961        let chunk = compile_src("typeof x;");
962        assert!(chunk.bytecode.contains(&(Opcode::Typeof as u8)));
963    }
964
965    #[test]
966    fn test_throw() {
967        let chunk = compile_src("throw 'error';");
968        assert!(chunk.bytecode.contains(&(Opcode::Throw as u8)));
969    }
970
971    #[test]
972    fn test_try_catch() {
973        let chunk = compile_src("try { x(); } catch (e) { y(); }");
974        assert!(chunk.bytecode.contains(&(Opcode::EnterTry as u8)));
975        assert!(chunk.bytecode.contains(&(Opcode::LeaveTry as u8)));
976    }
977
978    #[test]
979    fn test_this() {
980        let chunk = compile_src("this;");
981        assert!(chunk.bytecode.contains(&(Opcode::GetThis as u8)));
982    }
983
984    #[test]
985    fn test_not_operator() {
986        let chunk = compile_src("!x;");
987        assert!(chunk.bytecode.contains(&(Opcode::Not as u8)));
988    }
989
990    #[test]
991    fn test_opcode_from_byte() {
992        assert_eq!(Opcode::from_byte(0), Some(Opcode::LoadConst));
993        assert_eq!(Opcode::from_byte(255), Some(Opcode::Halt));
994        assert_eq!(Opcode::from_byte(99), None);
995    }
996
997    #[test]
998    fn test_for_loop_compile() {
999        let chunk = compile_src("for (let i = 0; i < 10; i = i + 1) { x; }");
1000        assert!(chunk.bytecode.contains(&(Opcode::Jump as u8)));
1001    }
1002
1003    #[test]
1004    fn test_compound_assign() {
1005        let chunk = compile_src("x += 1;");
1006        assert!(chunk.bytecode.contains(&(Opcode::Add as u8)));
1007    }
1008
1009    #[test]
1010    fn test_bool_null_undefined_constants() {
1011        let chunk = compile_src("true; false; null; undefined;");
1012        let has_bool_true = chunk
1013            .constants
1014            .iter()
1015            .any(|c| matches!(c, Constant::Bool(true)));
1016        let has_null = chunk.constants.iter().any(|c| matches!(c, Constant::Null));
1017        let has_undef = chunk
1018            .constants
1019            .iter()
1020            .any(|c| matches!(c, Constant::Undefined));
1021        assert!(has_bool_true);
1022        assert!(has_null);
1023        assert!(has_undef);
1024    }
1025
1026    #[test]
1027    fn test_closure_compile() {
1028        let chunk = compile_src("let f = function(x) { return x; };");
1029        assert!(chunk.bytecode.contains(&(Opcode::CreateClosure as u8)));
1030    }
1031}