Skip to main content

just_engine/runner/jit/
compiler.rs

1//! AST-to-bytecode compiler.
2//!
3//! Walks the AST once and emits a flat bytecode representation
4//! that the VM can execute without tree-walking overhead.
5
6use crate::parser::ast::{
7    AssignmentOperator, BinaryOperator, BlockStatementData, DeclarationType,
8    ExpressionOrSpreadElement, ExpressionOrSuper, ExpressionPatternType, ExpressionType,
9    ForIteratorData, FunctionBodyData, LiteralData, LiteralType, LogicalOperator,
10    MemberExpressionType, NumberLiteralType, PatternOrExpression, PatternType, ProgramData,
11    StatementType, SwitchCaseData, UnaryOperator, UpdateOperator, VariableDeclarationData,
12    VariableDeclarationKind, VariableDeclarationOrExpression,
13};
14use crate::runner::ds::value::{JsNumberType, JsValue};
15use std::collections::{HashMap, HashSet};
16
17use super::bytecode::{Chunk, FunctionTemplate, Instruction, OpCode};
18
19/// Tracks loop context for break/continue resolution.
20struct LoopContext {
21    /// Instruction index of the loop condition (continue target).
22    continue_target: usize,
23    /// Indices of break jumps that need patching when the loop ends.
24    break_jumps: Vec<usize>,
25    /// Indices of continue jumps that need patching (for for-loops where
26    /// the update position isn't known when continue is compiled).
27    continue_jumps: Vec<usize>,
28}
29
30/// The bytecode compiler.
31pub struct Compiler {
32    chunk: Chunk,
33    /// Stack of active loop contexts for break/continue.
34    loop_stack: Vec<LoopContext>,
35    /// Local slot indices for fast var access.
36    locals: HashMap<String, u32>,
37    /// Lexical scopes for let/const shadowing.
38    lexical_scopes: Vec<HashSet<String>>,
39}
40
41impl Compiler {
42    pub fn new() -> Self {
43        Compiler {
44            chunk: Chunk::new(),
45            loop_stack: Vec::new(),
46            locals: HashMap::new(),
47            lexical_scopes: vec![HashSet::new()],
48        }
49    }
50
51    fn get_local_slot(&self, name: &str) -> Option<u32> {
52        self.locals.get(name).copied()
53    }
54
55    fn get_or_add_local_slot(&mut self, name: &str) -> u32 {
56        if let Some(slot) = self.locals.get(name) {
57            return *slot;
58        }
59        let name_idx = self.chunk.add_name(name);
60        let slot = self.chunk.add_local(name_idx);
61        self.locals.insert(name.to_string(), slot);
62        slot
63    }
64
65    fn push_lexical_scope(&mut self) {
66        self.lexical_scopes.push(HashSet::new());
67    }
68
69    fn pop_lexical_scope(&mut self) {
70        self.lexical_scopes.pop();
71    }
72
73    fn declare_lexical(&mut self, name: &str) {
74        if let Some(scope) = self.lexical_scopes.last_mut() {
75            scope.insert(name.to_string());
76        }
77    }
78
79    fn is_lexically_shadowed(&self, name: &str) -> bool {
80        self.lexical_scopes
81            .iter()
82            .rev()
83            .any(|scope| scope.contains(name))
84    }
85
86    /// Compile a full program AST into bytecode.
87    pub fn compile_program(mut self, program: &ProgramData) -> Chunk {
88        for stmt in &program.body {
89            self.compile_statement(stmt);
90        }
91        self.chunk.emit_op(OpCode::Halt);
92        self.chunk
93    }
94
95    // ════════════════════════════════════════════════════════════
96    // Statements
97    // ════════════════════════════════════════════════════════════
98
99    fn compile_statement(&mut self, stmt: &StatementType) {
100        match stmt {
101            StatementType::EmptyStatement { .. } => {}
102
103            StatementType::ExpressionStatement { expression, .. } => {
104                self.compile_expression(expression);
105                self.chunk.emit_op(OpCode::Pop);
106            }
107
108            StatementType::BlockStatement(block) => {
109                self.compile_block(block);
110            }
111
112            StatementType::DeclarationStatement(decl) => {
113                self.compile_declaration(decl);
114            }
115
116            StatementType::IfStatement { test, consequent, alternate, .. } => {
117                self.compile_if(test, consequent, alternate.as_ref().map(|a| a.as_ref()));
118            }
119
120            StatementType::WhileStatement { test, body, .. } => {
121                self.compile_while(test, body);
122            }
123
124            StatementType::DoWhileStatement { test, body, .. } => {
125                self.compile_do_while(body, test);
126            }
127
128            StatementType::ForStatement { init, test, update, body, .. } => {
129                self.compile_for(
130                    init.as_ref(),
131                    test.as_ref().map(|t| t.as_ref()),
132                    update.as_ref().map(|u| u.as_ref()),
133                    body,
134                );
135            }
136
137            StatementType::ForInStatement(data) => {
138                self.compile_for_in(data);
139            }
140
141            StatementType::ForOfStatement(data) => {
142                self.compile_for_of(data);
143            }
144
145            StatementType::SwitchStatement { discriminant, cases, .. } => {
146                self.compile_switch(discriminant, cases);
147            }
148
149            StatementType::BreakStatement { .. } => {
150                self.compile_break();
151            }
152
153            StatementType::ContinueStatement { .. } => {
154                self.compile_continue();
155            }
156
157            StatementType::ReturnStatement { argument, .. } => {
158                if let Some(arg) = argument {
159                    self.compile_expression(arg);
160                } else {
161                    self.chunk.emit_op(OpCode::Undefined);
162                }
163                self.chunk.emit_op(OpCode::Return);
164            }
165
166            StatementType::ThrowStatement { argument, .. } => {
167                self.compile_expression(argument);
168                // For now, throw is treated as a return-like halt.
169                // Full exception handling would need exception tables.
170                self.chunk.emit_op(OpCode::Return);
171            }
172
173            StatementType::TryStatement { block, handler, finalizer, .. } => {
174                // Simplified: just execute the block, ignore try/catch semantics for now.
175                // Full implementation would need exception tables in the bytecode.
176                self.compile_block(block);
177                if let Some(_handler) = handler {
178                    // TODO: exception table support
179                }
180                if let Some(fin) = finalizer {
181                    self.compile_block(fin);
182                }
183            }
184
185            StatementType::DebuggerStatement { .. } => {}
186
187            StatementType::FunctionBody(body) => {
188                self.compile_function_body(body);
189            }
190        }
191    }
192
193    fn compile_block(&mut self, block: &BlockStatementData) {
194        self.push_lexical_scope();
195        self.chunk.emit_op(OpCode::PushScope);
196        for stmt in block.body.iter() {
197            self.compile_statement(stmt);
198        }
199        self.chunk.emit_op(OpCode::PopScope);
200        self.pop_lexical_scope();
201    }
202
203    fn compile_function_body(&mut self, body: &FunctionBodyData) {
204        self.push_lexical_scope();
205        for stmt in body.body.iter() {
206            self.compile_statement(stmt);
207        }
208        self.pop_lexical_scope();
209    }
210
211    fn compile_declaration(&mut self, decl: &DeclarationType) {
212        match decl {
213            DeclarationType::VariableDeclaration(var_decl) => {
214                self.compile_var_declaration(var_decl);
215            }
216            DeclarationType::FunctionOrGeneratorDeclaration(func_data) => {
217                if let Some(ref id) = func_data.id {
218                    let slot = self.get_or_add_local_slot(&id.name);
219                    let name_idx = self.chunk.add_name(&id.name);
220                    let func_idx = self.chunk.add_function(FunctionTemplate {
221                        body_ptr: func_data.body.as_ref() as *const _,
222                        params_ptr: &func_data.params.list as *const _,
223                    });
224                    // Declare in the EvalContext so the function is visible
225                    // to its own body (recursion) and to closures.
226                    self.chunk.emit_with(OpCode::DeclareVar, name_idx);
227                    self.chunk.emit_with(OpCode::MakeClosure, func_idx);
228                    self.chunk.emit_op(OpCode::Dup);
229                    self.chunk.emit_with(OpCode::InitVar, name_idx);
230                    self.chunk.emit_with(OpCode::InitLocal, slot);
231                }
232            }
233            DeclarationType::ClassDeclaration(_class_data) => {
234                // TODO: class compilation
235            }
236        }
237    }
238
239    fn compile_var_declaration(&mut self, var_decl: &VariableDeclarationData) {
240        let (declare_op, init_op) = match var_decl.kind {
241            VariableDeclarationKind::Var => (OpCode::DeclareVar, OpCode::InitVar),
242            VariableDeclarationKind::Let => (OpCode::DeclareLet, OpCode::InitBinding),
243            VariableDeclarationKind::Const => (OpCode::DeclareConst, OpCode::InitBinding),
244        };
245
246        for declarator in &var_decl.declarations {
247            if let PatternType::PatternWhichCanBeExpression(
248                ExpressionPatternType::Identifier(ref id),
249            ) = declarator.id.as_ref()
250            {
251                if matches!(var_decl.kind, VariableDeclarationKind::Var) {
252                    let slot = self.get_or_add_local_slot(&id.name);
253                    if let Some(ref init_expr) = declarator.init {
254                        self.compile_expression(init_expr);
255                    } else {
256                        self.chunk.emit_op(OpCode::Undefined);
257                    }
258                    self.chunk.emit_with(OpCode::InitLocal, slot);
259                    continue;
260                }
261
262                // Track lexical declarations to prevent var slot usage in nested scopes.
263                self.declare_lexical(&id.name);
264
265                let name_idx = self.chunk.add_name(&id.name);
266                self.chunk.emit_with(declare_op, name_idx);
267
268                if let Some(ref init_expr) = declarator.init {
269                    self.compile_expression(init_expr);
270                } else {
271                    self.chunk.emit_op(OpCode::Undefined);
272                }
273                self.chunk.emit_with(init_op, name_idx);
274            }
275        }
276    }
277
278    // ── Control flow ─────────────────────────────────────────
279
280    fn compile_if(
281        &mut self,
282        test: &ExpressionType,
283        consequent: &StatementType,
284        alternate: Option<&StatementType>,
285    ) {
286        self.compile_expression(test);
287        let jump_to_else = self.chunk.emit_with(OpCode::JumpIfFalse, 0);
288
289        self.compile_statement(consequent);
290
291        if let Some(alt) = alternate {
292            let jump_over_else = self.chunk.emit_with(OpCode::Jump, 0);
293            self.chunk.patch_jump(jump_to_else);
294            self.compile_statement(alt);
295            self.chunk.patch_jump(jump_over_else);
296        } else {
297            self.chunk.patch_jump(jump_to_else);
298        }
299    }
300
301    fn compile_while(&mut self, test: &ExpressionType, body: &StatementType) {
302        let loop_start = self.chunk.current_pos();
303
304        self.loop_stack.push(LoopContext {
305            continue_target: loop_start,
306            break_jumps: Vec::new(),
307            continue_jumps: Vec::new(),
308        });
309
310        self.compile_expression(test);
311        let exit_jump = self.chunk.emit_with(OpCode::JumpIfFalse, 0);
312
313        self.compile_statement(body);
314        self.chunk.emit_with(OpCode::Jump, loop_start as u32);
315
316        self.chunk.patch_jump(exit_jump);
317
318        let ctx = self.loop_stack.pop().unwrap();
319        for bj in ctx.break_jumps {
320            self.chunk.patch_jump(bj);
321        }
322    }
323
324    fn compile_do_while(&mut self, body: &StatementType, test: &ExpressionType) {
325        let loop_start = self.chunk.current_pos();
326
327        self.loop_stack.push(LoopContext {
328            continue_target: loop_start,
329            break_jumps: Vec::new(),
330            continue_jumps: Vec::new(),
331        });
332
333        self.compile_statement(body);
334
335        // Continue target is the test
336        let continue_target = self.chunk.current_pos();
337        if let Some(ctx) = self.loop_stack.last_mut() {
338            ctx.continue_target = continue_target;
339        }
340
341        self.compile_expression(test);
342        self.chunk.emit_with(OpCode::JumpIfTrue, loop_start as u32);
343
344        let ctx = self.loop_stack.pop().unwrap();
345        for cj in &ctx.continue_jumps {
346            self.chunk.code[*cj].operand = continue_target as u32;
347        }
348        for bj in ctx.break_jumps {
349            self.chunk.patch_jump(bj);
350        }
351    }
352
353    fn compile_for(
354        &mut self,
355        init: Option<&VariableDeclarationOrExpression>,
356        test: Option<&ExpressionType>,
357        update: Option<&ExpressionType>,
358        body: &StatementType,
359    ) {
360        self.push_lexical_scope();
361        self.chunk.emit_op(OpCode::PushScope);
362
363        // Init
364        if let Some(init) = init {
365            match init {
366                VariableDeclarationOrExpression::VariableDeclaration(var_decl) => {
367                    self.compile_var_declaration(var_decl);
368                }
369                VariableDeclarationOrExpression::Expression(expr) => {
370                    self.compile_expression(expr);
371                    self.chunk.emit_op(OpCode::Pop);
372                }
373            }
374        }
375
376        let loop_start = self.chunk.current_pos();
377
378        // We need to know where the update is for continue
379        // So we use a placeholder continue_target and fix it later
380        self.loop_stack.push(LoopContext {
381            continue_target: loop_start, // will be updated
382            break_jumps: Vec::new(),
383            continue_jumps: Vec::new(),
384        });
385
386        // Test
387        let exit_jump = if let Some(test) = test {
388            self.compile_expression(test);
389            Some(self.chunk.emit_with(OpCode::JumpIfFalse, 0))
390        } else {
391            None
392        };
393
394        // Body
395        self.compile_statement(body);
396
397        // Update (this is the continue target)
398        let update_pos = self.chunk.current_pos();
399        if let Some(ctx) = self.loop_stack.last_mut() {
400            ctx.continue_target = update_pos;
401        }
402
403        if let Some(update) = update {
404            self.compile_expression(update);
405            self.chunk.emit_op(OpCode::Pop);
406        }
407
408        self.chunk.emit_with(OpCode::Jump, loop_start as u32);
409
410        if let Some(ej) = exit_jump {
411            self.chunk.patch_jump(ej);
412        }
413
414        let ctx = self.loop_stack.pop().unwrap();
415        // Patch continue jumps to the update position
416        for cj in &ctx.continue_jumps {
417            self.chunk.code[*cj].operand = update_pos as u32;
418        }
419        for bj in ctx.break_jumps {
420            self.chunk.patch_jump(bj);
421        }
422
423        self.chunk.emit_op(OpCode::PopScope);
424        self.pop_lexical_scope();
425    }
426
427    fn compile_for_in(&mut self, _data: &ForIteratorData) {
428        // TODO: for-in requires runtime object key iteration
429        // Emit a no-op for now
430    }
431
432    fn compile_for_of(&mut self, _data: &ForIteratorData) {
433        // TODO: for-of requires runtime iterator protocol
434        // Emit a no-op for now
435    }
436
437    fn compile_switch(
438        &mut self,
439        discriminant: &ExpressionType,
440        cases: &[SwitchCaseData],
441    ) {
442        self.compile_expression(discriminant);
443
444        // For each case, we emit: Dup, <test_expr>, StrictEqual, JumpIfFalse
445        // For the default case, we just jump.
446        let mut case_body_jumps: Vec<usize> = Vec::new();
447        let mut default_jump: Option<usize> = None;
448
449        // Phase 1: emit all the tests
450        for case in cases {
451            if let Some(ref test) = case.test {
452                self.chunk.emit_op(OpCode::Dup);
453                self.compile_expression(test);
454                self.chunk.emit_op(OpCode::StrictEqual);
455                let jump = self.chunk.emit_with(OpCode::JumpIfTrue, 0);
456                case_body_jumps.push(jump);
457            } else {
458                // default case
459                default_jump = Some(case_body_jumps.len());
460                case_body_jumps.push(0); // placeholder
461            }
462        }
463
464        // Jump to default or end
465        let jump_to_default_or_end = self.chunk.emit_with(OpCode::Jump, 0);
466
467        // Set up break context
468        self.loop_stack.push(LoopContext {
469            continue_target: 0, // not used for switch
470            break_jumps: Vec::new(),
471            continue_jumps: Vec::new(),
472        });
473
474        // Phase 2: emit all the bodies
475        for (i, case) in cases.iter().enumerate() {
476            let body_pos = self.chunk.current_pos();
477            if i < case_body_jumps.len() && case_body_jumps[i] != 0 {
478                self.chunk.code[case_body_jumps[i]].operand = body_pos as u32;
479            }
480            if default_jump == Some(i) {
481                self.chunk.code[jump_to_default_or_end].operand = body_pos as u32;
482            }
483            for stmt in &case.consequent {
484                self.compile_statement(stmt);
485            }
486        }
487
488        // If no default, patch the jump-to-end
489        if default_jump.is_none() {
490            self.chunk.patch_jump(jump_to_default_or_end);
491        }
492
493        // Pop the discriminant
494        self.chunk.emit_op(OpCode::Pop);
495
496        let ctx = self.loop_stack.pop().unwrap();
497        for bj in ctx.break_jumps {
498            self.chunk.patch_jump(bj);
499        }
500    }
501
502    fn compile_break(&mut self) {
503        if let Some(ctx) = self.loop_stack.last_mut() {
504            let jump = self.chunk.emit_with(OpCode::Jump, 0);
505            ctx.break_jumps.push(jump);
506        }
507    }
508
509    fn compile_continue(&mut self) {
510        if let Some(ctx) = self.loop_stack.last() {
511            let target = ctx.continue_target;
512            let jump = self.chunk.emit_with(OpCode::Jump, target as u32);
513            // Record the jump so it can be patched later (for for-loops where
514            // the update position isn't known yet when continue is compiled).
515            if let Some(ctx) = self.loop_stack.last_mut() {
516                ctx.continue_jumps.push(jump);
517            }
518        }
519    }
520
521    // ════════════════════════════════════════════════════════════
522    // Expressions
523    // ════════════════════════════════════════════════════════════
524
525    fn compile_expression(&mut self, expr: &ExpressionType) {
526        match expr {
527            ExpressionType::Literal(lit) => {
528                self.compile_literal(lit);
529            }
530
531            ExpressionType::ExpressionWhichCanBePattern(pattern) => {
532                self.compile_expression_pattern(pattern);
533            }
534
535            ExpressionType::ThisExpression { .. } => {
536                // Push `this` — for now, treat as undefined at top level
537                self.chunk.emit_op(OpCode::Undefined);
538            }
539
540            ExpressionType::BinaryExpression { operator, left, right, .. } => {
541                self.compile_expression(left);
542                self.compile_expression(right);
543                self.compile_binary_op(operator);
544            }
545
546            ExpressionType::LogicalExpression { operator, left, right, .. } => {
547                self.compile_logical(operator, left, right);
548            }
549
550            ExpressionType::UnaryExpression { operator, argument, .. } => {
551                self.compile_expression(argument);
552                self.compile_unary_op(operator);
553            }
554
555            ExpressionType::UpdateExpression { operator, argument, prefix, .. } => {
556                self.compile_update(operator, argument, *prefix);
557            }
558
559            ExpressionType::AssignmentExpression { operator, left, right, .. } => {
560                self.compile_assignment(operator, left, right);
561            }
562
563            ExpressionType::ConditionalExpression { test, consequent, alternate, .. } => {
564                self.compile_expression(test);
565                let jump_to_alt = self.chunk.emit_with(OpCode::JumpIfFalse, 0);
566                self.compile_expression(consequent);
567                let jump_over = self.chunk.emit_with(OpCode::Jump, 0);
568                self.chunk.patch_jump(jump_to_alt);
569                self.compile_expression(alternate);
570                self.chunk.patch_jump(jump_over);
571            }
572
573            ExpressionType::SequenceExpression { expressions, .. } => {
574                for (i, expr) in expressions.iter().enumerate() {
575                    self.compile_expression(expr);
576                    if i < expressions.len() - 1 {
577                        self.chunk.emit_op(OpCode::Pop);
578                    }
579                }
580            }
581
582            ExpressionType::CallExpression { callee, arguments, .. } => {
583                self.compile_call(callee, arguments);
584            }
585
586            ExpressionType::MemberExpression(member_expr) => {
587                self.compile_member_expression(member_expr);
588            }
589
590            ExpressionType::ArrayExpression { .. } => {
591                // Fallback: push undefined for now
592                // Full array compilation would need CreateArray + SetElem opcodes
593                self.chunk.emit_op(OpCode::Undefined);
594            }
595
596            ExpressionType::ObjectExpression { .. } => {
597                // Fallback: push undefined for now
598                self.chunk.emit_op(OpCode::Undefined);
599            }
600
601            ExpressionType::FunctionOrGeneratorExpression(func_data) => {
602                let func_idx = self.chunk.add_function(FunctionTemplate {
603                    body_ptr: func_data.body.as_ref() as *const _,
604                    params_ptr: &func_data.params.list as *const _,
605                });
606                self.chunk.emit_with(OpCode::MakeClosure, func_idx);
607            }
608
609            ExpressionType::NewExpression { .. } => {
610                self.chunk.emit_op(OpCode::Undefined);
611            }
612
613            ExpressionType::ArrowFunctionExpression { params, body, .. } => {
614                match body.as_ref() {
615                    crate::parser::ast::FunctionBodyOrExpression::FunctionBody(func_body) => {
616                        let func_idx = self.chunk.add_function(FunctionTemplate {
617                            body_ptr: func_body as *const _,
618                            params_ptr: params as *const _,
619                        });
620                        self.chunk.emit_with(OpCode::MakeClosure, func_idx);
621                    }
622                    crate::parser::ast::FunctionBodyOrExpression::Expression(_) => {
623                        // Concise arrow bodies need wrapping; fall back for now
624                        self.chunk.emit_op(OpCode::Undefined);
625                    }
626                }
627            }
628
629            ExpressionType::YieldExpression { .. } => {
630                self.chunk.emit_op(OpCode::Undefined);
631            }
632
633            ExpressionType::TemplateLiteral(_) => {
634                self.chunk.emit_op(OpCode::Undefined);
635            }
636
637            ExpressionType::TaggedTemplateExpression { .. } => {
638                self.chunk.emit_op(OpCode::Undefined);
639            }
640
641            ExpressionType::ClassExpression(_) => {
642                self.chunk.emit_op(OpCode::Undefined);
643            }
644
645            ExpressionType::MetaProperty { .. } => {
646                self.chunk.emit_op(OpCode::Undefined);
647            }
648        }
649    }
650
651    fn compile_literal(&mut self, lit: &LiteralData) {
652        match &lit.value {
653            LiteralType::NullLiteral => {
654                self.chunk.emit_op(OpCode::Null);
655            }
656            LiteralType::BooleanLiteral(true) => {
657                self.chunk.emit_op(OpCode::True);
658            }
659            LiteralType::BooleanLiteral(false) => {
660                self.chunk.emit_op(OpCode::False);
661            }
662            LiteralType::StringLiteral(s) => {
663                let idx = self.chunk.add_constant(JsValue::String(s.clone()));
664                self.chunk.emit_with(OpCode::Constant, idx);
665            }
666            LiteralType::NumberLiteral(n) => {
667                let value = match n {
668                    NumberLiteralType::IntegerLiteral(i) => {
669                        JsValue::Number(JsNumberType::Integer(*i))
670                    }
671                    NumberLiteralType::FloatLiteral(f) => {
672                        JsValue::Number(JsNumberType::Float(*f))
673                    }
674                };
675                let idx = self.chunk.add_constant(value);
676                self.chunk.emit_with(OpCode::Constant, idx);
677            }
678            LiteralType::RegExpLiteral(_) => {
679                self.chunk.emit_op(OpCode::Undefined);
680            }
681        }
682    }
683
684    fn compile_expression_pattern(&mut self, pattern: &ExpressionPatternType) {
685        match pattern {
686            ExpressionPatternType::Identifier(id) => {
687                if !self.is_lexically_shadowed(&id.name) {
688                    if let Some(slot) = self.get_local_slot(&id.name) {
689                        self.chunk.emit_with(OpCode::GetLocal, slot);
690                        return;
691                    }
692                }
693                let name_idx = self.chunk.add_name(&id.name);
694                self.chunk.emit_with(OpCode::GetVar, name_idx);
695            }
696        }
697    }
698
699    fn compile_binary_op(&mut self, op: &BinaryOperator) {
700        match op {
701            BinaryOperator::Add => self.chunk.emit_op(OpCode::Add),
702            BinaryOperator::Subtract => self.chunk.emit_op(OpCode::Sub),
703            BinaryOperator::Multiply => self.chunk.emit_op(OpCode::Mul),
704            BinaryOperator::Divide => self.chunk.emit_op(OpCode::Div),
705            BinaryOperator::Modulo => self.chunk.emit_op(OpCode::Mod),
706            BinaryOperator::StrictlyEqual => self.chunk.emit_op(OpCode::StrictEqual),
707            BinaryOperator::StrictlyUnequal => self.chunk.emit_op(OpCode::StrictNotEqual),
708            BinaryOperator::LooselyEqual => self.chunk.emit_op(OpCode::Equal),
709            BinaryOperator::LooselyUnequal => self.chunk.emit_op(OpCode::NotEqual),
710            BinaryOperator::LessThan => self.chunk.emit_op(OpCode::LessThan),
711            BinaryOperator::LessThanEqual => self.chunk.emit_op(OpCode::LessEqual),
712            BinaryOperator::GreaterThan => self.chunk.emit_op(OpCode::GreaterThan),
713            BinaryOperator::GreaterThanEqual => self.chunk.emit_op(OpCode::GreaterEqual),
714            BinaryOperator::BitwiseAnd => self.chunk.emit_op(OpCode::BitAnd),
715            BinaryOperator::BitwiseOr => self.chunk.emit_op(OpCode::BitOr),
716            BinaryOperator::BitwiseXor => self.chunk.emit_op(OpCode::BitXor),
717            BinaryOperator::BitwiseLeftShift => self.chunk.emit_op(OpCode::ShiftLeft),
718            BinaryOperator::BitwiseRightShift => self.chunk.emit_op(OpCode::ShiftRight),
719            BinaryOperator::BitwiseUnsignedRightShift => self.chunk.emit_op(OpCode::UShiftRight),
720            BinaryOperator::In | BinaryOperator::InstanceOf => {
721                // Not yet compiled
722                self.chunk.emit_op(OpCode::Undefined);
723                0
724            }
725        };
726    }
727
728    fn compile_unary_op(&mut self, op: &UnaryOperator) {
729        match op {
730            UnaryOperator::Minus => { self.chunk.emit_op(OpCode::Negate); }
731            UnaryOperator::Plus => { self.chunk.emit_op(OpCode::UnaryPlus); }
732            UnaryOperator::LogicalNot => { self.chunk.emit_op(OpCode::Not); }
733            UnaryOperator::BitwiseNot => { self.chunk.emit_op(OpCode::BitNot); }
734            UnaryOperator::TypeOf => { self.chunk.emit_op(OpCode::TypeOf); }
735            UnaryOperator::Void => { self.chunk.emit_op(OpCode::Void); }
736            _ => { self.chunk.emit_op(OpCode::Undefined); }
737        };
738    }
739
740    fn compile_logical(
741        &mut self,
742        op: &LogicalOperator,
743        left: &ExpressionType,
744        right: &ExpressionType,
745    ) {
746        self.compile_expression(left);
747        match op {
748            LogicalOperator::And => {
749                // Short-circuit: if left is falsy, skip right
750                self.chunk.emit_op(OpCode::Dup);
751                let jump = self.chunk.emit_with(OpCode::JumpIfFalse, 0);
752                self.chunk.emit_op(OpCode::Pop);
753                self.compile_expression(right);
754                self.chunk.patch_jump(jump);
755            }
756            LogicalOperator::Or => {
757                // Short-circuit: if left is truthy, skip right
758                self.chunk.emit_op(OpCode::Dup);
759                let jump = self.chunk.emit_with(OpCode::JumpIfTrue, 0);
760                self.chunk.emit_op(OpCode::Pop);
761                self.compile_expression(right);
762                self.chunk.patch_jump(jump);
763            }
764        }
765    }
766
767    fn compile_update(
768        &mut self,
769        op: &UpdateOperator,
770        argument: &ExpressionType,
771        prefix: bool,
772    ) {
773        // Only handle simple identifier updates
774        if let ExpressionType::ExpressionWhichCanBePattern(
775            ExpressionPatternType::Identifier(ref id),
776        ) = argument
777        {
778            if !self.is_lexically_shadowed(&id.name) {
779                if let Some(slot) = self.get_local_slot(&id.name) {
780                    let one_idx = self
781                        .chunk
782                        .add_constant(JsValue::Number(JsNumberType::Integer(1)));
783                    match (op, prefix) {
784                        (UpdateOperator::PlusPlus, true) => {
785                            self.chunk.emit_with(OpCode::GetLocal, slot);
786                            self.chunk.emit_with(OpCode::Constant, one_idx);
787                            self.chunk.emit_op(OpCode::Add);
788                            self.chunk.emit_op(OpCode::Dup);
789                            self.chunk.emit_with(OpCode::SetLocal, slot);
790                        }
791                        (UpdateOperator::MinusMinus, true) => {
792                            self.chunk.emit_with(OpCode::GetLocal, slot);
793                            self.chunk.emit_with(OpCode::Constant, one_idx);
794                            self.chunk.emit_op(OpCode::Sub);
795                            self.chunk.emit_op(OpCode::Dup);
796                            self.chunk.emit_with(OpCode::SetLocal, slot);
797                        }
798                        (UpdateOperator::PlusPlus, false) => {
799                            self.chunk.emit_with(OpCode::GetLocal, slot);
800                            self.chunk.emit_op(OpCode::Dup);
801                            self.chunk.emit_with(OpCode::Constant, one_idx);
802                            self.chunk.emit_op(OpCode::Add);
803                            self.chunk.emit_with(OpCode::SetLocal, slot);
804                        }
805                        (UpdateOperator::MinusMinus, false) => {
806                            self.chunk.emit_with(OpCode::GetLocal, slot);
807                            self.chunk.emit_op(OpCode::Dup);
808                            self.chunk.emit_with(OpCode::Constant, one_idx);
809                            self.chunk.emit_op(OpCode::Sub);
810                            self.chunk.emit_with(OpCode::SetLocal, slot);
811                        }
812                    }
813                    return;
814                }
815            }
816            let name_idx = self.chunk.add_name(&id.name);
817            match (op, prefix) {
818                (UpdateOperator::PlusPlus, true) => {
819                    self.chunk.emit_with(OpCode::PreIncVar, name_idx);
820                }
821                (UpdateOperator::MinusMinus, true) => {
822                    self.chunk.emit_with(OpCode::PreDecVar, name_idx);
823                }
824                (UpdateOperator::PlusPlus, false) => {
825                    self.chunk.emit_with(OpCode::PostIncVar, name_idx);
826                }
827                (UpdateOperator::MinusMinus, false) => {
828                    self.chunk.emit_with(OpCode::PostDecVar, name_idx);
829                }
830            }
831        } else {
832            // Fallback for member expression updates
833            self.chunk.emit_op(OpCode::Undefined);
834        }
835    }
836
837    fn compile_assignment(
838        &mut self,
839        op: &AssignmentOperator,
840        left: &PatternOrExpression,
841        right: &ExpressionType,
842    ) {
843        // Extract the variable name from the left-hand side
844        let name = self.extract_assignment_target_name(left);
845
846        if let Some(name) = name {
847            if !self.is_lexically_shadowed(&name) {
848                if let Some(slot) = self.get_local_slot(&name) {
849                    match op {
850                        AssignmentOperator::Equals => {
851                            self.compile_expression(right);
852                            self.chunk.emit_op(OpCode::Dup);
853                            self.chunk.emit_with(OpCode::SetLocal, slot);
854                        }
855                        _ => {
856                            self.chunk.emit_with(OpCode::GetLocal, slot);
857                            self.compile_expression(right);
858                            self.compile_compound_op(op);
859                            self.chunk.emit_op(OpCode::Dup);
860                            self.chunk.emit_with(OpCode::SetLocal, slot);
861                        }
862                    }
863                    return;
864                }
865            }
866            let name_idx = self.chunk.add_name(&name);
867            match op {
868                AssignmentOperator::Equals => {
869                    self.compile_expression(right);
870                    self.chunk.emit_op(OpCode::Dup); // keep value on stack as result
871                    self.chunk.emit_with(OpCode::SetVar, name_idx);
872                }
873                _ => {
874                    // Compound assignment: get current value, compute, set
875                    self.chunk.emit_with(OpCode::GetVar, name_idx);
876                    self.compile_expression(right);
877                    self.compile_compound_op(op);
878                    self.chunk.emit_op(OpCode::Dup);
879                    self.chunk.emit_with(OpCode::SetVar, name_idx);
880                }
881            }
882        } else {
883            // Member expression assignment
884            self.compile_member_assignment(op, left, right);
885        }
886    }
887
888    fn extract_assignment_target_name(&self, target: &PatternOrExpression) -> Option<String> {
889        match target {
890            PatternOrExpression::Pattern(pat) => {
891                if let PatternType::PatternWhichCanBeExpression(
892                    ExpressionPatternType::Identifier(ref id),
893                ) = pat.as_ref()
894                {
895                    Some(id.name.clone())
896                } else {
897                    None
898                }
899            }
900            PatternOrExpression::Expression(expr) => {
901                if let ExpressionType::ExpressionWhichCanBePattern(
902                    ExpressionPatternType::Identifier(ref id),
903                ) = expr.as_ref()
904                {
905                    Some(id.name.clone())
906                } else {
907                    None
908                }
909            }
910        }
911    }
912
913    fn compile_member_assignment(
914        &mut self,
915        op: &AssignmentOperator,
916        left: &PatternOrExpression,
917        right: &ExpressionType,
918    ) {
919        if let PatternOrExpression::Expression(expr) = left {
920            if let ExpressionType::MemberExpression(member) = expr.as_ref() {
921                match member {
922                    MemberExpressionType::SimpleMemberExpression { object, property, .. } => {
923                        match object {
924                            ExpressionOrSuper::Expression(obj_expr) => {
925                                self.compile_expression(obj_expr);
926                            }
927                            ExpressionOrSuper::Super => {
928                                self.chunk.emit_op(OpCode::Undefined);
929                            }
930                        }
931
932                        let prop_idx = self.chunk.add_name(&property.name);
933                        if matches!(op, AssignmentOperator::Equals) {
934                            self.compile_expression(right);
935                            self.chunk.emit_with(OpCode::SetProp, prop_idx);
936                        } else {
937                            self.chunk.emit_op(OpCode::Dup);
938                            self.chunk.emit_with(OpCode::GetProp, prop_idx);
939                            self.compile_expression(right);
940                            self.compile_compound_op(op);
941                            self.chunk.emit_with(OpCode::SetProp, prop_idx);
942                        }
943                        return;
944                    }
945                    MemberExpressionType::ComputedMemberExpression { object, property, .. } => {
946                        match object {
947                            ExpressionOrSuper::Expression(obj_expr) => {
948                                self.compile_expression(obj_expr);
949                            }
950                            ExpressionOrSuper::Super => {
951                                self.chunk.emit_op(OpCode::Undefined);
952                            }
953                        }
954                        self.compile_expression(property);
955
956                        if matches!(op, AssignmentOperator::Equals) {
957                            self.compile_expression(right);
958                            self.chunk.emit_op(OpCode::SetElem);
959                        } else {
960                            self.chunk.emit_op(OpCode::Dup2);
961                            self.chunk.emit_op(OpCode::GetElem);
962                            self.compile_expression(right);
963                            self.compile_compound_op(op);
964                            self.chunk.emit_op(OpCode::SetElem);
965                        }
966                        return;
967                    }
968                }
969            }
970        }
971
972        // Fallback: evaluate right side only.
973        self.compile_expression(right);
974    }
975
976    fn compile_compound_op(&mut self, op: &AssignmentOperator) {
977        match op {
978            AssignmentOperator::AddEquals => { self.chunk.emit_op(OpCode::Add); }
979            AssignmentOperator::SubtractEquals => { self.chunk.emit_op(OpCode::Sub); }
980            AssignmentOperator::MultiplyEquals => { self.chunk.emit_op(OpCode::Mul); }
981            AssignmentOperator::DivideEquals => { self.chunk.emit_op(OpCode::Div); }
982            AssignmentOperator::ModuloEquals => { self.chunk.emit_op(OpCode::Mod); }
983            AssignmentOperator::BitwiseAndEquals => { self.chunk.emit_op(OpCode::BitAnd); }
984            AssignmentOperator::BitwiseOrEquals => { self.chunk.emit_op(OpCode::BitOr); }
985            AssignmentOperator::BitwiseXorEquals => { self.chunk.emit_op(OpCode::BitXor); }
986            AssignmentOperator::BitwiseLeftShiftEquals => { self.chunk.emit_op(OpCode::ShiftLeft); }
987            AssignmentOperator::BitwiseRightShiftEquals => { self.chunk.emit_op(OpCode::ShiftRight); }
988            AssignmentOperator::BitwiseUnsignedRightShiftEquals => { self.chunk.emit_op(OpCode::UShiftRight); }
989            AssignmentOperator::Equals => {}
990        };
991    }
992
993    fn compile_call(
994        &mut self,
995        callee: &ExpressionOrSuper,
996        arguments: &[ExpressionOrSpreadElement],
997    ) {
998        // Check if this is a method call (e.g., console.log)
999        match callee {
1000            ExpressionOrSuper::Expression(expr) => {
1001                if let ExpressionType::MemberExpression(
1002                    MemberExpressionType::SimpleMemberExpression { object, property, .. },
1003                ) = expr.as_ref()
1004                {
1005                    // Method call: push object, then args, then CallMethod
1006                    match object {
1007                        ExpressionOrSuper::Expression(obj_expr) => {
1008                            self.compile_expression(obj_expr);
1009                        }
1010                        ExpressionOrSuper::Super => {
1011                            self.chunk.emit_op(OpCode::Undefined);
1012                        }
1013                    }
1014
1015                    let argc = arguments.len();
1016                    for arg in arguments {
1017                        match arg {
1018                            ExpressionOrSpreadElement::Expression(e) => {
1019                                self.compile_expression(e);
1020                            }
1021                            ExpressionOrSpreadElement::SpreadElement(e) => {
1022                                self.compile_expression(e);
1023                            }
1024                        }
1025                    }
1026
1027                    let method_idx = self.chunk.add_name(&property.name);
1028                    // Extract the object variable name for built-in registry lookup
1029                    let obj_name_idx = match object {
1030                        ExpressionOrSuper::Expression(obj_expr) => {
1031                            if let ExpressionType::ExpressionWhichCanBePattern(
1032                                ExpressionPatternType::Identifier(id)
1033                            ) = obj_expr.as_ref() {
1034                                self.chunk.add_name(&id.name)
1035                            } else {
1036                                self.chunk.add_name("")
1037                            }
1038                        }
1039                        _ => self.chunk.add_name(""),
1040                    };
1041                    self.chunk.emit(Instruction::with_three_operands(
1042                        OpCode::CallMethod,
1043                        argc as u32,
1044                        method_idx,
1045                        obj_name_idx,
1046                    ));
1047                } else {
1048                    // Regular function call
1049                    self.compile_expression(expr);
1050                    let argc = arguments.len();
1051                    for arg in arguments {
1052                        match arg {
1053                            ExpressionOrSpreadElement::Expression(e) => {
1054                                self.compile_expression(e);
1055                            }
1056                            ExpressionOrSpreadElement::SpreadElement(e) => {
1057                                self.compile_expression(e);
1058                            }
1059                        }
1060                    }
1061                    self.chunk.emit_with(OpCode::Call, argc as u32);
1062                }
1063            }
1064            ExpressionOrSuper::Super => {
1065                self.chunk.emit_op(OpCode::Undefined);
1066            }
1067        }
1068    }
1069
1070    fn compile_member_expression(&mut self, member: &MemberExpressionType) {
1071        match member {
1072            MemberExpressionType::SimpleMemberExpression { object, property, .. } => {
1073                match object {
1074                    ExpressionOrSuper::Expression(expr) => {
1075                        self.compile_expression(expr);
1076                    }
1077                    ExpressionOrSuper::Super => {
1078                        self.chunk.emit_op(OpCode::Undefined);
1079                    }
1080                }
1081                let prop_idx = self.chunk.add_name(&property.name);
1082                self.chunk.emit_with(OpCode::GetProp, prop_idx);
1083            }
1084            MemberExpressionType::ComputedMemberExpression { object, property, .. } => {
1085                match object {
1086                    ExpressionOrSuper::Expression(expr) => {
1087                        self.compile_expression(expr);
1088                    }
1089                    ExpressionOrSuper::Super => {
1090                        self.chunk.emit_op(OpCode::Undefined);
1091                    }
1092                }
1093                self.compile_expression(property);
1094                self.chunk.emit_op(OpCode::GetElem);
1095            }
1096        }
1097    }
1098}