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