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

            
29
816
pub(super) fn compile_defvar(
30
816
    ctx: &mut CompileContext,
31
816
    emit: &mut FunctionEmitter,
32
816
    symbols: &mut SymbolTable,
33
816
    args: &[Expr],
34
816
) -> 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
816
    defvar(symbols, args)?;
44
816
    promote_def_binding(ctx, emit, symbols, args.first(), args.get(1))
45
816
}
46

            
47
340
pub(super) fn compile_defparam(
48
340
    ctx: &mut CompileContext,
49
340
    emit: &mut FunctionEmitter,
50
340
    symbols: &mut SymbolTable,
51
340
    args: &[Expr],
52
340
) -> Result<()> {
53
340
    defparameter(symbols, args)?;
54
272
    promote_def_binding(ctx, emit, symbols, args.first(), args.get(1))
55
340
}
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
1088
fn promote_def_binding(
65
1088
    ctx: &mut CompileContext,
66
1088
    emit: &mut FunctionEmitter,
67
1088
    symbols: &mut SymbolTable,
68
1088
    name_expr: Option<&Expr>,
69
1088
    init_expr: Option<&Expr>,
70
1088
) -> Result<()> {
71
1088
    let (Some(Expr::Symbol(name)), Some(init)) = (name_expr, init_expr) else {
72
68
        return Ok(());
73
    };
74
884
    if !matches!(
75
1020
        symbols.lookup(name).and_then(|s| s.value()),
76
        Some(Expr::WasmRuntime(_))
77
    ) {
78
884
        return Ok(());
79
136
    }
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
136
    let actual_ty = compile_for_stack(ctx, emit, symbols, init)?;
84
136
    let idx = ctx.alloc_local(actual_ty)?;
85
136
    emit.local_set(idx);
86
136
    if let Some(sym) = symbols.lookup_mut(name) {
87
136
        sym.set_value(Expr::WasmLocal(idx, actual_ty));
88
136
    }
89
136
    Ok(())
90
1088
}
91

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

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

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

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

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

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