Lines
83.33 %
Functions
54.55 %
Branches
100 %
mod binding;
mod compile_eval;
mod control;
pub(in crate::compiler::special) mod iteration;
mod labels;
mod lambda;
mod macros;
mod structure;
use tracing::debug;
use crate::ast::Expr;
use crate::error::{Error, Result};
use crate::runtime::SymbolTable;
use crate::ast::WasmType;
use super::context::CompileContext;
use super::emit::FunctionEmitter;
pub(in crate::compiler) use control::{
form_diverges_for_test, is_runtime_test, is_truthy, reject_non_boolean_runtime_test,
};
pub(in crate::compiler) use lambda::monomorph::lookup_or_emit_monomorph;
pub(in crate::compiler) use lambda::{
is_capture_free, try_emit_lambda_for_host_iter, try_emit_lambda_for_value,
pub(in crate::compiler) use structure::rhs_has_runtime_store;
pub(in crate::compiler) use structure::set_place as setf_set_place;
pub(super) type EvalFn = fn(&mut SymbolTable, &[Expr]) -> Result<Expr>;
pub(super) type CompileFn =
fn(&mut CompileContext, &mut FunctionEmitter, &mut SymbolTable, &[Expr]) -> Result<()>;
pub(super) type StackFn =
fn(&mut CompileContext, &mut FunctionEmitter, &mut SymbolTable, &[Expr]) -> Result<WasmType>;
/// Canonical metadata for a built-in special form. Mirrors `NativeSpec`
/// in `native/mod.rs` — every name appears exactly once across the four
/// dispatch paths so adding a form is a single row and missing handlers
/// surface at compile time. `compile` is the default effect path; the
/// optional `effect` overrides it when a form has specialized
/// effect-position codegen (e.g. DOLIST drops the loop's result).
pub(super) struct SpecialFormSpec {
pub name: &'static str,
pub eval: EvalFn,
pub compile: CompileFn,
pub stack: Option<StackFn>,
pub effect: Option<CompileFn>,
}
const DOMAINS: &[&[SpecialFormSpec]] = &[
control::FORMS,
binding::FORMS,
lambda::FORMS,
macros::FORMS,
compile_eval::FORMS,
iteration::FORMS,
labels::FORMS,
structure::FORMS,
];
fn lookup(name: &str) -> Option<&'static SpecialFormSpec> {
DOMAINS
.iter()
.flat_map(|d| d.iter())
.find(|s| s.name == name)
/// True if `name` is a special form (LET, DEFUN, QUOTE, …). The effect-position
/// fallback uses this to keep routing definition / non-value forms through
/// `eval_value`, while value-producing native / host-fn calls are compiled and
/// their result dropped (so argument effects emit).
pub(in crate::compiler) fn is_special_form(name: &str) -> bool {
lookup(name).is_some()
pub fn call(symbols: &mut SymbolTable, name: &str, args: &[Expr]) -> Result<Expr> {
debug!(special_form = %name, args = args.len(), "calling special form");
match lookup(name) {
Some(spec) => (spec.eval)(symbols, args),
None => Err(Error::Compile(format!(
"special form '{name}' not yet implemented"
))),
pub(super) fn compile(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
symbols: &mut SymbolTable,
name: &str,
args: &[Expr],
) -> Result<()> {
debug!(special_form = %name, args = args.len(), "compiling special form");
Some(spec) => (spec.compile)(ctx, emit, symbols, args),
pub(super) fn compile_for_effect(
Some(spec) => (spec.effect.unwrap_or(spec.compile))(ctx, emit, symbols, args),
pub(super) fn compile_for_stack(
) -> Result<WasmType> {
Some(spec) => match spec.stack {
Some(f) => f(ctx, emit, symbols, args),
"special form '{name}' cannot produce stack value"
},
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn registry_names_unique() {
let mut seen = HashSet::new();
for spec in DOMAINS.iter().flat_map(|d| d.iter()) {
assert!(
seen.insert(spec.name),
"duplicate special-form registration: {}",
spec.name
);
fn registry_lookup_covers_every_entry() {
lookup(spec.name).is_some(),
"lookup({}) returned None",