Lines
90.54 %
Functions
58.82 %
Branches
100 %
mod context;
mod emit;
pub(crate) mod expr;
mod layout;
mod native;
pub mod special;
use tracing::debug;
use crate::ast::{Expr, Program, WasmType};
use crate::error::Result;
use crate::runtime::SymbolTable;
use context::CompileContext;
use emit::FunctionEmitter;
pub struct Compiler;
impl Compiler {
#[must_use]
pub fn new() -> Self {
Self
}
pub fn compile(&mut self, program: &Program, symbols: &mut SymbolTable) -> Result<Vec<u8>> {
debug!(expr_count = program.exprs.len(), "compilation start");
let mut ctx = CompileContext::new();
let mut process = FunctionEmitter::new();
// output_base = get_output_offset()
process.call(ctx.func("get_output_offset"));
process.local_set(expr::LOCAL_OUTPUT_BASE);
expr::compile_program(&mut ctx, &mut process, symbols, program)?;
process.end();
let process_locals = ctx.build_locals_declaration();
ctx.reset_locals();
let should_apply = self.build_should_apply(&mut ctx, symbols)?;
ctx.add_should_apply(should_apply);
ctx.add_process(process.finish(&process_locals));
let wasm = ctx.finish();
debug!(wasm_size = wasm.len(), "compilation complete");
Ok(wasm)
fn build_should_apply(
&self,
ctx: &mut CompileContext,
symbols: &mut SymbolTable,
) -> Result<wasm_encoder::Function> {
let body = symbols
.lookup("SHOULD-APPLY")
.and_then(|s| s.function().cloned());
let Some(Expr::Lambda(params, body)) = body else {
return Ok(CompileContext::default_should_apply());
};
if !params.required.is_empty() {
return Err(crate::error::Error::Compile(
"SHOULD-APPLY must take no parameters".to_string(),
));
debug!("compiling custom should-apply");
let mut emit = FunctionEmitter::new();
let ty = expr::compile_for_stack(ctx, &mut emit, symbols, &body)?;
match ty {
WasmType::I32 => {}
WasmType::Ratio => {
emit.struct_get(ctx.type_idx("ratio"), 0);
emit.i64_const(0);
emit.i64_ne();
_ => {
"SHOULD-APPLY must return a boolean or numeric value".to_string(),
emit.end();
let locals = ctx.build_locals_declaration();
Ok(emit.finish(&locals))
impl Default for Compiler {
fn default() -> Self {
Self::new()
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Expr;
use crate::runtime::{Symbol, SymbolKind};
#[test]
fn test_compile_empty_program() {
let program = Program::default();
let mut compiler = Compiler::new();
let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
assert!(!wasm.is_empty());
assert_eq!(&wasm[0..4], b"\0asm");
fn test_compile_nil() {
let program = Program::new(vec![Expr::Nil]);
fn test_compile_bool() {
let program = Program::new(vec![Expr::Bool(true)]);
fn test_compile_number() {
use num_rational::Ratio;
let program = Program::new(vec![Expr::Number(Ratio::new(1, 2))]);
fn test_compile_string() {
let program = Program::new(vec![Expr::String("hello".into())]);
fn test_compile_symbol_with_value() {
let mut symbols = SymbolTable::new();
symbols.define(Symbol::new("REVISION", SymbolKind::Variable).with_value(Expr::Bool(true)));
let program = Program::new(vec![Expr::Symbol("REVISION".into())]);
let wasm = compiler.compile(&program, &mut symbols).unwrap();
fn test_compile_undefined_symbol() {
let program = Program::new(vec![Expr::Symbol("UNKNOWN".into())]);
let result = compiler.compile(&program, &mut SymbolTable::new());
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, crate::error::Error::UndefinedSymbol(_)));
fn test_defun_populates_function_cell() {
let program = Program::new(vec![Expr::List(vec![
Expr::Symbol("DEFUN".into()),
Expr::Symbol("SUM".into()),
Expr::List(vec![
Expr::Symbol("A".into()),
Expr::Symbol("B".into()),
Expr::Symbol("C".into()),
]),
Expr::String("Sums A, B, C".into()),
Expr::Symbol("+".into()),
])]);
let mut symbols = SymbolTable::with_builtins();
compiler.compile(&program, &mut symbols).unwrap();
let sym = symbols.lookup("SUM").expect("SUM should be defined");
assert!(sym.function().is_some());
assert!(matches!(sym.function(), Some(Expr::Lambda(_, _))));
assert_eq!(sym.doc(), Some("Sums A, B, C"));
fn test_defun_no_doc_populates_function_cell() {
Expr::Symbol("ADD".into()),
Expr::List(vec![Expr::Symbol("A".into()), Expr::Symbol("B".into())]),
let sym = symbols.lookup("ADD").expect("ADD should be defined");
assert!(sym.doc().is_none());