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

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

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

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

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

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

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

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

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