1
//! Expression-level compile + eval dispatch.
2
//!
3
//! Split into topic-focused submodules so each file stays under the
4
//! ~500-line CLAUDE.md guideline:
5
//! - [`atoms`] — primitive emit (nil, bool, number, string, symbol,
6
//!   push_ratio) and the `compile_text` plumbing they share.
7
//! - [`compile`] — the generic `compile_expr` / `compile_quoted_expr`
8
//!   dispatch + `serialize_stack_to_output`.
9
//! - [`effect`] — effect-position codegen (`compile_for_effect` plus
10
//!   the SETF / IF specialisations it owns).
11
//! - [`stack`] — stack-position codegen (`compile_for_stack` and the
12
//!   ratio/numeric refinements).
13
//! - [`call`] — function call dispatch (symbol calls, lambda calls,
14
//!   lambda-param binding).
15
//! - [`eval`] — eval-only path (`call`, `eval_value`, `dispatch_symbol`,
16
//!   macro expansion, lambda-call eval, `resolve_arg`).
17
//! - [`quasiquote`] — quasiquote / unquote expansion.
18
//! - [`format`] — debug/display formatting for Expr and RuntimeValue.
19
//!
20
//! `LOCAL_*` indices, `compile_program`, and the two `compile_body*`
21
//! helpers stay here in `mod.rs` since they're the cross-submodule
22
//! glue every entry point lands on.
23

            
24
mod atoms;
25
mod boxing;
26
mod call;
27
mod compile;
28
mod effect;
29
mod eval;
30
mod format;
31
mod quasiquote;
32
mod stack;
33

            
34
use crate::ast::{Expr, Program, WasmType};
35
use crate::error::Result;
36
use crate::runtime::SymbolTable;
37

            
38
use super::context::CompileContext;
39
use super::emit::FunctionEmitter;
40

            
41
// Reserved local-pool indices the compiler relies on across codegen
42
// paths. The output-base + GC-array slots come first so they're
43
// available before the `next_local` allocator kicks in. See
44
// `CompileContext::build_locals_declaration` for the matching wasm-
45
// side declaration.
46
pub const LOCAL_GC_ARR: u32 = 0;
47
pub const LOCAL_IDX: u32 = 1;
48
pub const LOCAL_OUTPUT_BASE: u32 = 2;
49
pub const LOCAL_TEMP_RATIO: u32 = 3;
50
pub const LOCAL_TEMP_I32: u32 = 4;
51
/// Absolute address of the entity currently being written to the output
52
/// buffer (`output_base + next_write_pos`). Every output writer positions its
53
/// entity header + data fields relative to this so writes APPEND at the runtime
54
/// `next_write_pos` — the single output protocol that lets compile-time-known
55
/// writes (a program result) and dynamic-count writes (a `create-tag` loop)
56
/// share one buffer without overwriting each other.
57
pub const LOCAL_ENTITY_BASE: u32 = 5;
58

            
59
// Submodule re-exports. Keeps `super::expr::<name>` callers stable
60
// across the refactor so neighbouring modules (`native`, `special`,
61
// the compiler root) don't have to learn the submodule layout.
62
pub(super) use atoms::{compile_nil, compile_string, compile_t, emit_nil_default, push_ratio};
63
pub(super) use boxing::{emit_box_anyref, emit_box_cell, emit_relabel_chain};
64
pub(super) use call::compile_call;
65
pub(super) use compile::{compile_expr, compile_quoted_expr, serialize_stack_to_output};
66
pub(super) use effect::compile_for_effect;
67
pub(super) use stack::{
68
    classify_stack_type, compile_call_for_stack, compile_for_stack, compile_for_stack_as,
69
    is_quoted_datum,
70
};
71

            
72
pub(crate) use eval::{call, eval_value, expand_macro, resolve_arg};
73
pub(crate) use format::format_expr;
74

            
75
/// An already-resolved value re-entering `call` as an argument: a symbol or a
76
/// list is data there, so it is quoted rather than evaluated a second time.
77
14359
pub(crate) fn as_literal_arg(expr: &Expr) -> Expr {
78
14359
    match expr {
79
581
        Expr::Symbol(_) | Expr::List(_) | Expr::Cons(_, _) => Expr::Quote(Box::new(expr.clone())),
80
13778
        _ => expr.clone(),
81
    }
82
14359
}
83

            
84
100277
pub fn compile_program(
85
100277
    ctx: &mut CompileContext,
86
100277
    emit: &mut FunctionEmitter,
87
100277
    symbols: &mut SymbolTable,
88
100277
    program: &Program,
89
100277
) -> Result<()> {
90
100277
    ctx.serializer().begin_output(emit);
91
100277
    if program.exprs.is_empty() {
92
1
        compile_nil(ctx, emit);
93
1
        return Ok(());
94
100276
    }
95
137875
    let has_trigger = program.exprs.iter().any(|e| {
96
1411
        matches!(
97
133799
            e,
98
133799
            Expr::List(elems)
99
133799
                if matches!(elems.first(), Some(Expr::Symbol(name)) if name == "DEFUN")
100
6725
                && matches!(elems.get(1), Some(Expr::Symbol(name)) if name == "SHOULD-APPLY")
101
        )
102
137875
    });
103
100276
    if has_trigger {
104
5395
        for expr in &program.exprs {
105
5395
            compile_for_effect(ctx, emit, symbols, expr)?;
106
        }
107
1411
        Ok(())
108
    } else {
109
98865
        for expr in &program.exprs[..program.exprs.len() - 1] {
110
37599
            compile_for_effect(ctx, emit, symbols, expr)?;
111
        }
112
98699
        compile_expr(ctx, emit, symbols, program.exprs.last().unwrap())
113
    }
114
100277
}
115

            
116
26892
pub(super) fn compile_body(
117
26892
    ctx: &mut CompileContext,
118
26892
    emit: &mut FunctionEmitter,
119
26892
    symbols: &mut SymbolTable,
120
26892
    body: &[Expr],
121
26892
) -> Result<()> {
122
26892
    for expr in &body[..body.len() - 1] {
123
5810
        compile_for_effect(ctx, emit, symbols, expr)?;
124
    }
125
26892
    compile_expr(ctx, emit, symbols, body.last().unwrap())
126
26892
}
127

            
128
7636
pub(super) fn compile_body_for_stack(
129
7636
    ctx: &mut CompileContext,
130
7636
    emit: &mut FunctionEmitter,
131
7636
    symbols: &mut SymbolTable,
132
7636
    body: &[Expr],
133
7636
) -> Result<WasmType> {
134
8134
    for expr in &body[..body.len() - 1] {
135
8134
        compile_for_effect(ctx, emit, symbols, expr)?;
136
    }
137
7636
    compile_for_stack(ctx, emit, symbols, body.last().unwrap())
138
7636
}