Skip to main content

compiler/
compiler.rs

1use object::builtins::BuiltIns;
2use serde::Serialize;
3use std::collections::HashMap;
4use std::rc::Rc;
5
6use object::Object;
7use parser::ast::{
8    BlockStatement, Expression, Literal, MethodDefinition, MethodKind, Node, Statement,
9};
10use parser::lexer::token::Span;
11use parser::lexer::token::TokenKind;
12use parser::validation::validate_program;
13
14use crate::op_code::Opcode::*;
15use crate::op_code::{make_instructions, Instructions, Opcode};
16use crate::symbol_table::{Symbol, SymbolScope, SymbolTable};
17
18struct CompilationScope {
19    instructions: Instructions,
20    last_instruction: EmittedInstruction,
21    previous_instruction: EmittedInstruction,
22    debug_info: DebugInfo,
23}
24
25pub struct Compiler {
26    pub constants: Vec<Rc<Object>>,
27    pub symbol_table: SymbolTable,
28    function_debug_info: HashMap<usize, DebugInfo>,
29    scopes: Vec<CompilationScope>,
30    scope_index: usize,
31    callable_kinds: Vec<CallableKind>,
32}
33
34#[derive(Debug, PartialEq)]
35pub struct Bytecode {
36    pub instructions: Instructions,
37    pub constants: Vec<Rc<Object>>,
38    pub debug_info: DebugInfo,
39    pub function_debug_info: HashMap<usize, DebugInfo>,
40}
41
42#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
43#[serde(rename_all = "camelCase")]
44pub struct PcSpan {
45    pub pc: usize,
46    pub span: Span,
47}
48
49/// One named slot in a frame's locals or the VM's globals.
50#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
51#[serde(rename_all = "camelCase")]
52pub struct BindingDebugInfo {
53    pub name: String,
54    pub slot: usize,
55}
56
57#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
58#[serde(rename_all = "camelCase")]
59pub struct DebugInfo {
60    pub pc_spans: Vec<PcSpan>,
61    /// Parameters (`this` first for methods) then `let`s, strictly increasing
62    /// by slot. Empty for main, whose bindings are the globals.
63    pub local_bindings: Vec<BindingDebugInfo>,
64    /// Captured names aligned with `GcClosure.free` / `OpGetFree` indices.
65    pub free_names: Vec<String>,
66}
67
68#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
69#[serde(tag = "type", rename_all = "camelCase")]
70pub enum InstructionScope {
71    Main,
72    Function { constant_index: usize },
73}
74
75#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
76#[serde(rename_all = "camelCase")]
77pub struct InstructionLineMapping {
78    pub line: usize,
79    pub pc: usize,
80    pub scope: InstructionScope,
81}
82
83#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
84#[serde(rename_all = "camelCase")]
85pub struct BytecodeDebugView {
86    pub detail: String,
87    pub main_debug_info: DebugInfo,
88    pub function_debug_info: HashMap<usize, DebugInfo>,
89    pub instruction_lines: Vec<InstructionLineMapping>,
90}
91
92struct ScopedInstructions {
93    instructions: Instructions,
94    debug_info: DebugInfo,
95}
96
97impl Bytecode {
98    pub fn string(&self) -> String {
99        self.debug_view().detail
100    }
101
102    pub fn debug_view(&self) -> BytecodeDebugView {
103        let mut builder = BytecodeDisplayBuilder::new();
104
105        builder.write_line("Instructions:");
106        for line in self.instructions.string().lines() {
107            builder
108                .write_instruction_line(line, InstructionScope::Main, |line| format!("{line}\n"));
109        }
110
111        builder.write_line("");
112        builder.write_line("Constants:");
113
114        if self.constants.is_empty() {
115            builder.write_line("(none)");
116        } else {
117            for (index, constant) in self.constants.iter().enumerate() {
118                match constant.as_ref() {
119                    Object::CompiledFunction(function) => {
120                        let name = if function.name.is_empty() {
121                            "<anonymous>"
122                        } else {
123                            function.name.as_str()
124                        };
125                        builder.write_line(&format!(
126                            "{index:04} CompiledFunction(name={name}, num_locals={}, num_parameters={})",
127                            function.num_locals,
128                            function.num_parameters
129                        ));
130                        builder.write_line("     Instructions:");
131
132                        let instructions = Instructions {
133                            data: function.instructions.clone(),
134                        };
135                        let scope = InstructionScope::Function {
136                            constant_index: index,
137                        };
138                        for line in instructions.string().lines() {
139                            builder.write_instruction_line(line, scope.clone(), |line| {
140                                format!("       {line}\n")
141                            });
142                        }
143                    }
144                    value => builder.write_line(&format!("{index:04} {value}")),
145                }
146            }
147        }
148
149        BytecodeDebugView {
150            detail: builder.output,
151            main_debug_info: self.debug_info.clone(),
152            function_debug_info: self.function_debug_info.clone(),
153            instruction_lines: builder.instruction_lines,
154        }
155    }
156}
157
158struct BytecodeDisplayBuilder {
159    output: String,
160    line: usize,
161    instruction_lines: Vec<InstructionLineMapping>,
162}
163
164impl BytecodeDisplayBuilder {
165    fn new() -> Self {
166        Self {
167            output: String::new(),
168            line: 0,
169            instruction_lines: vec![],
170        }
171    }
172
173    fn write_line(&mut self, line: &str) {
174        self.output.push_str(line);
175        self.output.push('\n');
176        self.line += 1;
177    }
178
179    fn write_instruction_line(
180        &mut self,
181        raw_line: &str,
182        scope: InstructionScope,
183        format_line: impl FnOnce(&str) -> String,
184    ) {
185        if let Some(pc) = parse_instruction_pc(raw_line) {
186            self.instruction_lines.push(InstructionLineMapping {
187                line: self.line,
188                pc,
189                scope,
190            });
191        }
192
193        self.output.push_str(&format_line(raw_line));
194        self.line += 1;
195    }
196}
197
198/// Splits a statement list at its final run of `debugger` statements, which
199/// are completion-transparent and execute after the block's value is decided.
200fn split_trailing_debuggers(body: &[Statement]) -> (&[Statement], &[Statement]) {
201    let split = body
202        .iter()
203        .rposition(|statement| !matches!(statement, Statement::Debugger(_)))
204        .map_or(0, |index| index + 1);
205    body.split_at(split)
206}
207
208/// Whether a statement contributes the surrounding block's completion value.
209/// This must be decided from the AST: statement-only forms such as property
210/// assignment may end in an implementation-detail `OpNull; OpPop` sequence.
211fn statement_contributes_value(statement: &Statement) -> bool {
212    matches!(statement, Statement::Expr(_))
213}
214
215fn parse_instruction_pc(line: &str) -> Option<usize> {
216    let trimmed = line.trim_start();
217    if trimmed.len() < 4 {
218        return None;
219    }
220
221    let pc_part = &trimmed[..4];
222    if !pc_part.chars().all(|c| c.is_ascii_digit()) {
223        return None;
224    }
225
226    pc_part.parse().ok()
227}
228
229impl DebugInfo {
230    pub fn add_pc_span(&mut self, pc: usize, span: &Span) {
231        if self
232            .pc_spans
233            .last()
234            .map(|last| last.span == *span)
235            .unwrap_or(false)
236        {
237            return;
238        }
239
240        self.pc_spans.push(PcSpan {
241            pc,
242            span: span.clone(),
243        });
244    }
245
246    pub fn span_for_pc(&self, pc: usize) -> Option<&Span> {
247        self.pc_spans
248            .iter()
249            .rev()
250            .find(|pc_span| pc_span.pc <= pc)
251            .map(|pc_span| &pc_span.span)
252    }
253
254    fn truncate_from_pc(&mut self, pc: usize) {
255        self.pc_spans.retain(|pc_span| pc_span.pc < pc);
256    }
257}
258
259#[derive(Clone)]
260pub struct EmittedInstruction {
261    pub opcode: Opcode,
262    pub position: usize,
263}
264
265type CompileError = String;
266
267/// Bytecode operand widths are fixed (u8 / u16). Without these checks,
268/// `make_instructions` silently truncates oversized operands and the VM
269/// reads the wrong slot/constant — a silent miscompile.
270///
271/// The `_count` and `_index` variants exist so the numbers in the error text
272/// are always counts of things the user wrote. Passing a zero-based operand
273/// index to a `_count` helper would report `256 exceeds ... 255` for what is
274/// really the 257th local.
275fn ensure_count(count: usize, max: usize, what: &str) -> Result<(), CompileError> {
276    if count > max {
277        return Err(format!("too many {}: {} exceeds the maximum of {}", what, count, max));
278    }
279    return Ok(());
280}
281
282/// For operands that hold a count directly (call arguments, array elements).
283fn ensure_u8_count(count: usize, what: &str) -> Result<(), CompileError> {
284    return ensure_count(count, u8::MAX as usize, what);
285}
286
287fn ensure_u16_count(count: usize, what: &str) -> Result<(), CompileError> {
288    return ensure_count(count, u16::MAX as usize, what);
289}
290
291/// For operands that hold a zero-based index (locals, globals, constants).
292/// `index` items already exist, so the one being added is number `index + 1`
293/// and the encodable maximum is one more than the largest index.
294fn ensure_u8_index(index: usize, what: &str) -> Result<(), CompileError> {
295    return ensure_count(index + 1, u8::MAX as usize + 1, what);
296}
297
298fn ensure_u16_index(index: usize, what: &str) -> Result<(), CompileError> {
299    return ensure_count(index + 1, u16::MAX as usize + 1, what);
300}
301
302#[derive(Clone, Copy, Debug, Eq, PartialEq)]
303enum CallableKind {
304    Function,
305    Method,
306    Constructor,
307}
308
309impl Default for Compiler {
310    fn default() -> Self {
311        Self::new()
312    }
313}
314
315impl Compiler {
316    pub fn new() -> Compiler {
317        let main_scope = CompilationScope {
318            instructions: Instructions {
319                data: vec![],
320            },
321            last_instruction: EmittedInstruction {
322                opcode: OpNull,
323                position: 0,
324            },
325            previous_instruction: EmittedInstruction {
326                opcode: OpNull,
327                position: 0,
328            },
329            debug_info: DebugInfo::default(),
330        };
331
332        let mut symbol_table = SymbolTable::new();
333        for (key, value) in BuiltIns.iter().enumerate() {
334            symbol_table.define_builtin(key, value.name.to_string());
335        }
336
337        return Compiler {
338            constants: vec![],
339            symbol_table,
340            function_debug_info: HashMap::new(),
341            scopes: vec![main_scope],
342            scope_index: 0,
343            callable_kinds: vec![],
344        };
345    }
346
347    pub fn new_with_state(symbol_table: SymbolTable, constants: Vec<Rc<Object>>) -> Compiler {
348        let mut compiler = Compiler::new();
349        compiler.constants = constants;
350        compiler.symbol_table = symbol_table;
351        return compiler;
352    }
353
354    pub fn compile(&mut self, node: &Node) -> Result<Bytecode, CompileError> {
355        match node {
356            Node::Program(p) => {
357                let mut predefined_names = self.symbol_table.visible_names();
358                predefined_names.extend(BuiltIns.iter().map(|builtin| builtin.name.to_string()));
359                let predefined_names = predefined_names
360                    .iter()
361                    .map(String::as_str)
362                    .collect::<Vec<_>>();
363                validate_program(p, &predefined_names).map_err(|error| error.message)?;
364                for stmt in &p.body {
365                    self.compile_stmt(stmt)?;
366                }
367            }
368            Node::Statement(s) => {
369                self.compile_stmt(s)?;
370            }
371            Node::Expression(e) => {
372                self.compile_expr(e)?;
373            }
374        }
375
376        return Ok(self.bytecode());
377    }
378
379    fn compile_stmt(&mut self, s: &Statement) -> Result<(), CompileError> {
380        match s {
381            Statement::Let(let_statement) => {
382                // A rebinding's RHS resolves against the preceding lexical
383                // environment. Named recursion is provided by Function scope
384                // inside the function body, not by an uninitialized slot.
385                self.compile_expr(&let_statement.expr)?;
386                let symbol = self.define_symbol(let_statement.identifier.name.clone())?;
387                if symbol.scope == SymbolScope::Global {
388                    self.emit_with_span(Opcode::OpSetGlobal, &[symbol.index], &let_statement.span);
389                } else {
390                    self.emit_with_span(Opcode::OpSetLocal, &[symbol.index], &let_statement.span);
391                }
392                return Ok(());
393            }
394            Statement::Return(r) => {
395                if self.callable_kinds.last() == Some(&CallableKind::Constructor) {
396                    return Err("constructor cannot return a value".to_string());
397                }
398                self.compile_expr(&r.argument)?;
399                self.emit_with_span(Opcode::OpReturnValue, &[], &r.span);
400                return Ok(());
401            }
402            Statement::Expr(e) => {
403                self.compile_expr(e)?;
404                self.emit_with_span(OpPop, &[], e.span());
405                return Ok(());
406            }
407            Statement::Class(class) => {
408                let symbol = self.define_symbol(class.name.name.clone())?;
409                let class_name = self.try_add_constant(Object::String(class.name.name.clone()))?;
410                self.emit_with_span(OpClass, &[class_name], &class.span);
411
412                for method in &class.methods {
413                    self.compile_method(&class.name.name, method)?;
414                    let method_name =
415                        self.try_add_constant(Object::String(method.name.name.clone()))?;
416                    let kind = match method.kind {
417                        MethodKind::Method => 0,
418                        MethodKind::Constructor => 1,
419                    };
420                    self.emit_with_span(OpMethod, &[method_name, kind], &method.span);
421                }
422
423                self.emit_with_span(OpSetGlobal, &[symbol.index], &class.span);
424                self.emit_with_span(OpNull, &[], &class.span);
425                self.emit_with_span(OpPop, &[], &class.span);
426                Ok(())
427            }
428            Statement::SetProperty(statement) => {
429                self.compile_expr(&statement.object)?;
430                self.compile_expr(&statement.value)?;
431                let property =
432                    self.try_add_constant(Object::String(statement.property.name.clone()))?;
433                self.emit_with_span(OpSetProperty, &[property], &statement.span);
434                self.emit_with_span(OpNull, &[], &statement.span);
435                self.emit_with_span(OpPop, &[], &statement.span);
436                Ok(())
437            }
438            Statement::Debugger(statement) => {
439                self.emit_with_span(OpDebugger, &[], &statement.span);
440                Ok(())
441            }
442        }
443    }
444
445    fn compile_expr(&mut self, e: &Expression) -> Result<(), CompileError> {
446        match e {
447            Expression::IDENTIFIER(identifier) => {
448                let symbol = self.symbol_table.resolve(identifier.name.clone());
449                match symbol {
450                    Some(symbol) => {
451                        self.load_symbol(&symbol, &identifier.span)?;
452                    }
453                    None => {
454                        return Err(format!("Undefined variable '{}'", identifier.name));
455                    }
456                }
457            }
458            Expression::LITERAL(l) => match l {
459                Literal::Integer(i) => {
460                    let int = Object::Integer(i.raw);
461                    let operands = vec![self.try_add_constant(int)?];
462                    self.emit_with_span(OpConst, &operands, &i.span);
463                }
464                Literal::Boolean(i) => {
465                    if i.raw {
466                        self.emit_with_span(OpTrue, &[], &i.span);
467                    } else {
468                        self.emit_with_span(OpFalse, &[], &i.span);
469                    }
470                }
471                Literal::String(s) => {
472                    let string_object = Object::String(s.raw.clone());
473                    let operands = vec![self.try_add_constant(string_object)?];
474                    self.emit_with_span(OpConst, &operands, &s.span);
475                }
476                Literal::Array(array) => {
477                    for element in array.elements.iter() {
478                        self.compile_expr(element)?;
479                    }
480                    ensure_u16_count(array.elements.len(), "array elements")?;
481                    self.emit_with_span(OpArray, &[array.elements.len()], &array.span);
482                }
483                Literal::Hash(hash) => {
484                    for (key, value) in hash.elements.iter() {
485                        self.compile_expr(key)?;
486                        self.compile_expr(value)?;
487                    }
488                    // OpHash counts keys and values, so the encodable operand is
489                    // always even and the last usable one is u16::MAX - 1. Check
490                    // the pair count the user actually wrote, not the doubled
491                    // operand, or the message reports twice the real limit.
492                    ensure_count(hash.elements.len(), u16::MAX as usize / 2, "hash pairs")?;
493                    self.emit_with_span(OpHash, &[hash.elements.len() * 2], &hash.span);
494                }
495            },
496            Expression::PREFIX(prefix) => {
497                self.compile_expr(&prefix.operand)?;
498                match prefix.op.kind {
499                    TokenKind::MINUS => {
500                        self.emit_with_span(OpMinus, &[], &prefix.span);
501                    }
502                    TokenKind::BANG => {
503                        self.emit_with_span(OpBang, &[], &prefix.span);
504                    }
505                    _ => {
506                        return Err(format!("unexpected prefix op: {}", prefix.op));
507                    }
508                }
509            }
510            Expression::INFIX(infix) => {
511                self.compile_expr(&infix.left)?;
512                self.compile_expr(&infix.right)?;
513                match infix.op.kind {
514                    TokenKind::PLUS => {
515                        self.emit_with_span(OpAdd, &[], &infix.span);
516                    }
517                    TokenKind::MINUS => {
518                        self.emit_with_span(OpSub, &[], &infix.span);
519                    }
520                    TokenKind::ASTERISK => {
521                        self.emit_with_span(OpMul, &[], &infix.span);
522                    }
523                    TokenKind::SLASH => {
524                        self.emit_with_span(OpDiv, &[], &infix.span);
525                    }
526                    TokenKind::GT => {
527                        self.emit_with_span(Opcode::OpGreaterThan, &[], &infix.span);
528                    }
529                    TokenKind::LT => {
530                        self.emit_with_span(Opcode::OpLessThan, &[], &infix.span);
531                    }
532                    TokenKind::EQ => {
533                        self.emit_with_span(Opcode::OpEqual, &[], &infix.span);
534                    }
535                    TokenKind::NotEq => {
536                        self.emit_with_span(Opcode::OpNotEqual, &[], &infix.span);
537                    }
538                    _ => {
539                        return Err(format!("unexpected infix op: {}", infix.op));
540                    }
541                }
542            }
543            Expression::IF(if_node) => {
544                self.compile_expr(&if_node.condition)?;
545                let jump_not_truthy = self.emit_with_span(OpJumpNotTruthy, &[9527], &if_node.span);
546                self.compile_block_statement_as_value(&if_node.consequent)?;
547
548                let jump_pos = self.emit_with_span(OpJump, &[9527], &if_node.span);
549
550                let after_consequence_location = self.current_instruction().data.len();
551                self.change_operand(jump_not_truthy, after_consequence_location)?;
552
553                if let Some(alternate) = &if_node.alternate {
554                    self.compile_block_statement_as_value(alternate)?;
555                } else {
556                    self.emit_with_span(OpNull, &[], &if_node.span);
557                }
558                let after_alternative_location = self.current_instruction().data.len();
559                self.change_operand(jump_pos, after_alternative_location)?;
560            }
561            Expression::Index(index) => {
562                self.compile_expr(&index.object)?;
563                self.compile_expr(&index.index)?;
564                self.emit_with_span(OpIndex, &[], &index.span);
565            }
566            Expression::FUNCTION(f) => {
567                let function_span = f.span.clone();
568                self.enter_scope();
569                self.callable_kinds.push(CallableKind::Function);
570                if !f.name.is_empty() {
571                    self.symbol_table.define_function_name(f.name.clone());
572                }
573                for param in f.params.iter() {
574                    self.define_symbol(param.identifier.name.clone())?;
575                }
576                self.compile_function_body(&f.body, &function_span)?;
577                let num_locals = self.symbol_table.num_definitions;
578                let free_symbols = self.symbol_table.free_symbols.clone();
579                let scoped_instructions = self.leave_scope();
580                self.callable_kinds.pop();
581                // Checked before the loads so the count is the only free-variable
582                // limit a user can hit. OpGetFree's u8 slot allows one more than
583                // OpClosure's u8 count does, and reporting that larger number
584                // would name a limit no closure can actually reach.
585                ensure_u8_count(free_symbols.len(), "free variables")?;
586                for x in free_symbols.clone() {
587                    self.load_symbol(&x, &function_span)?;
588                }
589
590                let compiled_function = Rc::from(object::CompiledFunction {
591                    name: f.name.clone(),
592                    instructions: scoped_instructions.instructions.data,
593                    num_locals,
594                    num_parameters: f.params.len(),
595                });
596
597                let constant_index =
598                    self.try_add_constant(Object::CompiledFunction(compiled_function))?;
599                self.function_debug_info_mut()
600                    .insert(constant_index, scoped_instructions.debug_info);
601                let operands = vec![constant_index, free_symbols.len()];
602                self.emit_with_span(OpClosure, &operands, &function_span);
603            }
604            Expression::FunctionCall(fc) => {
605                self.compile_expr(&fc.callee)?;
606                for arg in fc.arguments.iter() {
607                    self.compile_expr(arg)?;
608                }
609                ensure_u8_count(fc.arguments.len(), "call arguments")?;
610                self.emit_with_span(OpCall, &[fc.arguments.len()], &fc.span);
611            }
612            Expression::This(this) => {
613                let symbol = self
614                    .symbol_table
615                    .resolve("this".to_string())
616                    .ok_or_else(|| "this is only available inside a method".to_string())?;
617                self.load_symbol(&symbol, &this.span)?;
618            }
619            Expression::Property(property) => {
620                self.compile_expr(&property.object)?;
621                let name = self.try_add_constant(Object::String(property.property.name.clone()))?;
622                self.emit_with_span(OpGetProperty, &[name], &property.span);
623            }
624            Expression::New(new_expression) => {
625                let symbol = self
626                    .symbol_table
627                    .resolve(new_expression.callee.name.clone())
628                    .ok_or_else(|| {
629                        format!("Undefined variable '{}'", new_expression.callee.name)
630                    })?;
631                self.load_symbol(&symbol, &new_expression.callee.span)?;
632                for argument in &new_expression.arguments {
633                    self.compile_expr(argument)?;
634                }
635                ensure_u8_count(new_expression.arguments.len(), "constructor arguments")?;
636                self.emit_with_span(OpNew, &[new_expression.arguments.len()], &new_expression.span);
637            }
638        }
639
640        return Ok(());
641    }
642
643    /// Only the Free arm can fail: locals and globals were bounded by
644    /// `define_symbol`, builtins come from a fixed table, and Function is
645    /// always slot 0.
646    fn load_symbol(&mut self, symbol: &Rc<Symbol>, span: &Span) -> Result<(), CompileError> {
647        match symbol.scope {
648            SymbolScope::Global => {
649                self.emit_with_span(OpGetGlobal, &[symbol.index], span);
650            }
651            SymbolScope::LOCAL => {
652                self.emit_with_span(OpGetLocal, &[symbol.index], span);
653            }
654            SymbolScope::Builtin => {
655                self.emit_with_span(OpGetBuiltin, &[symbol.index], span);
656            }
657            SymbolScope::Free => {
658                // Free slots are assigned by resolve() while the body compiles,
659                // so this fires long before the capture list is emitted. Check
660                // it against OpClosure's u8 *count*, which caps at 255, not
661                // against OpGetFree's u8 slot, which would allow one more than
662                // any closure can actually carry.
663                ensure_u8_count(symbol.index + 1, "free variables")?;
664                self.emit_with_span(OpGetFree, &[symbol.index], span);
665            }
666            SymbolScope::Function => {
667                self.emit_with_span(OpCurrentClosure, &[], span);
668            }
669        }
670        return Ok(());
671    }
672
673    pub fn bytecode(&self) -> Bytecode {
674        return Bytecode {
675            instructions: self.current_instruction().clone(),
676            constants: self.constants.clone(),
677            debug_info: self.current_debug_info().clone(),
678            function_debug_info: self.function_debug_info.clone(),
679        };
680    }
681
682    fn define_symbol(&mut self, name: String) -> Result<Rc<Symbol>, CompileError> {
683        let symbol = self.symbol_table.define(name);
684        match symbol.scope {
685            SymbolScope::LOCAL => ensure_u8_index(symbol.index, "locals")?,
686            SymbolScope::Global => ensure_u16_index(symbol.index, "globals")?,
687            // Builtin indexes come from a fixed compile-time table well under
688            // u8::MAX, Function is always 0, and Free is assigned by resolve()
689            // rather than here — bounded by the OpClosure capture count.
690            SymbolScope::Builtin | SymbolScope::Free | SymbolScope::Function => {}
691        }
692        Ok(symbol)
693    }
694
695    /// Global slots in slot order, one entry per definition — a rebound name
696    /// appears once for every slot it ever occupied.
697    pub fn global_bindings(&self) -> Vec<BindingDebugInfo> {
698        self.symbol_table
699            .global_definitions()
700            .iter()
701            .map(|symbol| BindingDebugInfo {
702                name: symbol.name.clone(),
703                slot: symbol.index,
704            })
705            .collect()
706    }
707
708    /// Kept infallible for the published 1.1.0 signature. Prefer
709    /// [`Compiler::try_add_constant`], which rejects a pool too large for
710    /// `OpConst`'s u16 operand instead of handing back an index that truncates.
711    pub fn add_constant(&mut self, obj: Object) -> usize {
712        self.constants.push(Rc::new(obj));
713        return self.constants.len() - 1;
714    }
715
716    pub fn try_add_constant(&mut self, obj: Object) -> Result<usize, CompileError> {
717        ensure_u16_index(self.constants.len(), "constants")?;
718        return Ok(self.add_constant(obj));
719    }
720
721    pub fn emit(&mut self, op: Opcode, operands: &[usize]) -> usize {
722        let ins = make_instructions(op, operands);
723        let pos = self.add_instructions(&ins);
724        self.set_last_instruction(op, pos);
725
726        return pos;
727    }
728
729    pub fn emit_with_span(&mut self, op: Opcode, operands: &[usize], span: &Span) -> usize {
730        let pos = self.emit(op, operands);
731        self.add_pc_span(pos, span);
732        pos
733    }
734
735    fn compile_block_statement(
736        &mut self,
737        block_statement: &BlockStatement,
738    ) -> Result<(), CompileError> {
739        for stmt in &block_statement.body {
740            self.compile_stmt(stmt)?;
741        }
742        Ok(())
743    }
744
745    fn compile_block_statement_as_value(
746        &mut self,
747        block_statement: &BlockStatement,
748    ) -> Result<(), CompileError> {
749        // Trailing `debugger` statements are completion-transparent: the
750        // block's value (or null) is decided before they execute, and
751        // OpDebugger leaves the stack untouched, so a kept value stays on top.
752        let (leading, trailing_debuggers) = split_trailing_debuggers(&block_statement.body);
753        let has_value = leading.last().is_some_and(statement_contributes_value);
754        for stmt in leading {
755            self.compile_stmt(stmt)?;
756        }
757        // A block in expression position must leave one value on every
758        // fallthrough path. Statement-only and empty blocks evaluate to null.
759        if has_value {
760            debug_assert!(self.last_instruction_is(OpPop));
761            self.remove_last_pop();
762        }
763        for stmt in trailing_debuggers {
764            self.compile_stmt(stmt)?;
765        }
766        if !has_value {
767            self.emit_with_span(OpNull, &[], &block_statement.span);
768        }
769        Ok(())
770    }
771
772    /// Compiles a function or method body plus its implicit return. Trailing
773    /// `debugger` statements must not break the "last expression statement is
774    /// the return value" rule, so the value is unpopped before they execute
775    /// and returned after them.
776    fn compile_function_body(
777        &mut self,
778        body: &BlockStatement,
779        span: &Span,
780    ) -> Result<(), CompileError> {
781        let (leading, trailing_debuggers) = split_trailing_debuggers(&body.body);
782        if trailing_debuggers.is_empty() {
783            self.compile_block_statement(body)?;
784            if self.last_instruction_is(OpPop) {
785                self.replace_last_pop_with_return();
786            }
787            if !(self.last_instruction_is(OpReturnValue)) {
788                self.emit_with_span(OpReturn, &[], span);
789            }
790            return Ok(());
791        }
792
793        let produced_value = leading.last().is_some_and(statement_contributes_value);
794        for stmt in leading {
795            self.compile_stmt(stmt)?;
796        }
797        if produced_value {
798            debug_assert!(self.last_instruction_is(OpPop));
799            self.remove_last_pop();
800        }
801        for stmt in trailing_debuggers {
802            self.compile_stmt(stmt)?;
803        }
804        if produced_value {
805            self.emit_with_span(OpReturnValue, &[], span);
806        } else {
807            self.emit_with_span(OpReturn, &[], span);
808        }
809        Ok(())
810    }
811
812    fn compile_method(
813        &mut self,
814        class_name: &str,
815        method: &MethodDefinition,
816    ) -> Result<(), CompileError> {
817        let method_span = method.span.clone();
818        self.enter_scope();
819        let callable_kind = match method.kind {
820            MethodKind::Method => CallableKind::Method,
821            MethodKind::Constructor => CallableKind::Constructor,
822        };
823        self.callable_kinds.push(callable_kind);
824
825        self.define_symbol("this".to_string())?;
826        for parameter in &method.params {
827            self.define_symbol(parameter.identifier.name.clone())?;
828        }
829
830        match method.kind {
831            MethodKind::Constructor => {
832                // A trailing debugger needs no special handling here: the
833                // constructor's `this` return is appended after the body.
834                self.compile_block_statement(&method.body)?;
835                self.emit_with_span(OpGetLocal, &[0], &method_span);
836                self.emit_with_span(OpReturnValue, &[], &method_span);
837            }
838            MethodKind::Method => {
839                self.compile_function_body(&method.body, &method_span)?;
840            }
841        }
842
843        let num_locals = self.symbol_table.num_definitions;
844        let free_symbols = self.symbol_table.free_symbols.clone();
845        let scoped_instructions = self.leave_scope();
846        self.callable_kinds.pop();
847        ensure_u8_count(free_symbols.len(), "free variables")?;
848        for symbol in &free_symbols {
849            self.load_symbol(symbol, &method_span)?;
850        }
851
852        let compiled_function = Rc::new(object::CompiledFunction {
853            name: format!("{}.{}", class_name, method.name.name),
854            instructions: scoped_instructions.instructions.data,
855            num_locals,
856            num_parameters: method.params.len() + 1,
857        });
858        let constant_index = self.try_add_constant(Object::CompiledFunction(compiled_function))?;
859        self.function_debug_info_mut()
860            .insert(constant_index, scoped_instructions.debug_info);
861        self.emit_with_span(OpClosure, &[constant_index, free_symbols.len()], &method_span);
862        Ok(())
863    }
864
865    pub fn add_instructions(&mut self, ins: &Instructions) -> usize {
866        let pos = self.current_instruction().data.len();
867        let updated_ins = self.scopes[self.scope_index]
868            .instructions
869            .merge_instructions(ins);
870        self.scopes[self.scope_index].instructions = updated_ins;
871        return pos;
872    }
873
874    fn set_last_instruction(&mut self, op: Opcode, pos: usize) {
875        let previous_instruction = self.scopes[self.scope_index].last_instruction.clone();
876        let last_instruction = EmittedInstruction {
877            opcode: op,
878            position: pos,
879        };
880        self.scopes[self.scope_index].last_instruction = last_instruction;
881        self.scopes[self.scope_index].previous_instruction = previous_instruction;
882    }
883
884    fn last_instruction_is(&self, op: Opcode) -> bool {
885        if self.current_instruction().data.is_empty() {
886            return false;
887        }
888        return self.scopes[self.scope_index].last_instruction.opcode == op;
889    }
890
891    fn remove_last_pop(&mut self) {
892        let last = self.scopes[self.scope_index].last_instruction.clone();
893        let previous = self.scopes[self.scope_index].previous_instruction.clone();
894
895        let old = self.current_instruction().data.clone();
896        let new = old[..last.position].to_vec();
897
898        self.scopes[self.scope_index].instructions.data = new;
899        self.scopes[self.scope_index]
900            .debug_info
901            .truncate_from_pc(last.position);
902        self.scopes[self.scope_index].last_instruction = previous;
903    }
904
905    fn replace_instruction(&mut self, pos: usize, new_instruction: &Instructions) {
906        let ins = &mut self.scopes[self.scope_index].instructions;
907        for i in 0..new_instruction.data.len() {
908            ins.data[pos + i] = new_instruction.data[i];
909        }
910    }
911
912    fn replace_last_pop_with_return(&mut self) {
913        let last_pos = self.scopes[self.scope_index].last_instruction.position;
914        self.replace_instruction(last_pos, &make_instructions(OpReturnValue, &[]));
915        self.scopes[self.scope_index].last_instruction.opcode = OpReturnValue;
916    }
917
918    fn change_operand(&mut self, pos: usize, operand: usize) -> Result<(), CompileError> {
919        // Jump operands are byte offsets into the enclosing instruction stream,
920        // not a count of anything the user wrote, so they get their own message.
921        if operand > u16::MAX as usize {
922            return Err(format!(
923                "compiled code too large: jump target at byte {} is outside the {}-byte range of a jump operand",
924                operand,
925                u16::MAX
926            ));
927        }
928        let op = Opcode::from_repr(self.current_instruction().data[pos])
929            .expect("compiler emitted an unknown opcode");
930        let ins = make_instructions(op, &[operand]);
931        self.replace_instruction(pos, &ins);
932        Ok(())
933    }
934
935    fn current_instruction(&self) -> &Instructions {
936        return &self.scopes[self.scope_index].instructions;
937    }
938
939    fn current_debug_info(&self) -> &DebugInfo {
940        return &self.scopes[self.scope_index].debug_info;
941    }
942
943    fn function_debug_info_mut(&mut self) -> &mut HashMap<usize, DebugInfo> {
944        return &mut self.function_debug_info;
945    }
946
947    fn add_pc_span(&mut self, pc: usize, span: &Span) {
948        self.scopes[self.scope_index]
949            .debug_info
950            .add_pc_span(pc, span);
951    }
952
953    fn enter_scope(&mut self) {
954        let scope = CompilationScope {
955            instructions: Instructions {
956                data: vec![],
957            },
958            last_instruction: EmittedInstruction {
959                opcode: OpNull,
960                position: 0,
961            },
962            previous_instruction: EmittedInstruction {
963                opcode: OpNull,
964                position: 0,
965            },
966            debug_info: DebugInfo::default(),
967        };
968        self.scopes.push(scope);
969        self.scope_index += 1;
970        self.symbol_table = SymbolTable::new_enclosed_symbol_table(self.symbol_table.clone());
971    }
972
973    fn leave_scope(&mut self) -> ScopedInstructions {
974        let instructions = self.current_instruction().clone();
975        let mut debug_info = self.current_debug_info().clone();
976        // The scope's definition ledger is final here: `definitions[i].index == i`,
977        // so the copied bindings come out strictly increasing by slot.
978        debug_info.local_bindings = self
979            .symbol_table
980            .definitions
981            .iter()
982            .map(|symbol| BindingDebugInfo {
983                name: symbol.name.clone(),
984                slot: symbol.index,
985            })
986            .collect();
987        debug_info.free_names = self
988            .symbol_table
989            .free_symbols
990            .iter()
991            .map(|symbol| symbol.name.clone())
992            .collect();
993        self.scopes.pop();
994        self.scope_index -= 1;
995        let s = self.symbol_table.outer.as_ref().unwrap().as_ref().clone();
996        self.symbol_table = s;
997        return ScopedInstructions {
998            instructions,
999            debug_info,
1000        };
1001    }
1002}