1
mod context;
2
mod emit;
3
pub(crate) mod expr;
4
mod layout;
5
mod native;
6
pub mod special;
7

            
8
use tracing::debug;
9

            
10
use crate::ast::{Expr, Program, WasmType};
11
use crate::error::Result;
12
use crate::runtime::SymbolTable;
13

            
14
use context::CompileContext;
15
use emit::FunctionEmitter;
16

            
17
pub struct Compiler;
18

            
19
impl Compiler {
20
    #[must_use]
21
10439
    pub fn new() -> Self {
22
10439
        Self
23
10439
    }
24

            
25
20251
    pub fn compile(&mut self, program: &Program, symbols: &mut SymbolTable) -> Result<Vec<u8>> {
26
20251
        debug!(expr_count = program.exprs.len(), "compilation start");
27
20251
        let mut ctx = CompileContext::new();
28

            
29
20251
        let mut process = FunctionEmitter::new();
30

            
31
        // output_base = get_output_offset()
32
20251
        process.call(ctx.func("get_output_offset"));
33
20251
        process.local_set(expr::LOCAL_OUTPUT_BASE);
34

            
35
20251
        expr::compile_program(&mut ctx, &mut process, symbols, program)?;
36
18842
        process.end();
37

            
38
18842
        let process_locals = ctx.build_locals_declaration();
39
18842
        ctx.reset_locals();
40

            
41
18842
        let should_apply = self.build_should_apply(&mut ctx, symbols)?;
42
18842
        ctx.add_should_apply(should_apply);
43
18842
        ctx.add_process(process.finish(&process_locals));
44

            
45
18842
        let wasm = ctx.finish();
46
18842
        debug!(wasm_size = wasm.len(), "compilation complete");
47
18842
        Ok(wasm)
48
20251
    }
49

            
50
18842
    fn build_should_apply(
51
18842
        &self,
52
18842
        ctx: &mut CompileContext,
53
18842
        symbols: &mut SymbolTable,
54
18842
    ) -> Result<wasm_encoder::Function> {
55
18842
        let body = symbols
56
18842
            .lookup("SHOULD-APPLY")
57
18842
            .and_then(|s| s.function().cloned());
58

            
59
222
        let Some(Expr::Lambda(params, body)) = body else {
60
18620
            return Ok(CompileContext::default_should_apply());
61
        };
62

            
63
222
        if !params.required.is_empty() {
64
            return Err(crate::error::Error::Compile(
65
                "SHOULD-APPLY must take no parameters".to_string(),
66
            ));
67
222
        }
68

            
69
222
        debug!("compiling custom should-apply");
70
222
        let mut emit = FunctionEmitter::new();
71

            
72
222
        let ty = expr::compile_for_stack(ctx, &mut emit, symbols, &body)?;
73
222
        match ty {
74
222
            WasmType::I32 => {}
75
            WasmType::Ratio => {
76
                emit.struct_get(ctx.type_idx("ratio"), 0);
77
                emit.i64_const(0);
78
                emit.i64_ne();
79
            }
80
            _ => {
81
                return Err(crate::error::Error::Compile(
82
                    "SHOULD-APPLY must return a boolean or numeric value".to_string(),
83
                ));
84
            }
85
        }
86
222
        emit.end();
87

            
88
222
        let locals = ctx.build_locals_declaration();
89
222
        ctx.reset_locals();
90
222
        Ok(emit.finish(&locals))
91
18842
    }
92
}
93

            
94
impl Default for Compiler {
95
    fn default() -> Self {
96
        Self::new()
97
    }
98
}
99

            
100
#[cfg(test)]
101
mod tests {
102
    use super::*;
103
    use crate::ast::Expr;
104
    use crate::runtime::{Symbol, SymbolKind};
105

            
106
    #[test]
107
1
    fn test_compile_empty_program() {
108
1
        let program = Program::default();
109
1
        let mut compiler = Compiler::new();
110
1
        let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
111
1
        assert!(!wasm.is_empty());
112
1
        assert_eq!(&wasm[0..4], b"\0asm");
113
1
    }
114

            
115
    #[test]
116
1
    fn test_compile_nil() {
117
1
        let program = Program::new(vec![Expr::Nil]);
118
1
        let mut compiler = Compiler::new();
119
1
        let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
120
1
        assert!(!wasm.is_empty());
121
1
        assert_eq!(&wasm[0..4], b"\0asm");
122
1
    }
123

            
124
    #[test]
125
1
    fn test_compile_bool() {
126
1
        let program = Program::new(vec![Expr::Bool(true)]);
127
1
        let mut compiler = Compiler::new();
128
1
        let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
129
1
        assert!(!wasm.is_empty());
130
1
    }
131

            
132
    #[test]
133
1
    fn test_compile_number() {
134
        use num_rational::Ratio;
135
1
        let program = Program::new(vec![Expr::Number(Ratio::new(1, 2))]);
136
1
        let mut compiler = Compiler::new();
137
1
        let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
138
1
        assert!(!wasm.is_empty());
139
1
    }
140

            
141
    #[test]
142
1
    fn test_compile_string() {
143
1
        let program = Program::new(vec![Expr::String("hello".into())]);
144
1
        let mut compiler = Compiler::new();
145
1
        let wasm = compiler.compile(&program, &mut SymbolTable::new()).unwrap();
146
1
        assert!(!wasm.is_empty());
147
1
        assert_eq!(&wasm[0..4], b"\0asm");
148
1
    }
149

            
150
    #[test]
151
1
    fn test_compile_symbol_with_value() {
152
1
        let mut symbols = SymbolTable::new();
153
1
        symbols.define(Symbol::new("REVISION", SymbolKind::Variable).with_value(Expr::Bool(true)));
154
1
        let program = Program::new(vec![Expr::Symbol("REVISION".into())]);
155
1
        let mut compiler = Compiler::new();
156
1
        let wasm = compiler.compile(&program, &mut symbols).unwrap();
157
1
        assert!(!wasm.is_empty());
158
1
    }
159

            
160
    #[test]
161
1
    fn test_compile_undefined_symbol() {
162
1
        let program = Program::new(vec![Expr::Symbol("UNKNOWN".into())]);
163
1
        let mut compiler = Compiler::new();
164
1
        let result = compiler.compile(&program, &mut SymbolTable::new());
165
1
        assert!(result.is_err());
166
1
        let err = result.unwrap_err();
167
1
        assert!(matches!(err, crate::error::Error::UndefinedSymbol(_)));
168
1
    }
169

            
170
    #[test]
171
1
    fn test_defun_populates_function_cell() {
172
1
        let program = Program::new(vec![Expr::List(vec![
173
1
            Expr::Symbol("DEFUN".into()),
174
1
            Expr::Symbol("SUM".into()),
175
1
            Expr::List(vec![
176
1
                Expr::Symbol("A".into()),
177
1
                Expr::Symbol("B".into()),
178
1
                Expr::Symbol("C".into()),
179
1
            ]),
180
1
            Expr::String("Sums A, B, C".into()),
181
1
            Expr::List(vec![
182
1
                Expr::Symbol("+".into()),
183
1
                Expr::Symbol("A".into()),
184
1
                Expr::Symbol("B".into()),
185
1
                Expr::Symbol("C".into()),
186
1
            ]),
187
1
        ])]);
188
1
        let mut compiler = Compiler::new();
189
1
        let mut symbols = SymbolTable::with_builtins();
190
1
        compiler.compile(&program, &mut symbols).unwrap();
191

            
192
1
        let sym = symbols.lookup("SUM").expect("SUM should be defined");
193
1
        assert!(sym.function().is_some());
194
1
        assert!(matches!(sym.function(), Some(Expr::Lambda(_, _))));
195
1
        assert_eq!(sym.doc(), Some("Sums A, B, C"));
196
1
    }
197

            
198
    #[test]
199
1
    fn test_defun_no_doc_populates_function_cell() {
200
1
        let program = Program::new(vec![Expr::List(vec![
201
1
            Expr::Symbol("DEFUN".into()),
202
1
            Expr::Symbol("ADD".into()),
203
1
            Expr::List(vec![Expr::Symbol("A".into()), Expr::Symbol("B".into())]),
204
1
            Expr::List(vec![
205
1
                Expr::Symbol("+".into()),
206
1
                Expr::Symbol("A".into()),
207
1
                Expr::Symbol("B".into()),
208
1
            ]),
209
1
        ])]);
210
1
        let mut compiler = Compiler::new();
211
1
        let mut symbols = SymbolTable::with_builtins();
212
1
        compiler.compile(&program, &mut symbols).unwrap();
213

            
214
1
        let sym = symbols.lookup("ADD").expect("ADD should be defined");
215
1
        assert!(sym.function().is_some());
216
1
        assert!(sym.doc().is_none());
217
1
    }
218
}