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
10pub struct CompileResult {
12 pub chunk: Chunk,
14 pub functions: Vec<Function>,
16}
17
18const MAX_COMPILE_DEPTH: usize = 256;
21
22pub fn compile(expr: &ResolvedExpr) -> Result<CompileResult, SemaError> {
24 compile_with_locals(expr, 0)
25}
26
27pub 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
37pub fn compile_many(exprs: &[ResolvedExpr]) -> Result<CompileResult, SemaError> {
39 compile_many_with_locals(exprs, 0)
40}
41
42pub 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
63fn 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 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 let n_upvalues = u16::from_le_bytes([code[pc + 3], code[pc + 4]]) as usize;
81 pc += 1 + 2 + 2 + n_upvalues * 4;
83 }
84 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; }
98 Op::CallNative => {
99 pc += 1 + 2 + 2; }
101 Op::LoadGlobal | Op::StoreGlobal | Op::DefineGlobal => {
102 pc += 1 + 4; }
104 Op::Jump | Op::JumpIfFalse | Op::JumpIfTrue => {
105 pc += 1 + 4; }
107 _ => {
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 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 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 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 fn compile_set(&mut self, vr: &VarRef, val: &ResolvedExpr) -> Result<(), SemaError> {
318 self.compile_expr(val)?;
319 self.emit.emit_op(Op::Dup); 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); Ok(())
330 }
331
332 fn compile_lambda(&mut self, def: &ResolvedLambda) -> Result<(), SemaError> {
335 let mut inner = Compiler::new();
337 inner.n_locals = def.n_locals;
338
339 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 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 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 for uv in &def.upvalues {
385 match uv {
386 UpvalueDesc::ParentLocal(slot) => {
387 self.emit.emit_u16(1); self.emit.emit_u16(*slot);
389 }
390 UpvalueDesc::ParentUpvalue(idx) => {
391 self.emit.emit_u16(0); self.emit.emit_u16(*idx);
393 }
394 }
395 }
396
397 Ok(())
398 }
399
400 fn compile_call(
403 &mut self,
404 func: &ResolvedExpr,
405 args: &[ResolvedExpr],
406 tail: bool,
407 ) -> Result<(), SemaError> {
408 self.compile_expr(func)?;
410 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 fn compile_let(
427 &mut self,
428 bindings: &[(VarRef, ResolvedExpr)],
429 body: &[ResolvedExpr],
430 ) -> Result<(), SemaError> {
431 for (_, init) in bindings {
433 self.compile_expr(init)?;
434 }
435 for (vr, _) in bindings.iter().rev() {
437 self.compile_var_store(vr);
438 }
439 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 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 for (vr, _) in bindings {
463 self.emit.emit_op(Op::Nil);
464 self.compile_var_store(vr);
465 }
466 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 let params: Vec<Spur> = bindings.iter().map(|(vr, _)| vr.name).collect();
488
489 let mut inner = Compiler::new();
491 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 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 self.emit.emit_op(Op::MakeClosure);
536 self.emit.emit_u16(func_id);
537 self.emit.emit_u16(0);
539
540 self.compile_var_store(name);
542
543 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 fn compile_do(&mut self, do_loop: &ResolvedDoLoop) -> Result<(), SemaError> {
558 for var in &do_loop.vars {
560 self.compile_expr(&var.init)?;
561 self.compile_var_store(&var.name);
562 }
563
564 let loop_top = self.emit.current_pc();
566
567 self.compile_expr(&do_loop.test)?;
569 let exit_jump = self.emit.emit_jump(Op::JumpIfTrue);
570
571 for expr in &do_loop.body {
573 self.compile_expr(expr)?;
574 self.emit.emit_op(Op::Pop);
575 }
576
577 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 for vr in step_vars.iter().rev() {
588 self.compile_var_store(vr);
589 }
590
591 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 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 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 self.compile_begin(body)?;
625 let try_end = self.emit.current_pc();
626
627 let success_jump = self.emit.emit_jump(Op::Jump);
629
630 let handler_pc = self.emit.current_pc();
631
632 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 self.compile_begin(handler)?;
643
644 self.emit.patch_jump(success_jump);
645
646 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 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 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 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); }
696 }
697 let end_jump = self.emit.emit_jump(Op::Jump);
698 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 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 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 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 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 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 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 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 self.emit.emit_op(Op::LoadGlobal);
871 self.emit.emit_u32(spur_to_u32(intern("__vm-prompt")));
872 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 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 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 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 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 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; let _ = func_id;
1061 }
1062 _ => {} }
1064 }
1065 ops
1066 }
1067
1068 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 #[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 #[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 #[test]
1124 fn test_compile_if() {
1125 let result = compile_str("(if #t 1 2)");
1126 let ops = extract_ops(&result.chunk);
1127 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 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 #[test]
1170 fn test_compile_define() {
1171 let result = compile_str("(define x 42)");
1172 let ops = extract_ops(&result.chunk);
1173 assert_eq!(ops, vec![Op::Const, Op::DefineGlobal, Op::Nil, Op::Return]);
1175 }
1176
1177 #[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 let inner_ops = extract_ops(&func.chunk);
1189 assert_eq!(inner_ops, vec![Op::LoadLocal0, Op::Return]);
1190
1191 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 #[test]
1208 fn test_compile_non_tail_call() {
1209 let result = compile_str("(+ 1 2)");
1211 let ops = extract_ops(&result.chunk);
1212 assert_eq!(
1214 ops,
1215 vec![Op::LoadGlobal, Op::Const, Op::Const, Op::Call, Op::Return]
1216 );
1217 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 assert_eq!(
1228 inner_ops,
1229 vec![Op::LoadGlobal, Op::Const, Op::TailCall, Op::Return]
1230 );
1231 assert!(!inner_ops.contains(&Op::Call));
1233 }
1234
1235 #[test]
1236 fn test_compile_non_tail_in_begin() {
1237 let result = compile_str("(lambda () (f 1) (g 2))");
1239 let inner_ops = extract_ops(&result.functions[0].chunk);
1240 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 #[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 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 result = compile_str("(lambda () (let* ((x 1) (y x)) y))");
1281 let inner_ops = extract_ops(&result.functions[0].chunk);
1282 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 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 #[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 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 assert_eq!(ops, vec![Op::Const, Op::Dup, Op::StoreGlobal, Op::Return]);
1333 }
1334
1335 #[test]
1336 fn test_compile_set_upvalue() {
1337 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 assert_eq!(
1343 inner_ops,
1344 vec![Op::Const, Op::Dup, Op::StoreUpvalue, Op::Return]
1345 );
1346 }
1347
1348 #[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 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 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 #[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 #[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 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 let ops = extract_ops(&func.chunk);
1442 assert!(ops.contains(&Op::StoreLocal)); assert!(ops.contains(&Op::Jump));
1445 }
1446
1447 #[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 let outer = &result.functions[0];
1455 let outer_ops = extract_ops(&outer.chunk);
1456 assert!(outer_ops.contains(&Op::MakeClosure));
1457 let inner = &result.functions[1];
1459 let inner_ops = extract_ops(&inner.chunk);
1460 assert_eq!(inner_ops, vec![Op::LoadUpvalue, Op::Return]);
1461 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 let result = compile_str("(lambda () (lambda () 1) (lambda () 2))");
1474 assert_eq!(result.functions.len(), 3);
1475 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 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 #[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 let jump_pcs: Vec<usize> = (0..func.chunk.code.len())
1506 .filter(|&pc| func.chunk.code[pc] == Op::Jump as u8)
1507 .collect();
1508 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 assert!(ops.contains(&Op::JumpIfTrue));
1515 }
1516
1517 #[test]
1520 fn test_compile_named_let() {
1521 let result = compile_str("(lambda () (let loop ((n 10)) (if (= n 0) n (loop (- n 1)))))");
1523 assert!(result.functions.len() >= 2);
1525 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 #[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 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 assert_eq!(ops, vec![Op::Const, Op::Return]);
1565 }
1566
1567 #[test]
1570 fn test_calling_convention_runtime_call() {
1571 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 assert_eq!(ops[0], Op::LoadGlobal);
1577 }
1578
1579 #[test]
1582 fn test_compile_map_literal() {
1583 let result = compile_str("{:a 1 :b 2}");
1584 let ops = extract_ops(&result.chunk);
1585 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 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}