1
mod binding;
2
mod compile_eval;
3
mod control;
4
pub(in crate::compiler::special) mod iteration;
5
mod labels;
6
mod lambda;
7
mod macros;
8
mod structure;
9

            
10
use tracing::debug;
11

            
12
use crate::ast::Expr;
13
use crate::error::{Error, Result};
14
use crate::runtime::SymbolTable;
15

            
16
use crate::ast::WasmType;
17

            
18
use super::context::CompileContext;
19
use super::emit::FunctionEmitter;
20

            
21
pub(in crate::compiler) use control::{
22
    form_diverges_for_test, is_runtime_test, is_truthy, reject_non_boolean_runtime_test,
23
};
24
pub(in crate::compiler) use lambda::monomorph::lookup_or_emit_monomorph;
25
pub(in crate::compiler) use lambda::{
26
    is_capture_free, try_emit_lambda_for_host_iter, try_emit_lambda_for_value,
27
};
28
pub(in crate::compiler) use structure::rhs_has_runtime_store;
29
pub(in crate::compiler) use structure::set_place as setf_set_place;
30

            
31
pub(super) type EvalFn = fn(&mut SymbolTable, &[Expr]) -> Result<Expr>;
32
pub(super) type CompileFn =
33
    fn(&mut CompileContext, &mut FunctionEmitter, &mut SymbolTable, &[Expr]) -> Result<()>;
34
pub(super) type StackFn =
35
    fn(&mut CompileContext, &mut FunctionEmitter, &mut SymbolTable, &[Expr]) -> Result<WasmType>;
36

            
37
/// Canonical metadata for a built-in special form. Mirrors `NativeSpec`
38
/// in `native/mod.rs` — every name appears exactly once across the four
39
/// dispatch paths so adding a form is a single row and missing handlers
40
/// surface at compile time. `compile` is the default effect path; the
41
/// optional `effect` overrides it when a form has specialized
42
/// effect-position codegen (e.g. DOLIST drops the loop's result).
43
pub(super) struct SpecialFormSpec {
44
    pub name: &'static str,
45
    pub eval: EvalFn,
46
    pub compile: CompileFn,
47
    pub stack: Option<StackFn>,
48
    pub effect: Option<CompileFn>,
49
}
50

            
51
const DOMAINS: &[&[SpecialFormSpec]] = &[
52
    control::FORMS,
53
    binding::FORMS,
54
    lambda::FORMS,
55
    macros::FORMS,
56
    compile_eval::FORMS,
57
    iteration::FORMS,
58
    labels::FORMS,
59
    structure::FORMS,
60
];
61

            
62
1029738
fn lookup(name: &str) -> Option<&'static SpecialFormSpec> {
63
1029738
    DOMAINS
64
1029738
        .iter()
65
4911992
        .flat_map(|d| d.iter())
66
26894482
        .find(|s| s.name == name)
67
1029738
}
68

            
69
/// True if `name` is a special form (LET, DEFUN, QUOTE, …). The effect-position
70
/// fallback uses this to keep routing definition / non-value forms through
71
/// `eval_value`, while value-producing native / host-fn calls are compiled and
72
/// their result dropped (so argument effects emit).
73
39618
pub(in crate::compiler) fn is_special_form(name: &str) -> bool {
74
39618
    lookup(name).is_some()
75
39618
}
76

            
77
877826
pub fn call(symbols: &mut SymbolTable, name: &str, args: &[Expr]) -> Result<Expr> {
78
877826
    debug!(special_form = %name, args = args.len(), "calling special form");
79
877826
    match lookup(name) {
80
877826
        Some(spec) => (spec.eval)(symbols, args),
81
        None => Err(Error::Compile(format!(
82
            "special form '{name}' not yet implemented"
83
        ))),
84
    }
85
877826
}
86

            
87
72422
pub(super) fn compile(
88
72422
    ctx: &mut CompileContext,
89
72422
    emit: &mut FunctionEmitter,
90
72422
    symbols: &mut SymbolTable,
91
72422
    name: &str,
92
72422
    args: &[Expr],
93
72422
) -> Result<()> {
94
72422
    debug!(special_form = %name, args = args.len(), "compiling special form");
95
72422
    match lookup(name) {
96
72422
        Some(spec) => (spec.compile)(ctx, emit, symbols, args),
97
        None => Err(Error::Compile(format!(
98
            "special form '{name}' not yet implemented"
99
        ))),
100
    }
101
72422
}
102

            
103
10579
pub(super) fn compile_for_effect(
104
10579
    ctx: &mut CompileContext,
105
10579
    emit: &mut FunctionEmitter,
106
10579
    symbols: &mut SymbolTable,
107
10579
    name: &str,
108
10579
    args: &[Expr],
109
10579
) -> Result<()> {
110
10579
    match lookup(name) {
111
10579
        Some(spec) => (spec.effect.unwrap_or(spec.compile))(ctx, emit, symbols, args),
112
        None => Err(Error::Compile(format!(
113
            "special form '{name}' not yet implemented"
114
        ))),
115
    }
116
10579
}
117

            
118
29252
pub(super) fn compile_for_stack(
119
29252
    ctx: &mut CompileContext,
120
29252
    emit: &mut FunctionEmitter,
121
29252
    symbols: &mut SymbolTable,
122
29252
    name: &str,
123
29252
    args: &[Expr],
124
29252
) -> Result<WasmType> {
125
29252
    match lookup(name) {
126
29252
        Some(spec) => match spec.stack {
127
29181
            Some(f) => f(ctx, emit, symbols, args),
128
71
            None => Err(Error::Compile(format!(
129
71
                "special form '{name}' cannot produce stack value"
130
71
            ))),
131
        },
132
        None => Err(Error::Compile(format!(
133
            "special form '{name}' not yet implemented"
134
        ))),
135
    }
136
29252
}
137

            
138
#[cfg(test)]
139
mod tests {
140
    use super::*;
141
    use std::collections::HashSet;
142

            
143
    #[test]
144
1
    fn registry_names_unique() {
145
1
        let mut seen = HashSet::new();
146
41
        for spec in DOMAINS.iter().flat_map(|d| d.iter()) {
147
41
            assert!(
148
41
                seen.insert(spec.name),
149
                "duplicate special-form registration: {}",
150
                spec.name
151
            );
152
        }
153
1
    }
154

            
155
    #[test]
156
1
    fn registry_lookup_covers_every_entry() {
157
41
        for spec in DOMAINS.iter().flat_map(|d| d.iter()) {
158
41
            assert!(
159
41
                lookup(spec.name).is_some(),
160
                "lookup({}) returned None",
161
                spec.name
162
            );
163
        }
164
1
    }
165
}