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
962755
fn lookup(name: &str) -> Option<&'static SpecialFormSpec> {
63
962755
    DOMAINS
64
962755
        .iter()
65
4586573
        .flat_map(|d| d.iter())
66
25122469
        .find(|s| s.name == name)
67
962755
}
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
37970
pub(in crate::compiler) fn is_special_form(name: &str) -> bool {
74
37970
    lookup(name).is_some()
75
37970
}
76

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

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

            
103
10152
pub(super) fn compile_for_effect(
104
10152
    ctx: &mut CompileContext,
105
10152
    emit: &mut FunctionEmitter,
106
10152
    symbols: &mut SymbolTable,
107
10152
    name: &str,
108
10152
    args: &[Expr],
109
10152
) -> Result<()> {
110
10152
    match lookup(name) {
111
10152
        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
10152
}
117

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