Skip to main content

nomiscript/compiler/special/
mod.rs

1mod binding;
2mod compile_eval;
3mod control;
4pub(in crate::compiler::special) mod iteration;
5mod labels;
6mod lambda;
7mod macros;
8mod structure;
9
10use tracing::debug;
11
12use crate::ast::Expr;
13use crate::error::{Error, Result};
14use crate::runtime::SymbolTable;
15
16use crate::ast::WasmType;
17
18use super::context::CompileContext;
19use super::emit::FunctionEmitter;
20
21pub(in crate::compiler) use control::{
22    form_diverges_for_test, is_runtime_test, is_truthy, reject_non_boolean_runtime_test,
23};
24pub(in crate::compiler) use lambda::monomorph::lookup_or_emit_monomorph;
25pub(in crate::compiler) use lambda::{
26    is_capture_free, try_emit_lambda_for_host_iter, try_emit_lambda_for_value,
27};
28pub(in crate::compiler) use structure::rhs_has_runtime_store;
29pub(in crate::compiler) use structure::set_place as setf_set_place;
30
31pub(super) type EvalFn = fn(&mut SymbolTable, &[Expr]) -> Result<Expr>;
32pub(super) type CompileFn =
33    fn(&mut CompileContext, &mut FunctionEmitter, &mut SymbolTable, &[Expr]) -> Result<()>;
34pub(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).
43pub(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
51const 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
62fn lookup(name: &str) -> Option<&'static SpecialFormSpec> {
63    DOMAINS
64        .iter()
65        .flat_map(|d| d.iter())
66        .find(|s| s.name == name)
67}
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).
73pub(in crate::compiler) fn is_special_form(name: &str) -> bool {
74    lookup(name).is_some()
75}
76
77pub fn call(symbols: &mut SymbolTable, name: &str, args: &[Expr]) -> Result<Expr> {
78    debug!(special_form = %name, args = args.len(), "calling special form");
79    match lookup(name) {
80        Some(spec) => (spec.eval)(symbols, args),
81        None => Err(Error::Compile(format!(
82            "special form '{name}' not yet implemented"
83        ))),
84    }
85}
86
87pub(super) fn compile(
88    ctx: &mut CompileContext,
89    emit: &mut FunctionEmitter,
90    symbols: &mut SymbolTable,
91    name: &str,
92    args: &[Expr],
93) -> Result<()> {
94    debug!(special_form = %name, args = args.len(), "compiling special form");
95    match lookup(name) {
96        Some(spec) => (spec.compile)(ctx, emit, symbols, args),
97        None => Err(Error::Compile(format!(
98            "special form '{name}' not yet implemented"
99        ))),
100    }
101}
102
103pub(super) fn compile_for_effect(
104    ctx: &mut CompileContext,
105    emit: &mut FunctionEmitter,
106    symbols: &mut SymbolTable,
107    name: &str,
108    args: &[Expr],
109) -> Result<()> {
110    match lookup(name) {
111        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}
117
118pub(super) fn compile_for_stack(
119    ctx: &mut CompileContext,
120    emit: &mut FunctionEmitter,
121    symbols: &mut SymbolTable,
122    name: &str,
123    args: &[Expr],
124) -> Result<WasmType> {
125    match lookup(name) {
126        Some(spec) => match spec.stack {
127            Some(f) => f(ctx, emit, symbols, args),
128            None => Err(Error::Compile(format!(
129                "special form '{name}' cannot produce stack value"
130            ))),
131        },
132        None => Err(Error::Compile(format!(
133            "special form '{name}' not yet implemented"
134        ))),
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use std::collections::HashSet;
142
143    #[test]
144    fn registry_names_unique() {
145        let mut seen = HashSet::new();
146        for spec in DOMAINS.iter().flat_map(|d| d.iter()) {
147            assert!(
148                seen.insert(spec.name),
149                "duplicate special-form registration: {}",
150                spec.name
151            );
152        }
153    }
154
155    #[test]
156    fn registry_lookup_covers_every_entry() {
157        for spec in DOMAINS.iter().flat_map(|d| d.iter()) {
158            assert!(
159                lookup(spec.name).is_some(),
160                "lookup({}) returned None",
161                spec.name
162            );
163        }
164    }
165}