1
//! `DEFUN`, `DEFVAR`, `DEFPARAMETER` — name-binding forms that mutate
2
//! the symbol table in place.
3
//!
4
//! The compile-side wrappers run the eval-side handler first (to
5
//! register the symbol), then call `promote_def_binding` so a runtime
6
//! init expression actually lands its value in a wasm local. Without
7
//! that promotion, later uses of the symbol resolve to a
8
//! `WasmRuntime` placeholder and emit no value at codegen.
9

            
10
use crate::ast::Expr;
11
use crate::compiler::context::CompileContext;
12
use crate::compiler::emit::FunctionEmitter;
13
use crate::compiler::expr::{compile_expr, compile_for_stack, eval_value};
14
use crate::error::{Error, Result};
15
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
16

            
17
use super::parse::parse_lambda_params;
18

            
19
168
pub(super) fn compile_defun(
20
168
    ctx: &mut CompileContext,
21
168
    emit: &mut FunctionEmitter,
22
168
    symbols: &mut SymbolTable,
23
168
    args: &[Expr],
24
168
) -> Result<()> {
25
168
    let result = defun(symbols, args)?;
26
168
    compile_expr(ctx, emit, symbols, &result)
27
168
}
28

            
29
996
pub(super) fn compile_defvar(
30
996
    ctx: &mut CompileContext,
31
996
    emit: &mut FunctionEmitter,
32
996
    symbols: &mut SymbolTable,
33
996
    args: &[Expr],
34
996
) -> Result<()> {
35
    // defvar / defparameter are pure side-effects on the symbol
36
    // table — they don't emit a value into the script's output stream
37
    // and don't push a value onto the wasm stack. The
38
    // `promote_def_binding` step is the only wasm emission: when the
39
    // init expression is a runtime form (e.g. `(entity-count)`), it
40
    // allocates a local and emits the init's producer into it so
41
    // subsequent uses of the symbol resolve to a real `WasmLocal`.
42
    // Constant inits emit no wasm here at all.
43
996
    defvar(symbols, args)?;
44
996
    promote_def_binding(ctx, emit, symbols, args.first(), args.get(1))
45
996
}
46

            
47
415
pub(super) fn compile_defparam(
48
415
    ctx: &mut CompileContext,
49
415
    emit: &mut FunctionEmitter,
50
415
    symbols: &mut SymbolTable,
51
415
    args: &[Expr],
52
415
) -> Result<()> {
53
415
    defparameter(symbols, args)?;
54
332
    promote_def_binding(ctx, emit, symbols, args.first(), args.get(1))
55
415
}
56

            
57
/// After `defvar` / `defparameter` evaluates the init expression and
58
/// stores its result as the symbol's value, replace any
59
/// `Expr::WasmRuntime(_)` placeholder with `Expr::WasmLocal(idx, ty)`
60
/// — and emit the init's wasm into that local. Without this, the
61
/// symbol's value claims to live at runtime but nothing put it on the
62
/// stack; later uses resolve to the placeholder and `compile_for_stack`
63
/// silently emits no value.
64
1328
fn promote_def_binding(
65
1328
    ctx: &mut CompileContext,
66
1328
    emit: &mut FunctionEmitter,
67
1328
    symbols: &mut SymbolTable,
68
1328
    name_expr: Option<&Expr>,
69
1328
    init_expr: Option<&Expr>,
70
1328
) -> Result<()> {
71
1328
    let (Some(Expr::Symbol(name)), Some(init)) = (name_expr, init_expr) else {
72
83
        return Ok(());
73
    };
74
1079
    if !matches!(
75
1245
        symbols.lookup(name).and_then(|s| s.value()),
76
        Some(Expr::WasmRuntime(_))
77
    ) {
78
1079
        return Ok(());
79
166
    }
80
    // Size the local from the type codegen actually pushes, not the eval-time
81
    // placeholder — the two can disagree (e.g. a fold over a runtime closure),
82
    // and trusting the placeholder would `local.set` a mistyped value.
83
166
    let actual_ty = compile_for_stack(ctx, emit, symbols, init)?;
84
166
    let idx = ctx.alloc_local(actual_ty)?;
85
166
    emit.local_set(idx);
86
166
    if let Some(sym) = symbols.lookup_mut(name) {
87
166
        sym.set_value(Expr::WasmLocal(idx, actual_ty));
88
166
    }
89
166
    Ok(())
90
1328
}
91

            
92
505060
pub(super) fn defun(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
93
505060
    if args.len() < 3 {
94
        return Err(Error::Compile(
95
            "DEFUN requires a name, parameter list, and body".to_string(),
96
        ));
97
505060
    }
98
505060
    let name = match &args[0] {
99
505060
        Expr::Symbol(s) => s.clone(),
100
        other => {
101
            return Err(Error::Compile(format!(
102
                "DEFUN: expected symbol name, got {other:?}"
103
            )));
104
        }
105
    };
106
505060
    let params = parse_lambda_params("DEFUN", &args[1])?;
107
505060
    let (doc, body_idx) = match args.get(2) {
108
333
        Some(Expr::String(s)) if args.len() > 3 => (Some(s.clone()), 3),
109
504810
        _ => (None, 2),
110
    };
111
505060
    if body_idx >= args.len() {
112
        return Err(Error::Compile("DEFUN: missing body".to_string()));
113
505060
    }
114
505060
    let body = if args.len() == body_idx + 1 {
115
504977
        args[body_idx].clone()
116
    } else {
117
83
        let mut forms = Vec::with_capacity(args.len() - body_idx + 1);
118
83
        forms.push(Expr::Symbol("BEGIN".to_string()));
119
83
        forms.extend_from_slice(&args[body_idx..]);
120
83
        Expr::List(forms)
121
    };
122
505060
    let lambda = Expr::Lambda(params, Box::new(body));
123

            
124
505060
    if let Some(sym) = symbols.lookup_mut(&name) {
125
332
        sym.set_function(lambda);
126
332
        if let Some(d) = doc {
127
            sym.set_doc(d);
128
332
        }
129
    } else {
130
504728
        let mut sym = Symbol::new(&name, SymbolKind::Variable).with_function(lambda);
131
504728
        if let Some(d) = doc {
132
250
            sym = sym.with_doc(d);
133
504478
        }
134
504728
        symbols.define(sym);
135
    }
136
505060
    Ok(Expr::Quote(Box::new(Expr::Symbol(name))))
137
505060
}
138

            
139
1079
pub(super) fn defvar(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
140
1079
    if args.is_empty() {
141
        return Err(Error::Compile(
142
            "DEFVAR requires at least a name".to_string(),
143
        ));
144
1079
    }
145
1079
    let name = match &args[0] {
146
1079
        Expr::Symbol(s) => s.clone(),
147
        other => {
148
            return Err(Error::Compile(format!(
149
                "DEFVAR: expected symbol name, got {other:?}"
150
            )));
151
        }
152
    };
153
1079
    let initial = args.get(1).map(|e| eval_value(symbols, e)).transpose()?;
154
1079
    let doc = match args.get(2) {
155
166
        Some(Expr::String(s)) => Some(s.clone()),
156
913
        _ => None,
157
    };
158

            
159
1079
    if let Some(sym) = symbols.lookup_mut(&name) {
160
83
        if sym.value().is_none()
161
            && let Some(val) = initial
162
        {
163
            sym.set_value(val);
164
83
        }
165
83
        if let Some(d) = doc {
166
            sym.set_doc(d);
167
83
        }
168
    } else {
169
996
        let mut sym = Symbol::new(&name, SymbolKind::Variable);
170
996
        if let Some(val) = initial {
171
913
            sym = sym.with_value(val);
172
913
        }
173
996
        if let Some(d) = doc {
174
166
            sym = sym.with_doc(d);
175
830
        }
176
996
        symbols.define(sym);
177
    }
178
1079
    Ok(Expr::Quote(Box::new(Expr::Symbol(name))))
179
1079
}
180

            
181
415
pub(super) fn defparameter(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
182
415
    if args.len() < 2 {
183
83
        return Err(Error::Compile(
184
83
            "DEFPARAMETER requires a name and initial value".to_string(),
185
83
        ));
186
332
    }
187
332
    let name = match &args[0] {
188
332
        Expr::Symbol(s) => s.clone(),
189
        other => {
190
            return Err(Error::Compile(format!(
191
                "DEFPARAMETER: expected symbol name, got {other:?}"
192
            )));
193
        }
194
    };
195
332
    let value = eval_value(symbols, &args[1])?;
196
332
    let doc = match args.get(2) {
197
        Some(Expr::String(s)) => Some(s.clone()),
198
332
        _ => None,
199
    };
200

            
201
332
    if let Some(sym) = symbols.lookup_mut(&name) {
202
83
        sym.set_value(value);
203
83
        if let Some(d) = doc {
204
            sym.set_doc(d);
205
83
        }
206
    } else {
207
249
        let mut sym = Symbol::new(&name, SymbolKind::Variable).with_value(value);
208
249
        if let Some(d) = doc {
209
            sym = sym.with_doc(d);
210
249
        }
211
249
        symbols.define(sym);
212
    }
213
332
    Ok(Expr::Quote(Box::new(Expr::Symbol(name))))
214
415
}