1mod context;
2mod emit;
3pub(crate) mod expr;
4mod layout;
5mod native;
6pub mod special;
7
8use tracing::debug;
9
10use crate::ast::{Expr, Program, WasmType};
11use crate::error::{Error, Result};
12use crate::host_fn::HostFnSpec;
13use crate::runtime::SymbolTable;
14
15use context::CompileContext;
16use emit::FunctionEmitter;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum CompileMode {
27 Script,
28 Eval,
29}
30
31pub struct Compiler {
32 host_fns: Vec<HostFnSpec>,
33}
34
35impl Compiler {
36 #[must_use]
37 pub fn new() -> Self {
38 Self {
39 host_fns: Vec::new(),
40 }
41 }
42
43 #[must_use]
48 pub fn with_host_fns(host_fns: Vec<HostFnSpec>) -> Self {
49 Self { host_fns }
50 }
51
52 pub fn compile(&mut self, program: &Program, symbols: &mut SymbolTable) -> Result<Vec<u8>> {
53 self.compile_with_mode(program, symbols, CompileMode::Script)
54 }
55
56 pub fn compile_with_mode(
57 &mut self,
58 program: &Program,
59 symbols: &mut SymbolTable,
60 mode: CompileMode,
61 ) -> Result<Vec<u8>> {
62 debug!(expr_count = program.exprs.len(), ?mode, "compilation start");
63 match mode {
64 CompileMode::Script => self.compile_script(program, symbols),
65 CompileMode::Eval => self.compile_eval(program, symbols),
66 }
67 }
68
69 fn compile_script(&mut self, program: &Program, symbols: &mut SymbolTable) -> Result<Vec<u8>> {
70 let mut ctx = CompileContext::new()?;
71
72 let bootstrap_helper_count = ctx.pending_helper_count();
81
82 let mut process = FunctionEmitter::new();
83
84 ctx.emit_boundary_wrapper(&mut process, None, |ctx, emit| {
88 emit.call(ctx.ids.get_output_offset()?);
90 emit.local_set(expr::LOCAL_OUTPUT_BASE);
91 expr::compile_program(ctx, emit, symbols, program)
92 })?;
93 process.end();
94
95 let process_locals = ctx.build_locals_declaration();
96 ctx.reset_locals();
97
98 let should_apply = self.build_should_apply(&mut ctx, symbols)?;
99 ctx.add_should_apply(should_apply, bootstrap_helper_count);
100 ctx.add_process(process.finish(&process_locals));
101
102 let wasm = ctx.finish();
103 debug!(wasm_size = wasm.len(), "compilation complete");
104 Ok(wasm)
105 }
106
107 fn compile_eval(&mut self, program: &Program, symbols: &mut SymbolTable) -> Result<Vec<u8>> {
108 let (wasm, _ty) = self.compile_eval_with_type(program, symbols)?;
109 Ok(wasm)
110 }
111
112 pub fn compile_eval_with_type(
118 &mut self,
119 program: &Program,
120 symbols: &mut SymbolTable,
121 ) -> Result<(Vec<u8>, Option<WasmType>)> {
122 let mut ctx = CompileContext::new_eval_with_host_fns(&self.host_fns)?;
123 let mut emit = FunctionEmitter::new();
124
125 let mut result_ty: Option<WasmType> = None;
130 ctx.emit_boundary_wrapper(&mut emit, Some(WasmType::AnyRef), |ctx, emit| {
131 if program.exprs.is_empty() {
132 emit.ref_null_any();
133 return Ok(());
134 }
135 let last_idx = program.exprs.len() - 1;
136 for expr in &program.exprs[..last_idx] {
137 expr::compile_for_effect(ctx, emit, symbols, expr)?;
138 }
139 let last = &program.exprs[last_idx];
140 if is_definition_form(last) {
141 expr::compile_for_effect(ctx, emit, symbols, last)?;
142 emit.ref_null_any();
143 } else {
144 let ty = expr::compile_for_stack(ctx, emit, symbols, last)?;
145 emit_to_anyref(ctx, emit, ty);
146 result_ty = Some(ty);
147 }
148 Ok(())
149 })?;
150 emit.end();
151
152 let locals = ctx.build_locals_declaration();
153 ctx.reset_locals();
154 ctx.add_nomi_eval(emit.finish(&locals));
155
156 let wasm = ctx.finish();
157 debug!(wasm_size = wasm.len(), "eval compilation complete");
158 Ok((wasm, result_ty))
159 }
160
161 fn build_should_apply(
162 &self,
163 ctx: &mut CompileContext,
164 symbols: &mut SymbolTable,
165 ) -> Result<wasm_encoder::Function> {
166 let body = symbols
167 .lookup("SHOULD-APPLY")
168 .and_then(|s| s.function().cloned());
169
170 let Some(Expr::Lambda(params, body)) = body else {
171 return Ok(CompileContext::default_should_apply());
172 };
173
174 if !params.required.is_empty() {
175 return Err(Error::Compile(
176 "SHOULD-APPLY must take no parameters".to_string(),
177 ));
178 }
179
180 debug!("compiling custom should-apply");
181 let mut emit = FunctionEmitter::new();
182
183 ctx.emit_boundary_wrapper(&mut emit, Some(WasmType::I32), |ctx, emit| {
186 let ty = expr::compile_for_stack(ctx, emit, symbols, &body)?;
187 match ty {
188 WasmType::I32 | WasmType::Bool => Ok(()),
192 WasmType::Ratio => {
193 emit.struct_get(ctx.ids.ty_ratio, 0);
194 emit.i64_const(0);
195 emit.i64_ne();
196 Ok(())
197 }
198 _ => Err(Error::Compile(
199 "SHOULD-APPLY must return a boolean or numeric value".to_string(),
200 )),
201 }
202 })?;
203 emit.end();
204
205 let locals = ctx.build_locals_declaration();
206 ctx.reset_locals();
207 Ok(emit.finish(&locals))
208 }
209}
210
211fn emit_to_anyref(ctx: &CompileContext, emit: &mut FunctionEmitter, ty: WasmType) {
219 match ty {
220 WasmType::I32 => {
221 emit.i64_extend_i32_s();
222 emit.call(ctx.ids.ratio_from_i64);
223 }
224 WasmType::Bool => expr::emit_box_anyref(ctx, emit, ty),
225 WasmType::Ratio
226 | WasmType::Commodity
227 | WasmType::StringRef
228 | WasmType::PairRef(_)
229 | WasmType::EntityRef(_)
230 | WasmType::Closure(_)
231 | WasmType::AnyRef => {}
232 }
233}
234
235fn is_definition_form(expr: &Expr) -> bool {
236 let Expr::List(elems) = expr else {
237 return false;
238 };
239 let Some(Expr::Symbol(name)) = elems.first() else {
240 return false;
241 };
242 matches!(
243 name.as_str(),
244 "DEFUN" | "DEFVAR" | "DEFMACRO" | "DEFPARAMETER" | "DEFSTRUCT"
245 )
246}
247
248impl Default for Compiler {
249 fn default() -> Self {
250 Self::new()
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use crate::ast::Expr;
258 use crate::runtime::{Symbol, SymbolKind};
259
260 #[test]
261 fn test_compile_empty_program() {
262 let program = Program::default();
263 let mut compiler = Compiler::new();
264 let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
265 assert!(!wasm.is_empty());
266 assert_eq!(&wasm[0..4], b"\0asm");
267 }
268
269 #[test]
270 fn test_compile_eval_empty_program_emits_capture_nil() {
271 let program = Program::default();
272 let mut compiler = Compiler::new();
273 let wasm = compiler
274 .compile_with_mode(&program, &mut SymbolTable::new(), CompileMode::Eval)
275 .unwrap();
276 assert!(!wasm.is_empty());
277 assert_eq!(&wasm[0..4], b"\0asm");
278 }
279
280 #[test]
281 fn test_compile_eval_integer_literal() {
282 let program = Program::new(vec![Expr::Number(num_rational::Ratio::from_integer(7))]);
283 let mut compiler = Compiler::new();
284 let wasm = compiler
285 .compile_with_mode(&program, &mut SymbolTable::new(), CompileMode::Eval)
286 .unwrap();
287 assert!(!wasm.is_empty());
288 }
289
290 #[test]
291 fn test_compile_eval_arithmetic() {
292 let program = Program::new(vec![Expr::List(vec![
293 Expr::Symbol("+".into()),
294 Expr::Number(num_rational::Ratio::from_integer(1)),
295 Expr::Number(num_rational::Ratio::from_integer(2)),
296 ])]);
297 let mut compiler = Compiler::new();
298 let mut symbols = SymbolTable::with_builtins();
299 let wasm = compiler
300 .compile_with_mode(&program, &mut symbols, CompileMode::Eval)
301 .unwrap();
302 assert!(!wasm.is_empty());
303 }
304
305 #[test]
306 fn test_compile_default_uses_script_mode() {
307 let program = Program::new(vec![Expr::T]);
308 let mut compiler = Compiler::new();
309 let mut symbols = SymbolTable::new();
310 let default_bytes = compiler.compile(&program, &mut symbols).unwrap();
311 let mut symbols = SymbolTable::new();
312 let explicit_bytes = compiler
313 .compile_with_mode(&program, &mut symbols, CompileMode::Script)
314 .unwrap();
315 assert_eq!(default_bytes, explicit_bytes);
316 }
317
318 #[test]
319 fn test_compile_eval_and_script_produce_distinct_bytes() {
320 let program = Program::new(vec![Expr::Number(num_rational::Ratio::from_integer(1))]);
321 let mut compiler = Compiler::new();
322 let mut s1 = SymbolTable::new();
323 let mut s2 = SymbolTable::new();
324 let script_bytes = compiler
325 .compile_with_mode(&program, &mut s1, CompileMode::Script)
326 .unwrap();
327 let eval_bytes = compiler
328 .compile_with_mode(&program, &mut s2, CompileMode::Eval)
329 .unwrap();
330 assert_ne!(script_bytes, eval_bytes);
331 }
332
333 #[test]
334 fn test_compile_nil() {
335 let program = Program::new(vec![Expr::Nil]);
336 let mut compiler = Compiler::new();
337 let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
338 assert!(!wasm.is_empty());
339 assert_eq!(&wasm[0..4], b"\0asm");
340 }
341
342 #[test]
343 fn test_compile_bool() {
344 let program = Program::new(vec![Expr::T]);
345 let mut compiler = Compiler::new();
346 let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
347 assert!(!wasm.is_empty());
348 }
349
350 #[test]
351 fn test_compile_number() {
352 use num_rational::Ratio;
353 let program = Program::new(vec![Expr::Number(Ratio::new(1, 2))]);
354 let mut compiler = Compiler::new();
355 let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
356 assert!(!wasm.is_empty());
357 }
358
359 #[test]
360 fn test_compile_string() {
361 let program = Program::new(vec![Expr::String("hello".into())]);
362 let mut compiler = Compiler::new();
363 let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
364 assert!(!wasm.is_empty());
365 assert_eq!(&wasm[0..4], b"\0asm");
366 }
367
368 #[test]
369 fn test_compile_symbol_with_value() {
370 let mut symbols = SymbolTable::new();
371 symbols.define(Symbol::new("REVISION", SymbolKind::Variable).with_value(Expr::T));
372 let program = Program::new(vec![Expr::Symbol("REVISION".into())]);
373 let mut compiler = Compiler::new();
374 let wasm = compiler.compile(&program, &mut symbols).unwrap();
375 assert!(!wasm.is_empty());
376 }
377
378 #[test]
379 fn test_compile_undefined_symbol() {
380 let program = Program::new(vec![Expr::Symbol("UNKNOWN".into())]);
381 let mut compiler = Compiler::new();
382 let result = compiler.compile(&program, &mut SymbolTable::new());
383 assert!(result.is_err());
384 let err = result.unwrap_err();
385 assert!(matches!(err, crate::error::Error::UndefinedSymbol(_)));
386 }
387
388 #[test]
389 fn test_defun_populates_function_cell() {
390 let program = Program::new(vec![Expr::List(vec![
391 Expr::Symbol("DEFUN".into()),
392 Expr::Symbol("SUM".into()),
393 Expr::List(vec![
394 Expr::Symbol("A".into()),
395 Expr::Symbol("B".into()),
396 Expr::Symbol("C".into()),
397 ]),
398 Expr::String("Sums A, B, C".into()),
399 Expr::List(vec![
400 Expr::Symbol("+".into()),
401 Expr::Symbol("A".into()),
402 Expr::Symbol("B".into()),
403 Expr::Symbol("C".into()),
404 ]),
405 ])]);
406 let mut compiler = Compiler::new();
407 let mut symbols = SymbolTable::with_builtins();
408 compiler.compile(&program, &mut symbols).unwrap();
409
410 let sym = symbols.lookup("SUM").expect("SUM should be defined");
411 assert!(sym.function().is_some());
412 assert!(matches!(sym.function(), Some(Expr::Lambda(_, _))));
413 assert_eq!(sym.doc(), Some("Sums A, B, C"));
414 }
415
416 #[test]
417 fn test_defun_no_doc_populates_function_cell() {
418 let program = Program::new(vec![Expr::List(vec![
419 Expr::Symbol("DEFUN".into()),
420 Expr::Symbol("ADD".into()),
421 Expr::List(vec![Expr::Symbol("A".into()), Expr::Symbol("B".into())]),
422 Expr::List(vec![
423 Expr::Symbol("+".into()),
424 Expr::Symbol("A".into()),
425 Expr::Symbol("B".into()),
426 ]),
427 ])]);
428 let mut compiler = Compiler::new();
429 let mut symbols = SymbolTable::with_builtins();
430 compiler.compile(&program, &mut symbols).unwrap();
431
432 let sym = symbols.lookup("ADD").expect("ADD should be defined");
433 assert!(sym.function().is_some());
434 assert!(sym.doc().is_none());
435 }
436}