Lines
97.5 %
Functions
72.22 %
Branches
100 %
mod context;
mod emit;
pub(crate) mod expr;
mod layout;
mod native;
pub mod special;
use tracing::debug;
use wasm_encoder::{HeapType, RefType, ValType};
use crate::ast::Program;
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();
ctx.add_should_apply();
let gc_arr_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(ctx.type_idx("i8_array")),
});
let mut process = FunctionEmitter::new_with_locals(&[
(1, gc_arr_ref),
(1, ValType::I32),
]);
// 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();
ctx.add_process(process.finish());
let wasm = ctx.finish();
debug!(wasm_size = wasm.len(), "compilation complete");
Ok(wasm)
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());