Skip to main content

sema_vm/
compiler.rs

1use sema_core::{intern, SemaError, Spur, Value};
2
3use crate::chunk::{Chunk, ExceptionEntry, Function, UpvalueDesc};
4use crate::core_expr::{
5    ResolvedDoLoop, ResolvedExpr, ResolvedLambda, ResolvedPromptEntry, VarRef, VarResolution,
6};
7use crate::emit::Emitter;
8use crate::opcodes::Op;
9
10/// Result of compiling a top-level expression.
11pub struct CompileResult {
12    /// The top-level chunk to execute.
13    pub chunk: Chunk,
14    /// All compiled function templates (referenced by MakeClosure func_id).
15    pub functions: Vec<Function>,
16}
17
18/// Maximum recursion depth for the compiler.
19/// This prevents native stack overflow from deeply nested expressions.
20const MAX_COMPILE_DEPTH: usize = 256;
21
22/// Compile a resolved expression tree into bytecode.
23pub fn compile(expr: &ResolvedExpr) -> Result<CompileResult, SemaError> {
24    compile_with_locals(expr, 0)
25}
26
27/// Compile a resolved expression tree into bytecode with a known top-level local count.
28pub fn compile_with_locals(expr: &ResolvedExpr, n_locals: u16) -> Result<CompileResult, SemaError> {
29    let mut compiler = Compiler::new();
30    compiler.n_locals = n_locals;
31    compiler.compile_expr(expr)?;
32    compiler.emit.emit_op(Op::Return);
33    let (chunk, functions) = compiler.finish();
34    Ok(CompileResult { chunk, functions })
35}
36
37/// Compile multiple top-level expressions into a single chunk.
38pub fn compile_many(exprs: &[ResolvedExpr]) -> Result<CompileResult, SemaError> {
39    compile_many_with_locals(exprs, 0)
40}
41
42/// Compile multiple top-level expressions with a known top-level local count.
43pub fn compile_many_with_locals(
44    exprs: &[ResolvedExpr],
45    n_locals: u16,
46) -> Result<CompileResult, SemaError> {
47    let mut compiler = Compiler::new();
48    compiler.n_locals = n_locals;
49    for (i, expr) in exprs.iter().enumerate() {
50        compiler.compile_expr(expr)?;
51        if i < exprs.len() - 1 {
52            compiler.emit.emit_op(Op::Pop);
53        }
54    }
55    if exprs.is_empty() {
56        compiler.emit.emit_op(Op::Nil);
57    }
58    compiler.emit.emit_op(Op::Return);
59    let (chunk, functions) = compiler.finish();
60    Ok(CompileResult { chunk, functions })
61}
62
63/// Walk bytecode and add `offset` to all MakeClosure func_id operands.
64fn patch_closure_func_ids(chunk: &mut Chunk, offset: u16) {
65    let code = &mut chunk.code;
66    let mut pc = 0;
67    while pc < code.len() {
68        let Some(op) = Op::from_u8(code[pc]) else {
69            break;
70        };
71        match op {
72            Op::MakeClosure => {
73                // func_id is at pc+1..pc+3 (u16 LE)
74                let old = u16::from_le_bytes([code[pc + 1], code[pc + 2]]);
75                let new = old + offset;
76                let bytes = new.to_le_bytes();
77                code[pc + 1] = bytes[0];
78                code[pc + 2] = bytes[1];
79                // n_upvalues at pc+3..pc+5
80                let n_upvalues = u16::from_le_bytes([code[pc + 3], code[pc + 4]]) as usize;
81                // Skip: op(1) + func_id(2) + n_upvalues(2) + n_upvalues * (is_local(2) + idx(2))
82                pc += 1 + 2 + 2 + n_upvalues * 4;
83            }
84            // Variable-length instructions: skip op + operands
85            Op::Const
86            | Op::LoadLocal
87            | Op::StoreLocal
88            | Op::LoadUpvalue
89            | Op::StoreUpvalue
90            | Op::Call
91            | Op::TailCall
92            | Op::MakeList
93            | Op::MakeVector
94            | Op::MakeMap
95            | Op::MakeHashMap => {
96                pc += 1 + 2; // op + u16
97            }
98            Op::CallNative => {
99                pc += 1 + 2 + 2; // op + u16 native_id + u16 argc
100            }
101            Op::LoadGlobal | Op::StoreGlobal | Op::DefineGlobal => {
102                pc += 1 + 4; // op + u32
103            }
104            Op::Jump | Op::JumpIfFalse | Op::JumpIfTrue => {
105                pc += 1 + 4; // op + i32
106            }
107            // Single-byte instructions
108            _ => {
109                pc += 1;
110            }
111        }
112    }
113}
114
115struct Compiler {
116    emit: Emitter,
117    functions: Vec<Function>,
118    exception_entries: Vec<ExceptionEntry>,
119    n_locals: u16,
120    depth: usize,
121}
122
123impl Compiler {
124    fn new() -> Self {
125        Compiler {
126            emit: Emitter::new(),
127            functions: Vec::new(),
128            exception_entries: Vec::new(),
129            n_locals: 0,
130            depth: 0,
131        }
132    }
133
134    fn finish(self) -> (Chunk, Vec<Function>) {
135        let mut chunk = self.emit.into_chunk();
136        chunk.n_locals = self.n_locals;
137        chunk.exception_table = self.exception_entries;
138        (chunk, self.functions)
139    }
140
141    fn compile_expr(&mut self, expr: &ResolvedExpr) -> Result<(), SemaError> {
142        self.depth += 1;
143        if self.depth > MAX_COMPILE_DEPTH {
144            self.depth -= 1;
145            return Err(SemaError::eval("maximum compilation depth exceeded"));
146        }
147        let result = self.compile_expr_inner(expr);
148        self.depth -= 1;
149        result
150    }
151
152    fn compile_expr_inner(&mut self, expr: &ResolvedExpr) -> Result<(), SemaError> {
153        match expr {
154            ResolvedExpr::Const(val) => self.compile_const(val),
155            ResolvedExpr::Var(vr) => self.compile_var_load(vr),
156            ResolvedExpr::If { test, then, else_ } => self.compile_if(test, then, else_),
157            ResolvedExpr::Begin(exprs) => self.compile_begin(exprs),
158            ResolvedExpr::Set(vr, val) => self.compile_set(vr, val),
159            ResolvedExpr::Lambda(def) => self.compile_lambda(def),
160            ResolvedExpr::Call { func, args, tail } => self.compile_call(func, args, *tail),
161            ResolvedExpr::Define(spur, val) => self.compile_define(*spur, val),
162            ResolvedExpr::Let { bindings, body } => self.compile_let(bindings, body),
163            ResolvedExpr::LetStar { bindings, body } => self.compile_let_star(bindings, body),
164            ResolvedExpr::Letrec { bindings, body } => self.compile_letrec(bindings, body),
165            ResolvedExpr::NamedLet {
166                name,
167                bindings,
168                body,
169            } => self.compile_named_let(name, bindings, body),
170            ResolvedExpr::Do(do_loop) => self.compile_do(do_loop),
171            ResolvedExpr::Try {
172                body,
173                catch_var,
174                handler,
175            } => self.compile_try(body, catch_var, handler),
176            ResolvedExpr::Throw(val) => self.compile_throw(val),
177            ResolvedExpr::And(exprs) => self.compile_and(exprs),
178            ResolvedExpr::Or(exprs) => self.compile_or(exprs),
179            ResolvedExpr::Quote(val) => self.compile_const(val),
180            ResolvedExpr::MakeList(exprs) => self.compile_make_list(exprs),
181            ResolvedExpr::MakeVector(exprs) => self.compile_make_vector(exprs),
182            ResolvedExpr::MakeMap(pairs) => self.compile_make_map(pairs),
183            ResolvedExpr::Defmacro {
184                name,
185                params,
186                rest,
187                body,
188            } => self.compile_defmacro(*name, params, rest, body),
189            ResolvedExpr::DefineRecordType {
190                type_name,
191                ctor_name,
192                pred_name,
193                field_names,
194                field_specs,
195            } => self.compile_define_record_type(
196                *type_name,
197                *ctor_name,
198                *pred_name,
199                field_names,
200                field_specs,
201            ),
202            ResolvedExpr::Module {
203                name,
204                exports,
205                body,
206            } => self.compile_module(*name, exports, body),
207            ResolvedExpr::Import { path, selective } => self.compile_import(path, selective),
208            ResolvedExpr::Load(path) => self.compile_load(path),
209            ResolvedExpr::Eval(expr) => self.compile_eval(expr),
210            ResolvedExpr::Prompt(entries) => self.compile_prompt(entries),
211            ResolvedExpr::Message { role, parts } => self.compile_message(role, parts),
212            ResolvedExpr::Deftool {
213                name,
214                description,
215                parameters,
216                handler,
217            } => self.compile_deftool(*name, description, parameters, handler),
218            ResolvedExpr::Defagent { name, options } => self.compile_defagent(*name, options),
219            ResolvedExpr::Delay(expr) => self.compile_delay(expr),
220            ResolvedExpr::Force(expr) => self.compile_force(expr),
221            ResolvedExpr::Macroexpand(expr) => self.compile_macroexpand(expr),
222        }
223    }
224
225    // --- Constants ---
226
227    fn compile_const(&mut self, val: &Value) -> Result<(), SemaError> {
228        if val.is_nil() {
229            self.emit.emit_op(Op::Nil);
230        } else if val.as_bool() == Some(true) {
231            self.emit.emit_op(Op::True);
232        } else if val.as_bool() == Some(false) {
233            self.emit.emit_op(Op::False);
234        } else {
235            self.emit.emit_const(val.clone());
236        }
237        Ok(())
238    }
239
240    // --- Variable access ---
241
242    fn compile_var_load(&mut self, vr: &VarRef) -> Result<(), SemaError> {
243        match vr.resolution {
244            VarResolution::Local { slot } => match slot {
245                0 => self.emit.emit_op(Op::LoadLocal0),
246                1 => self.emit.emit_op(Op::LoadLocal1),
247                2 => self.emit.emit_op(Op::LoadLocal2),
248                3 => self.emit.emit_op(Op::LoadLocal3),
249                _ => {
250                    self.emit.emit_op(Op::LoadLocal);
251                    self.emit.emit_u16(slot);
252                }
253            },
254            VarResolution::Upvalue { index } => {
255                self.emit.emit_op(Op::LoadUpvalue);
256                self.emit.emit_u16(index);
257            }
258            VarResolution::Global { spur } => {
259                self.emit.emit_op(Op::LoadGlobal);
260                self.emit.emit_u32(spur_to_u32(spur));
261            }
262        }
263        Ok(())
264    }
265
266    fn compile_var_store(&mut self, vr: &VarRef) {
267        match vr.resolution {
268            VarResolution::Local { slot } => {
269                self.emit.emit_op(Op::StoreLocal);
270                self.emit.emit_u16(slot);
271            }
272            VarResolution::Upvalue { index } => {
273                self.emit.emit_op(Op::StoreUpvalue);
274                self.emit.emit_u16(index);
275            }
276            VarResolution::Global { spur } => {
277                self.emit.emit_op(Op::StoreGlobal);
278                self.emit.emit_u32(spur_to_u32(spur));
279            }
280        }
281    }
282
283    // --- Control flow ---
284
285    fn compile_if(
286        &mut self,
287        test: &ResolvedExpr,
288        then: &ResolvedExpr,
289        else_: &ResolvedExpr,
290    ) -> Result<(), SemaError> {
291        self.compile_expr(test)?;
292        let else_jump = self.emit.emit_jump(Op::JumpIfFalse);
293        self.compile_expr(then)?;
294        let end_jump = self.emit.emit_jump(Op::Jump);
295        self.emit.patch_jump(else_jump);
296        self.compile_expr(else_)?;
297        self.emit.patch_jump(end_jump);
298        Ok(())
299    }
300
301    fn compile_begin(&mut self, exprs: &[ResolvedExpr]) -> Result<(), SemaError> {
302        if exprs.is_empty() {
303            self.emit.emit_op(Op::Nil);
304            return Ok(());
305        }
306        for (i, expr) in exprs.iter().enumerate() {
307            self.compile_expr(expr)?;
308            if i < exprs.len() - 1 {
309                self.emit.emit_op(Op::Pop);
310            }
311        }
312        Ok(())
313    }
314
315    // --- Assignment ---
316
317    fn compile_set(&mut self, vr: &VarRef, val: &ResolvedExpr) -> Result<(), SemaError> {
318        self.compile_expr(val)?;
319        self.emit.emit_op(Op::Dup); // set! returns the value
320        self.compile_var_store(vr);
321        Ok(())
322    }
323
324    fn compile_define(&mut self, spur: Spur, val: &ResolvedExpr) -> Result<(), SemaError> {
325        self.compile_expr(val)?;
326        self.emit.emit_op(Op::DefineGlobal);
327        self.emit.emit_u32(spur_to_u32(spur));
328        self.emit.emit_op(Op::Nil); // define returns nil
329        Ok(())
330    }
331
332    // --- Lambda ---
333
334    fn compile_lambda(&mut self, def: &ResolvedLambda) -> Result<(), SemaError> {
335        // Compile the lambda body into a separate function
336        let mut inner = Compiler::new();
337        inner.n_locals = def.n_locals;
338
339        // Compile body
340        if def.body.is_empty() {
341            inner.emit.emit_op(Op::Nil);
342        } else {
343            for (i, expr) in def.body.iter().enumerate() {
344                inner.compile_expr(expr)?;
345                if i < def.body.len() - 1 {
346                    inner.emit.emit_op(Op::Pop);
347                }
348            }
349        }
350        inner.emit.emit_op(Op::Return);
351
352        let func_id = self.functions.len() as u16;
353        let (mut chunk, mut child_functions) = inner.finish();
354
355        // The inner compiler assigned func_ids starting from 0, but child functions
356        // will be placed starting at func_id + 1 in our functions vec.
357        // Patch all MakeClosure func_id operands in the inner chunk and child functions.
358        let offset = func_id + 1;
359        if offset > 0 && !child_functions.is_empty() {
360            patch_closure_func_ids(&mut chunk, offset);
361            for f in &mut child_functions {
362                patch_closure_func_ids(&mut f.chunk, offset);
363            }
364        }
365
366        let func = Function {
367            name: def.name,
368            chunk,
369            upvalue_descs: def.upvalues.clone(),
370            arity: def.params.len() as u16,
371            has_rest: def.rest.is_some(),
372            local_names: Vec::new(),
373        };
374        self.functions.push(func);
375        self.functions.extend(child_functions);
376
377        // Emit MakeClosure instruction
378        let n_upvalues = def.upvalues.len() as u16;
379        self.emit.emit_op(Op::MakeClosure);
380        self.emit.emit_u16(func_id);
381        self.emit.emit_u16(n_upvalues);
382
383        // Emit upvalue descriptors inline
384        for uv in &def.upvalues {
385            match uv {
386                UpvalueDesc::ParentLocal(slot) => {
387                    self.emit.emit_u16(1); // is_local = true (using u16 for alignment)
388                    self.emit.emit_u16(*slot);
389                }
390                UpvalueDesc::ParentUpvalue(idx) => {
391                    self.emit.emit_u16(0); // is_local = false
392                    self.emit.emit_u16(*idx);
393                }
394            }
395        }
396
397        Ok(())
398    }
399
400    // --- Function calls ---
401
402    fn compile_call(
403        &mut self,
404        func: &ResolvedExpr,
405        args: &[ResolvedExpr],
406        tail: bool,
407    ) -> Result<(), SemaError> {
408        // Compile function expression
409        self.compile_expr(func)?;
410        // Compile arguments
411        for arg in args {
412            self.compile_expr(arg)?;
413        }
414        let argc = args.len() as u16;
415        if tail {
416            self.emit.emit_op(Op::TailCall);
417        } else {
418            self.emit.emit_op(Op::Call);
419        }
420        self.emit.emit_u16(argc);
421        Ok(())
422    }
423
424    // --- Let forms ---
425
426    fn compile_let(
427        &mut self,
428        bindings: &[(VarRef, ResolvedExpr)],
429        body: &[ResolvedExpr],
430    ) -> Result<(), SemaError> {
431        // Compile all init expressions first
432        for (_, init) in bindings {
433            self.compile_expr(init)?;
434        }
435        // Store into local slots (in reverse to match stack order)
436        for (vr, _) in bindings.iter().rev() {
437            self.compile_var_store(vr);
438        }
439        // Compile body
440        self.compile_begin(body)
441    }
442
443    fn compile_let_star(
444        &mut self,
445        bindings: &[(VarRef, ResolvedExpr)],
446        body: &[ResolvedExpr],
447    ) -> Result<(), SemaError> {
448        // Sequential: compile init, store, next binding
449        for (vr, init) in bindings {
450            self.compile_expr(init)?;
451            self.compile_var_store(vr);
452        }
453        self.compile_begin(body)
454    }
455
456    fn compile_letrec(
457        &mut self,
458        bindings: &[(VarRef, ResolvedExpr)],
459        body: &[ResolvedExpr],
460    ) -> Result<(), SemaError> {
461        // Initialize all slots to nil first
462        for (vr, _) in bindings {
463            self.emit.emit_op(Op::Nil);
464            self.compile_var_store(vr);
465        }
466        // Then compile and assign each init
467        for (vr, init) in bindings {
468            self.compile_expr(init)?;
469            self.compile_var_store(vr);
470        }
471        self.compile_begin(body)
472    }
473
474    fn compile_named_let(
475        &mut self,
476        name: &VarRef,
477        bindings: &[(VarRef, ResolvedExpr)],
478        body: &[ResolvedExpr],
479    ) -> Result<(), SemaError> {
480        // Named let is compiled as a local recursive function.
481        // The loop variable `name` is bound to a lambda that takes the binding params.
482        // 1. Create the lambda for the loop body
483        // 2. Bind it to `name`
484        // 3. Call it with initial values
485
486        // Build a synthetic ResolvedLambda for the loop body
487        let params: Vec<Spur> = bindings.iter().map(|(vr, _)| vr.name).collect();
488
489        // Compile the lambda body into a separate function
490        let mut inner = Compiler::new();
491        // The lambda needs locals for its params + the loop name
492        // The resolver has already set up the slots
493        let max_slot = bindings
494            .iter()
495            .map(|(vr, _)| match vr.resolution {
496                VarResolution::Local { slot } => slot + 1,
497                _ => 0,
498            })
499            .max()
500            .unwrap_or(0);
501        let name_slot = match name.resolution {
502            VarResolution::Local { slot } => slot + 1,
503            _ => 0,
504        };
505        inner.n_locals = max_slot.max(name_slot);
506
507        // Compile body
508        if body.is_empty() {
509            inner.emit.emit_op(Op::Nil);
510        } else {
511            for (i, expr) in body.iter().enumerate() {
512                inner.compile_expr(expr)?;
513                if i < body.len() - 1 {
514                    inner.emit.emit_op(Op::Pop);
515                }
516            }
517        }
518        inner.emit.emit_op(Op::Return);
519
520        let func_id = self.functions.len() as u16;
521        let (chunk, child_functions) = inner.finish();
522
523        let func = Function {
524            name: Some(name.name),
525            chunk,
526            upvalue_descs: Vec::new(),
527            arity: params.len() as u16,
528            has_rest: false,
529            local_names: Vec::new(),
530        };
531        self.functions.push(func);
532        self.functions.extend(child_functions);
533
534        // Emit MakeClosure for the loop function
535        self.emit.emit_op(Op::MakeClosure);
536        self.emit.emit_u16(func_id);
537        // TODO: named-let doesn't support capturing outer variables yet
538        self.emit.emit_u16(0);
539
540        // Store the closure in the loop name's slot
541        self.compile_var_store(name);
542
543        // Now call it with the initial binding values
544        self.compile_var_load(name)?;
545        for (_, init) in bindings {
546            self.compile_expr(init)?;
547        }
548        let argc = bindings.len() as u16;
549        self.emit.emit_op(Op::Call);
550        self.emit.emit_u16(argc);
551
552        Ok(())
553    }
554
555    // --- Do loop ---
556
557    fn compile_do(&mut self, do_loop: &ResolvedDoLoop) -> Result<(), SemaError> {
558        // 1. Compile init expressions and store to vars
559        for var in &do_loop.vars {
560            self.compile_expr(&var.init)?;
561            self.compile_var_store(&var.name);
562        }
563
564        // 2. Loop top
565        let loop_top = self.emit.current_pc();
566
567        // 3. Compile test
568        self.compile_expr(&do_loop.test)?;
569        let exit_jump = self.emit.emit_jump(Op::JumpIfTrue);
570
571        // 4. Compile loop body
572        for expr in &do_loop.body {
573            self.compile_expr(expr)?;
574            self.emit.emit_op(Op::Pop);
575        }
576
577        // 5. Compile step expressions and update vars
578        // First compile all step values, then store (to avoid using partially-updated vars)
579        let mut step_vars = Vec::new();
580        for var in &do_loop.vars {
581            if let Some(step) = &var.step {
582                self.compile_expr(step)?;
583                step_vars.push(&var.name);
584            }
585        }
586        // Store in reverse order (stack is LIFO)
587        for vr in step_vars.iter().rev() {
588            self.compile_var_store(vr);
589        }
590
591        // 6. Jump back to loop top
592        self.emit.emit_op(Op::Jump);
593        let jump_end_pc = self.emit.current_pc();
594        let offset = loop_top as i32 - (jump_end_pc as i32 + 4);
595        self.emit.emit_i32(offset);
596
597        // 7. Exit: compile result expressions
598        self.emit.patch_jump(exit_jump);
599        if do_loop.result.is_empty() {
600            self.emit.emit_op(Op::Nil);
601        } else {
602            for (i, expr) in do_loop.result.iter().enumerate() {
603                self.compile_expr(expr)?;
604                if i < do_loop.result.len() - 1 {
605                    self.emit.emit_op(Op::Pop);
606                }
607            }
608        }
609
610        Ok(())
611    }
612
613    // --- Exception handling ---
614
615    fn compile_try(
616        &mut self,
617        body: &[ResolvedExpr],
618        catch_var: &VarRef,
619        handler: &[ResolvedExpr],
620    ) -> Result<(), SemaError> {
621        let try_start = self.emit.current_pc();
622
623        // Compile body
624        self.compile_begin(body)?;
625        let try_end = self.emit.current_pc();
626
627        // Jump over handler on success
628        let success_jump = self.emit.emit_jump(Op::Jump);
629
630        let handler_pc = self.emit.current_pc();
631
632        // The VM will push the caught error value onto the stack
633        // Store it in the catch variable slot
634        let catch_slot = match catch_var.resolution {
635            VarResolution::Local { slot } => slot,
636            _ => 0,
637        };
638        self.emit.emit_op(Op::StoreLocal);
639        self.emit.emit_u16(catch_slot);
640
641        // Compile handler body
642        self.compile_begin(handler)?;
643
644        self.emit.patch_jump(success_jump);
645
646        // Add exception table entry
647        // We need to modify the emitter's chunk directly — use a deferred approach
648        // Store the exception entry data and apply after finish
649        // Actually, the Emitter gives us into_chunk which we can modify.
650        // Let's store exception entries separately and merge at finish.
651        // For now, store in the compiler and merge.
652        // We'll need to access the chunk... let's extend Emitter slightly or use a side vec.
653        self.add_exception_entry(ExceptionEntry {
654            try_start,
655            try_end,
656            handler_pc,
657            stack_depth: self.n_locals,
658            catch_slot,
659        });
660
661        Ok(())
662    }
663
664    fn add_exception_entry(&mut self, entry: ExceptionEntry) {
665        // We'll store these and apply when finishing the chunk
666        // For now, emit directly into the emitter's chunk
667        // Since Emitter doesn't expose this, we use a workaround:
668        // Store entries in Compiler and merge in finish_chunk
669        self.exception_entries.push(entry);
670    }
671
672    fn compile_throw(&mut self, val: &ResolvedExpr) -> Result<(), SemaError> {
673        self.compile_expr(val)?;
674        self.emit.emit_op(Op::Throw);
675        Ok(())
676    }
677
678    // --- Short-circuit boolean ---
679
680    fn compile_and(&mut self, exprs: &[ResolvedExpr]) -> Result<(), SemaError> {
681        if exprs.is_empty() {
682            self.emit.emit_op(Op::True);
683            return Ok(());
684        }
685
686        let mut jumps = Vec::new();
687        for (i, expr) in exprs.iter().enumerate() {
688            self.compile_expr(expr)?;
689            if i < exprs.len() - 1 {
690                // Dup so the value is preserved if we short-circuit
691                self.emit.emit_op(Op::Dup);
692                let jump = self.emit.emit_jump(Op::JumpIfFalse);
693                jumps.push(jump);
694                self.emit.emit_op(Op::Pop); // discard the dup'd value (continuing)
695            }
696        }
697        let end_jump = self.emit.emit_jump(Op::Jump);
698        // Short-circuit target: the dup'd falsy value is on the stack
699        for jump in jumps {
700            self.emit.patch_jump(jump);
701        }
702        self.emit.patch_jump(end_jump);
703        Ok(())
704    }
705
706    fn compile_or(&mut self, exprs: &[ResolvedExpr]) -> Result<(), SemaError> {
707        if exprs.is_empty() {
708            self.emit.emit_op(Op::False);
709            return Ok(());
710        }
711
712        let mut jumps = Vec::new();
713        for (i, expr) in exprs.iter().enumerate() {
714            self.compile_expr(expr)?;
715            if i < exprs.len() - 1 {
716                self.emit.emit_op(Op::Dup);
717                let jump = self.emit.emit_jump(Op::JumpIfTrue);
718                jumps.push(jump);
719                self.emit.emit_op(Op::Pop);
720            }
721        }
722        let end_jump = self.emit.emit_jump(Op::Jump);
723        for jump in jumps {
724            self.emit.patch_jump(jump);
725        }
726        self.emit.patch_jump(end_jump);
727        Ok(())
728    }
729
730    // --- Data constructors ---
731
732    fn compile_make_list(&mut self, exprs: &[ResolvedExpr]) -> Result<(), SemaError> {
733        for expr in exprs {
734            self.compile_expr(expr)?;
735        }
736        self.emit.emit_op(Op::MakeList);
737        self.emit.emit_u16(exprs.len() as u16);
738        Ok(())
739    }
740
741    fn compile_make_vector(&mut self, exprs: &[ResolvedExpr]) -> Result<(), SemaError> {
742        for expr in exprs {
743            self.compile_expr(expr)?;
744        }
745        self.emit.emit_op(Op::MakeVector);
746        self.emit.emit_u16(exprs.len() as u16);
747        Ok(())
748    }
749
750    fn compile_make_map(
751        &mut self,
752        pairs: &[(ResolvedExpr, ResolvedExpr)],
753    ) -> Result<(), SemaError> {
754        for (key, val) in pairs {
755            self.compile_expr(key)?;
756            self.compile_expr(val)?;
757        }
758        self.emit.emit_op(Op::MakeMap);
759        self.emit.emit_u16(pairs.len() as u16);
760        Ok(())
761    }
762
763    // --- Forms that delegate to runtime native calls ---
764    // These forms cannot be fully compiled to bytecode because they need
765    // access to the tree-walker (eval, macros, modules) or have complex
766    // runtime semantics. They are compiled as calls to well-known global
767    // functions that the VM/interpreter provides.
768
769    fn compile_eval(&mut self, expr: &ResolvedExpr) -> Result<(), SemaError> {
770        self.emit_runtime_call("__vm-eval", &[expr])
771    }
772
773    fn compile_load(&mut self, path: &ResolvedExpr) -> Result<(), SemaError> {
774        self.emit_runtime_call("__vm-load", &[path])
775    }
776
777    fn compile_import(&mut self, path: &ResolvedExpr, selective: &[Spur]) -> Result<(), SemaError> {
778        let sel_list: Vec<Value> = selective
779            .iter()
780            .map(|s| Value::symbol_from_spur(*s))
781            .collect();
782        self.emit_runtime_call_with_const("__vm-import", path, &Value::list(sel_list))
783    }
784
785    fn compile_module(
786        &mut self,
787        _name: Spur,
788        _exports: &[Spur],
789        body: &[ResolvedExpr],
790    ) -> Result<(), SemaError> {
791        // Compile module body sequentially
792        for (i, expr) in body.iter().enumerate() {
793            self.compile_expr(expr)?;
794            if i < body.len() - 1 {
795                self.emit.emit_op(Op::Pop);
796            }
797        }
798        if body.is_empty() {
799            self.emit.emit_op(Op::Nil);
800        }
801        // Module result is the last body expression
802        // Module registration is handled by the VM when it sees this was a module
803        Ok(())
804    }
805
806    fn compile_defmacro(
807        &mut self,
808        name: Spur,
809        params: &[Spur],
810        rest: &Option<Spur>,
811        body: &[ResolvedExpr],
812    ) -> Result<(), SemaError> {
813        // Defmacro at compile time — emit as a call to __vm-defmacro
814        // For now, compile the body as a lambda and register it
815        let param_vals: Vec<Value> = params.iter().map(|s| Value::symbol_from_spur(*s)).collect();
816        self.emit.emit_op(Op::LoadGlobal);
817        self.emit.emit_u32(spur_to_u32(intern("__vm-defmacro")));
818        self.emit.emit_const(Value::symbol_from_spur(name));
819        self.emit.emit_const(Value::list(param_vals));
820        if let Some(r) = rest {
821            self.emit.emit_const(Value::symbol_from_spur(*r));
822        } else {
823            self.emit.emit_op(Op::Nil);
824        }
825        // Compile body as a begin
826        self.compile_begin(body)?;
827        self.emit.emit_op(Op::Call);
828        self.emit.emit_u16(4);
829        Ok(())
830    }
831
832    fn compile_define_record_type(
833        &mut self,
834        type_name: Spur,
835        ctor_name: Spur,
836        pred_name: Spur,
837        field_names: &[Spur],
838        field_specs: &[(Spur, Spur)],
839    ) -> Result<(), SemaError> {
840        // Emit as a call to __vm-define-record-type with all info as constants
841        // Function must be pushed first (before args) to match VM calling convention
842        self.emit.emit_op(Op::LoadGlobal);
843        self.emit
844            .emit_u32(spur_to_u32(intern("__vm-define-record-type")));
845        self.emit.emit_const(Value::symbol_from_spur(type_name));
846        self.emit.emit_const(Value::symbol_from_spur(ctor_name));
847        self.emit.emit_const(Value::symbol_from_spur(pred_name));
848        let fields: Vec<Value> = field_names
849            .iter()
850            .map(|s| Value::symbol_from_spur(*s))
851            .collect();
852        self.emit.emit_const(Value::list(fields));
853        let specs: Vec<Value> = field_specs
854            .iter()
855            .map(|(f, a)| {
856                Value::list(vec![
857                    Value::symbol_from_spur(*f),
858                    Value::symbol_from_spur(*a),
859                ])
860            })
861            .collect();
862        self.emit.emit_const(Value::list(specs));
863        self.emit.emit_op(Op::Call);
864        self.emit.emit_u16(5);
865        Ok(())
866    }
867
868    fn compile_prompt(&mut self, entries: &[ResolvedPromptEntry]) -> Result<(), SemaError> {
869        // Function must be pushed first (before args) to match VM calling convention
870        self.emit.emit_op(Op::LoadGlobal);
871        self.emit.emit_u32(spur_to_u32(intern("__vm-prompt")));
872        // Compile each prompt entry and build a list
873        for entry in entries {
874            match entry {
875                ResolvedPromptEntry::RoleContent { role, parts } => {
876                    self.emit.emit_const(Value::string(role));
877                    for part in parts {
878                        self.compile_expr(part)?;
879                    }
880                    self.emit.emit_op(Op::MakeList);
881                    self.emit.emit_u16(parts.len() as u16);
882                    // Make a (role parts-list) pair
883                    self.emit.emit_op(Op::MakeList);
884                    self.emit.emit_u16(2);
885                }
886                ResolvedPromptEntry::Expr(expr) => {
887                    self.compile_expr(expr)?;
888                }
889            }
890        }
891        self.emit.emit_op(Op::MakeList);
892        self.emit.emit_u16(entries.len() as u16);
893        self.emit.emit_op(Op::Call);
894        self.emit.emit_u16(1);
895        Ok(())
896    }
897
898    fn compile_message(
899        &mut self,
900        role: &ResolvedExpr,
901        parts: &[ResolvedExpr],
902    ) -> Result<(), SemaError> {
903        self.emit.emit_op(Op::LoadGlobal);
904        self.emit.emit_u32(spur_to_u32(intern("__vm-message")));
905        self.compile_expr(role)?;
906        for part in parts {
907            self.compile_expr(part)?;
908        }
909        self.emit.emit_op(Op::MakeList);
910        self.emit.emit_u16(parts.len() as u16);
911        self.emit.emit_op(Op::Call);
912        self.emit.emit_u16(2);
913        Ok(())
914    }
915
916    fn compile_deftool(
917        &mut self,
918        name: Spur,
919        description: &ResolvedExpr,
920        parameters: &ResolvedExpr,
921        handler: &ResolvedExpr,
922    ) -> Result<(), SemaError> {
923        self.emit.emit_op(Op::LoadGlobal);
924        self.emit.emit_u32(spur_to_u32(intern("__vm-deftool")));
925        self.emit.emit_const(Value::symbol_from_spur(name));
926        self.compile_expr(description)?;
927        self.compile_expr(parameters)?;
928        self.compile_expr(handler)?;
929        self.emit.emit_op(Op::Call);
930        self.emit.emit_u16(4);
931        Ok(())
932    }
933
934    fn compile_defagent(&mut self, name: Spur, options: &ResolvedExpr) -> Result<(), SemaError> {
935        self.emit.emit_op(Op::LoadGlobal);
936        self.emit.emit_u32(spur_to_u32(intern("__vm-defagent")));
937        self.emit.emit_const(Value::symbol_from_spur(name));
938        self.compile_expr(options)?;
939        self.emit.emit_op(Op::Call);
940        self.emit.emit_u16(2);
941        Ok(())
942    }
943
944    fn compile_delay(&mut self, expr: &ResolvedExpr) -> Result<(), SemaError> {
945        // Delay wraps expr in a zero-arg lambda (thunk)
946        // The resolver already handles this if lowered as a lambda,
947        // but if it comes through as Delay, compile as a call to __vm-delay
948        self.emit.emit_op(Op::LoadGlobal);
949        self.emit.emit_u32(spur_to_u32(intern("__vm-delay")));
950        self.compile_expr(expr)?;
951        self.emit.emit_op(Op::Call);
952        self.emit.emit_u16(1);
953        Ok(())
954    }
955
956    fn compile_force(&mut self, expr: &ResolvedExpr) -> Result<(), SemaError> {
957        self.emit.emit_op(Op::LoadGlobal);
958        self.emit.emit_u32(spur_to_u32(intern("__vm-force")));
959        self.compile_expr(expr)?;
960        self.emit.emit_op(Op::Call);
961        self.emit.emit_u16(1);
962        Ok(())
963    }
964
965    fn compile_macroexpand(&mut self, expr: &ResolvedExpr) -> Result<(), SemaError> {
966        self.emit.emit_op(Op::LoadGlobal);
967        self.emit.emit_u32(spur_to_u32(intern("__vm-macroexpand")));
968        self.compile_expr(expr)?;
969        self.emit.emit_op(Op::Call);
970        self.emit.emit_u16(1);
971        Ok(())
972    }
973
974    // --- Helper: emit a call to a well-known runtime function ---
975
976    fn emit_runtime_call(&mut self, name: &str, args: &[&ResolvedExpr]) -> Result<(), SemaError> {
977        self.emit.emit_op(Op::LoadGlobal);
978        self.emit.emit_u32(spur_to_u32(intern(name)));
979        for arg in args {
980            self.compile_expr(arg)?;
981        }
982        self.emit.emit_op(Op::Call);
983        self.emit.emit_u16(args.len() as u16);
984        Ok(())
985    }
986
987    fn emit_runtime_call_with_const(
988        &mut self,
989        name: &str,
990        arg1: &ResolvedExpr,
991        arg2: &Value,
992    ) -> Result<(), SemaError> {
993        self.emit.emit_op(Op::LoadGlobal);
994        self.emit.emit_u32(spur_to_u32(intern(name)));
995        self.compile_expr(arg1)?;
996        self.emit.emit_const(arg2.clone());
997        self.emit.emit_op(Op::Call);
998        self.emit.emit_u16(2);
999        Ok(())
1000    }
1001}
1002
1003fn spur_to_u32(spur: Spur) -> u32 {
1004    spur.into_inner().get()
1005}
1006
1007#[cfg(test)]
1008mod tests {
1009    use super::*;
1010    use crate::lower::lower;
1011    use crate::resolve::resolve;
1012
1013    fn compile_str(input: &str) -> CompileResult {
1014        let val = sema_reader::read(input).unwrap();
1015        let core = lower(&val).unwrap();
1016        let resolved = resolve(&core).unwrap();
1017        compile(&resolved).unwrap()
1018    }
1019
1020    fn compile_many_str(input: &str) -> CompileResult {
1021        let vals = sema_reader::read_many(input).unwrap();
1022        let mut resolved = Vec::new();
1023        for val in &vals {
1024            let core = lower(val).unwrap();
1025            resolved.push(resolve(&core).unwrap());
1026        }
1027        compile_many(&resolved).unwrap()
1028    }
1029
1030    /// Extract just the opcode bytes from a chunk, skipping operands.
1031    fn extract_ops(chunk: &Chunk) -> Vec<Op> {
1032        let code = &chunk.code;
1033        let mut ops = Vec::new();
1034        let mut pc = 0;
1035        while pc < code.len() {
1036            let op = unsafe { std::mem::transmute::<u8, Op>(code[pc]) };
1037            ops.push(op);
1038            pc += 1;
1039            // Skip operands based on opcode
1040            match op {
1041                Op::Const
1042                | Op::LoadLocal
1043                | Op::StoreLocal
1044                | Op::LoadUpvalue
1045                | Op::StoreUpvalue
1046                | Op::Call
1047                | Op::TailCall
1048                | Op::MakeList
1049                | Op::MakeVector
1050                | Op::MakeMap
1051                | Op::MakeHashMap => pc += 2,
1052                Op::LoadGlobal | Op::StoreGlobal | Op::DefineGlobal => pc += 4,
1053                Op::Jump | Op::JumpIfFalse | Op::JumpIfTrue => pc += 4,
1054                Op::CallNative => pc += 4,
1055                Op::MakeClosure => {
1056                    let func_id = u16::from_le_bytes([code[pc], code[pc + 1]]);
1057                    let n_upvalues = u16::from_le_bytes([code[pc + 2], code[pc + 3]]);
1058                    pc += 4;
1059                    pc += n_upvalues as usize * 4; // each upvalue is u16 + u16
1060                    let _ = func_id;
1061                }
1062                _ => {} // zero-operand opcodes
1063            }
1064        }
1065        ops
1066    }
1067
1068    /// Read the i32 operand of a Jump/JumpIfFalse/JumpIfTrue at the given opcode PC.
1069    fn read_jump_offset(chunk: &Chunk, op_pc: usize) -> i32 {
1070        i32::from_le_bytes([
1071            chunk.code[op_pc + 1],
1072            chunk.code[op_pc + 2],
1073            chunk.code[op_pc + 3],
1074            chunk.code[op_pc + 4],
1075        ])
1076    }
1077
1078    // --- Literal compilation ---
1079
1080    #[test]
1081    fn test_compile_int_literal() {
1082        let result = compile_str("42");
1083        let ops = extract_ops(&result.chunk);
1084        assert_eq!(ops, vec![Op::Const, Op::Return]);
1085        assert_eq!(result.chunk.consts[0], Value::int(42));
1086    }
1087
1088    #[test]
1089    fn test_compile_nil() {
1090        let result = compile_str("()");
1091        let ops = extract_ops(&result.chunk);
1092        assert_eq!(ops, vec![Op::Nil, Op::Return]);
1093    }
1094
1095    #[test]
1096    fn test_compile_true_false() {
1097        let t = compile_str("#t");
1098        assert_eq!(extract_ops(&t.chunk), vec![Op::True, Op::Return]);
1099
1100        let f = compile_str("#f");
1101        assert_eq!(extract_ops(&f.chunk), vec![Op::False, Op::Return]);
1102    }
1103
1104    #[test]
1105    fn test_compile_string_literal() {
1106        let result = compile_str("\"hello\"");
1107        let ops = extract_ops(&result.chunk);
1108        assert_eq!(ops, vec![Op::Const, Op::Return]);
1109        assert_eq!(result.chunk.consts[0].as_str(), Some("hello"));
1110    }
1111
1112    // --- Variable access ---
1113
1114    #[test]
1115    fn test_compile_global_var() {
1116        let result = compile_str("x");
1117        let ops = extract_ops(&result.chunk);
1118        assert_eq!(ops, vec![Op::LoadGlobal, Op::Return]);
1119    }
1120
1121    // --- Control flow ---
1122
1123    #[test]
1124    fn test_compile_if() {
1125        let result = compile_str("(if #t 1 2)");
1126        let ops = extract_ops(&result.chunk);
1127        // TRUE, JumpIfFalse, CONST(1), Jump, CONST(2), RETURN
1128        assert_eq!(
1129            ops,
1130            vec![
1131                Op::True,
1132                Op::JumpIfFalse,
1133                Op::Const,
1134                Op::Jump,
1135                Op::Const,
1136                Op::Return
1137            ]
1138        );
1139    }
1140
1141    #[test]
1142    fn test_compile_nested_if() {
1143        let result = compile_str("(if #t (if #f 1 2) 3)");
1144        let ops = extract_ops(&result.chunk);
1145        let jif_count = ops.iter().filter(|&&op| op == Op::JumpIfFalse).count();
1146        assert_eq!(jif_count, 2);
1147    }
1148
1149    #[test]
1150    fn test_compile_begin() {
1151        let result = compile_str("(begin 1 2 3)");
1152        let ops = extract_ops(&result.chunk);
1153        // CONST(1), POP, CONST(2), POP, CONST(3), RETURN
1154        assert_eq!(
1155            ops,
1156            vec![
1157                Op::Const,
1158                Op::Pop,
1159                Op::Const,
1160                Op::Pop,
1161                Op::Const,
1162                Op::Return
1163            ]
1164        );
1165    }
1166
1167    // --- Define ---
1168
1169    #[test]
1170    fn test_compile_define() {
1171        let result = compile_str("(define x 42)");
1172        let ops = extract_ops(&result.chunk);
1173        // CONST(42), DefineGlobal, Nil, Return
1174        assert_eq!(ops, vec![Op::Const, Op::DefineGlobal, Op::Nil, Op::Return]);
1175    }
1176
1177    // --- Lambda ---
1178
1179    #[test]
1180    fn test_compile_lambda() {
1181        let result = compile_str("(lambda (x) x)");
1182        assert_eq!(result.functions.len(), 1);
1183        let func = &result.functions[0];
1184        assert_eq!(func.arity, 1);
1185        assert!(!func.has_rest);
1186
1187        // Inner function: LoadLocal0, Return
1188        let inner_ops = extract_ops(&func.chunk);
1189        assert_eq!(inner_ops, vec![Op::LoadLocal0, Op::Return]);
1190
1191        // Top-level: MakeClosure, Return
1192        let top_ops = extract_ops(&result.chunk);
1193        assert_eq!(top_ops, vec![Op::MakeClosure, Op::Return]);
1194    }
1195
1196    #[test]
1197    fn test_compile_lambda_rest_param() {
1198        let result = compile_str("(lambda (x . rest) x)");
1199        assert_eq!(result.functions.len(), 1);
1200        let func = &result.functions[0];
1201        assert_eq!(func.arity, 1);
1202        assert!(func.has_rest);
1203    }
1204
1205    // --- Calls: non-tail vs tail ---
1206
1207    #[test]
1208    fn test_compile_non_tail_call() {
1209        // Top-level call is NOT in tail position of a lambda
1210        let result = compile_str("(+ 1 2)");
1211        let ops = extract_ops(&result.chunk);
1212        // LoadGlobal(+), Const(1), Const(2), Call(2), Return
1213        assert_eq!(
1214            ops,
1215            vec![Op::LoadGlobal, Op::Const, Op::Const, Op::Call, Op::Return]
1216        );
1217        // Verify it's Call, not TailCall
1218        assert!(!ops.contains(&Op::TailCall));
1219    }
1220
1221    #[test]
1222    fn test_compile_tail_call() {
1223        let result = compile_str("(lambda () (f 1))");
1224        assert_eq!(result.functions.len(), 1);
1225        let inner_ops = extract_ops(&result.functions[0].chunk);
1226        // LoadGlobal(f), Const(1), TailCall(1), Return
1227        assert_eq!(
1228            inner_ops,
1229            vec![Op::LoadGlobal, Op::Const, Op::TailCall, Op::Return]
1230        );
1231        // Verify it's TailCall, NOT Call
1232        assert!(!inner_ops.contains(&Op::Call));
1233    }
1234
1235    #[test]
1236    fn test_compile_non_tail_in_begin() {
1237        // (lambda () (f 1) (g 2)) — first call is NOT tail, second IS tail
1238        let result = compile_str("(lambda () (f 1) (g 2))");
1239        let inner_ops = extract_ops(&result.functions[0].chunk);
1240        // f call: LoadGlobal, Const, Call, Pop
1241        // g call: LoadGlobal, Const, TailCall, Return
1242        assert_eq!(
1243            inner_ops,
1244            vec![
1245                Op::LoadGlobal,
1246                Op::Const,
1247                Op::Call,
1248                Op::Pop,
1249                Op::LoadGlobal,
1250                Op::Const,
1251                Op::TailCall,
1252                Op::Return
1253            ]
1254        );
1255    }
1256
1257    // --- Let forms ---
1258
1259    #[test]
1260    fn test_compile_let() {
1261        let result = compile_str("(lambda () (let ((x 1) (y 2)) x))");
1262        let inner_ops = extract_ops(&result.functions[0].chunk);
1263        // CONST(1), CONST(2), StoreLocal(y=1), StoreLocal(x=0), LoadLocal0(x=0), Return
1264        assert_eq!(
1265            inner_ops,
1266            vec![
1267                Op::Const,
1268                Op::Const,
1269                Op::StoreLocal,
1270                Op::StoreLocal,
1271                Op::LoadLocal0,
1272                Op::Return
1273            ]
1274        );
1275    }
1276
1277    #[test]
1278    fn test_compile_let_star() {
1279        // let* stores sequentially so later bindings see earlier ones
1280        let result = compile_str("(lambda () (let* ((x 1) (y x)) y))");
1281        let inner_ops = extract_ops(&result.functions[0].chunk);
1282        // CONST(1), StoreLocal(x), LoadLocal(x), StoreLocal(y), LoadLocal(y), Return
1283        assert_eq!(
1284            inner_ops,
1285            vec![
1286                Op::Const,
1287                Op::StoreLocal,
1288                Op::LoadLocal0,
1289                Op::StoreLocal,
1290                Op::LoadLocal1,
1291                Op::Return
1292            ]
1293        );
1294    }
1295
1296    #[test]
1297    fn test_compile_letrec() {
1298        let result = compile_str("(lambda () (letrec ((x 1)) x))");
1299        let inner_ops = extract_ops(&result.functions[0].chunk);
1300        // Nil, StoreLocal(x), CONST(1), StoreLocal(x), LoadLocal(x), Return
1301        assert_eq!(
1302            inner_ops,
1303            vec![
1304                Op::Nil,
1305                Op::StoreLocal,
1306                Op::Const,
1307                Op::StoreLocal,
1308                Op::LoadLocal0,
1309                Op::Return
1310            ]
1311        );
1312    }
1313
1314    // --- Set! ---
1315
1316    #[test]
1317    fn test_compile_set_local() {
1318        let result = compile_str("(lambda (x) (set! x 42))");
1319        let inner_ops = extract_ops(&result.functions[0].chunk);
1320        // CONST(42), Dup, StoreLocal(0), Return
1321        assert_eq!(
1322            inner_ops,
1323            vec![Op::Const, Op::Dup, Op::StoreLocal, Op::Return]
1324        );
1325    }
1326
1327    #[test]
1328    fn test_compile_set_global() {
1329        let result = compile_str("(set! x 42)");
1330        let ops = extract_ops(&result.chunk);
1331        // CONST(42), Dup, StoreGlobal, Return
1332        assert_eq!(ops, vec![Op::Const, Op::Dup, Op::StoreGlobal, Op::Return]);
1333    }
1334
1335    #[test]
1336    fn test_compile_set_upvalue() {
1337        // Inner lambda sets outer variable
1338        let result = compile_str("(lambda (x) (lambda () (set! x 1)))");
1339        assert_eq!(result.functions.len(), 2);
1340        let inner_ops = extract_ops(&result.functions[1].chunk);
1341        // CONST(1), Dup, StoreUpvalue(0), Return
1342        assert_eq!(
1343            inner_ops,
1344            vec![Op::Const, Op::Dup, Op::StoreUpvalue, Op::Return]
1345        );
1346    }
1347
1348    // --- Short-circuit boolean ---
1349
1350    #[test]
1351    fn test_compile_and_empty() {
1352        let result = compile_str("(and)");
1353        let ops = extract_ops(&result.chunk);
1354        assert_eq!(ops, vec![Op::True, Op::Return]);
1355    }
1356
1357    #[test]
1358    fn test_compile_or_empty() {
1359        let result = compile_str("(or)");
1360        let ops = extract_ops(&result.chunk);
1361        assert_eq!(ops, vec![Op::False, Op::Return]);
1362    }
1363
1364    #[test]
1365    fn test_compile_and_short_circuit() {
1366        let result = compile_str("(and 1 2)");
1367        let ops = extract_ops(&result.chunk);
1368        // CONST(1), Dup, JumpIfFalse, Pop, CONST(2), Jump, Return
1369        assert_eq!(
1370            ops,
1371            vec![
1372                Op::Const,
1373                Op::Dup,
1374                Op::JumpIfFalse,
1375                Op::Pop,
1376                Op::Const,
1377                Op::Jump,
1378                Op::Return
1379            ]
1380        );
1381    }
1382
1383    #[test]
1384    fn test_compile_or_short_circuit() {
1385        let result = compile_str("(or 1 2)");
1386        let ops = extract_ops(&result.chunk);
1387        // CONST(1), Dup, JumpIfTrue, Pop, CONST(2), Jump, Return
1388        assert_eq!(
1389            ops,
1390            vec![
1391                Op::Const,
1392                Op::Dup,
1393                Op::JumpIfTrue,
1394                Op::Pop,
1395                Op::Const,
1396                Op::Jump,
1397                Op::Return
1398            ]
1399        );
1400    }
1401
1402    // --- Data constructors ---
1403
1404    #[test]
1405    fn test_compile_vector_literal() {
1406        let result = compile_str("[1 2 3]");
1407        let ops = extract_ops(&result.chunk);
1408        assert_eq!(
1409            ops,
1410            vec![Op::Const, Op::Const, Op::Const, Op::MakeVector, Op::Return]
1411        );
1412    }
1413
1414    #[test]
1415    fn test_compile_quote() {
1416        let result = compile_str("'(1 2 3)");
1417        let ops = extract_ops(&result.chunk);
1418        assert_eq!(ops, vec![Op::Const, Op::Return]);
1419    }
1420
1421    // --- Exception handling ---
1422
1423    #[test]
1424    fn test_compile_throw() {
1425        let result = compile_str("(throw 42)");
1426        let ops = extract_ops(&result.chunk);
1427        assert_eq!(ops, vec![Op::Const, Op::Throw, Op::Return]);
1428    }
1429
1430    #[test]
1431    fn test_compile_try_catch() {
1432        let result = compile_str("(lambda () (try (/ 1 0) (catch e e)))");
1433        let func = &result.functions[0];
1434        // Verify exception table
1435        assert_eq!(func.chunk.exception_table.len(), 1);
1436        let entry = &func.chunk.exception_table[0];
1437        assert!(entry.try_start < entry.try_end);
1438        assert!(entry.handler_pc > entry.try_end);
1439        assert!((entry.handler_pc as usize) < func.chunk.code.len());
1440        // Handler should store to catch slot then load it
1441        let ops = extract_ops(&func.chunk);
1442        assert!(ops.contains(&Op::StoreLocal)); // store caught error
1443                                                // The jump-over-handler should be present
1444        assert!(ops.contains(&Op::Jump));
1445    }
1446
1447    // --- Closures ---
1448
1449    #[test]
1450    fn test_compile_closure_with_upvalue() {
1451        let result = compile_str("(lambda (x) (lambda () x))");
1452        assert_eq!(result.functions.len(), 2);
1453        // Outer function: MakeClosure for inner, Return
1454        let outer = &result.functions[0];
1455        let outer_ops = extract_ops(&outer.chunk);
1456        assert!(outer_ops.contains(&Op::MakeClosure));
1457        // Inner function: LoadUpvalue(0), Return
1458        let inner = &result.functions[1];
1459        let inner_ops = extract_ops(&inner.chunk);
1460        assert_eq!(inner_ops, vec![Op::LoadUpvalue, Op::Return]);
1461        // Inner function's upvalue_descs should match
1462        assert_eq!(inner.upvalue_descs.len(), 1);
1463        assert!(matches!(
1464            inner.upvalue_descs[0],
1465            UpvalueDesc::ParentLocal(0)
1466        ));
1467    }
1468
1469    #[test]
1470    fn test_compile_nested_lambda_func_ids() {
1471        // (lambda () (lambda () 1) (lambda () 2))
1472        // Outer is func 0, inner lambdas are func 1 and func 2
1473        let result = compile_str("(lambda () (lambda () 1) (lambda () 2))");
1474        assert_eq!(result.functions.len(), 3);
1475        // Verify each inner function compiles correctly
1476        let f1 = &result.functions[1];
1477        let f1_ops = extract_ops(&f1.chunk);
1478        assert_eq!(f1_ops, vec![Op::Const, Op::Return]);
1479        assert_eq!(f1.chunk.consts[0], Value::int(1));
1480
1481        let f2 = &result.functions[2];
1482        let f2_ops = extract_ops(&f2.chunk);
1483        assert_eq!(f2_ops, vec![Op::Const, Op::Return]);
1484        assert_eq!(f2.chunk.consts[0], Value::int(2));
1485
1486        // Verify the outer function has two MakeClosure instructions
1487        // with func_ids 1 and 2 (checking the raw bytes)
1488        let outer = &result.functions[0];
1489        let outer_ops = extract_ops(&outer.chunk);
1490        let mc_count = outer_ops
1491            .iter()
1492            .filter(|&&op| op == Op::MakeClosure)
1493            .count();
1494        assert_eq!(mc_count, 2);
1495    }
1496
1497    // --- Do loop ---
1498
1499    #[test]
1500    fn test_compile_do_loop() {
1501        let result = compile_str("(lambda () (do ((i 0 (+ i 1))) ((= i 10) i) (display i)))");
1502        let func = &result.functions[0];
1503        let ops = extract_ops(&func.chunk);
1504        // Must contain a backward Jump (negative offset) for the loop back-edge
1505        let jump_pcs: Vec<usize> = (0..func.chunk.code.len())
1506            .filter(|&pc| func.chunk.code[pc] == Op::Jump as u8)
1507            .collect();
1508        // Find the back-edge jump (should have a negative offset)
1509        let has_back_edge = jump_pcs
1510            .iter()
1511            .any(|&pc| read_jump_offset(&func.chunk, pc) < 0);
1512        assert!(has_back_edge, "do loop must have a backward jump");
1513        // Must have JumpIfTrue for the exit condition
1514        assert!(ops.contains(&Op::JumpIfTrue));
1515    }
1516
1517    // --- Named let ---
1518
1519    #[test]
1520    fn test_compile_named_let() {
1521        // Named let desugars to letrec+lambda, compiled as letrec with a closure
1522        let result = compile_str("(lambda () (let loop ((n 10)) (if (= n 0) n (loop (- n 1)))))");
1523        // Should have at least 2 functions: outer lambda + loop lambda
1524        assert!(result.functions.len() >= 2);
1525        // The outer function should contain MakeClosure (for the loop) + TailCall (initial invocation in tail position)
1526        let outer = &result.functions[0];
1527        let outer_ops = extract_ops(&outer.chunk);
1528        assert!(outer_ops.contains(&Op::MakeClosure));
1529        assert!(outer_ops.contains(&Op::TailCall) || outer_ops.contains(&Op::Call));
1530    }
1531
1532    // --- compile_many ---
1533
1534    #[test]
1535    fn test_compile_many_empty() {
1536        let result = compile_many(&[]).unwrap();
1537        let ops = extract_ops(&result.chunk);
1538        assert_eq!(ops, vec![Op::Nil, Op::Return]);
1539    }
1540
1541    #[test]
1542    fn test_compile_many_multiple() {
1543        let result = compile_many_str("1 2 3");
1544        let ops = extract_ops(&result.chunk);
1545        // CONST(1), Pop, CONST(2), Pop, CONST(3), Return
1546        assert_eq!(
1547            ops,
1548            vec![
1549                Op::Const,
1550                Op::Pop,
1551                Op::Const,
1552                Op::Pop,
1553                Op::Const,
1554                Op::Return
1555            ]
1556        );
1557    }
1558
1559    #[test]
1560    fn test_compile_many_single() {
1561        let result = compile_many_str("42");
1562        let ops = extract_ops(&result.chunk);
1563        // CONST(42), Return (no Pop)
1564        assert_eq!(ops, vec![Op::Const, Op::Return]);
1565    }
1566
1567    // --- Calling convention: function must be below args ---
1568
1569    #[test]
1570    fn test_calling_convention_runtime_call() {
1571        // (eval 42) compiles as: LoadGlobal(__vm-eval), CONST(42), Call(1)
1572        let result = compile_str("(eval 42)");
1573        let ops = extract_ops(&result.chunk);
1574        assert_eq!(ops, vec![Op::LoadGlobal, Op::Const, Op::Call, Op::Return]);
1575        // The first op must be LoadGlobal (function loaded first)
1576        assert_eq!(ops[0], Op::LoadGlobal);
1577    }
1578
1579    // --- Map literal ---
1580
1581    #[test]
1582    fn test_compile_map_literal() {
1583        let result = compile_str("{:a 1 :b 2}");
1584        let ops = extract_ops(&result.chunk);
1585        // key, val, key, val, MakeMap, Return
1586        assert_eq!(
1587            ops,
1588            vec![
1589                Op::Const,
1590                Op::Const,
1591                Op::Const,
1592                Op::Const,
1593                Op::MakeMap,
1594                Op::Return
1595            ]
1596        );
1597    }
1598
1599    #[test]
1600    fn test_compile_depth_limit() {
1601        // Build deeply nested Begin(Begin(Begin(...Const(1)...))) bypassing lowering
1602        let mut expr = ResolvedExpr::Const(Value::int(1));
1603        for _ in 0..300 {
1604            expr = ResolvedExpr::Begin(vec![expr]);
1605        }
1606        let result = compile(&expr);
1607        let err = result.err().expect("expected compilation to fail");
1608        let msg = err.to_string();
1609        assert!(
1610            msg.contains("compilation depth"),
1611            "expected compilation depth error, got: {msg}"
1612        );
1613    }
1614}