1
use crate::ast::{Expr, LambdaParams, WasmType};
2
use crate::error::{Error, Result};
3
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
4

            
5
use super::super::context::CompileContext;
6
use super::super::emit::FunctionEmitter;
7
use super::super::expr::{
8
    compile_expr, compile_for_effect, compile_for_stack, compile_for_stack_as, eval_value,
9
    format_expr, serialize_stack_to_output,
10
};
11
use super::SpecialFormSpec;
12

            
13
pub(super) const FORMS: &[SpecialFormSpec] = &[
14
    SpecialFormSpec {
15
        name: "DEFSTRUCT",
16
        eval: defstruct,
17
        compile: compile_defstruct,
18
        stack: None,
19
        effect: None,
20
    },
21
    SpecialFormSpec {
22
        name: "SETF",
23
        eval: setf,
24
        compile: compile_setf,
25
        stack: Some(compile_setf_for_stack),
26
        effect: None,
27
    },
28
];
29

            
30
pub(super) fn compile_defstruct(
31
    ctx: &mut CompileContext,
32
    emit: &mut FunctionEmitter,
33
    symbols: &mut SymbolTable,
34
    args: &[Expr],
35
) -> Result<()> {
36
    let result = defstruct(symbols, args)?;
37
    compile_expr(ctx, emit, symbols, &result)
38
}
39

            
40
68
pub(super) fn compile_setf(
41
68
    ctx: &mut CompileContext,
42
68
    emit: &mut FunctionEmitter,
43
68
    symbols: &mut SymbolTable,
44
68
    args: &[Expr],
45
68
) -> Result<()> {
46
    // Value/effect position (`compile_expr` dispatch — e.g. a body/program tail
47
    // `(setf x v)`): SETF's value (it returns the assigned value) must be
48
    // SERIALIZED to the output buffer, leaving the wasm stack empty — the
49
    // `compile_expr` contract. The `stack` handler keeps the value on the stack
50
    // for genuine stack consumers (`(list … (setf n …))`); leaving it here would
51
    // strand a value at the end of the enclosing block (invalid wasm).
52
68
    let ty = compile_setf_for_stack(ctx, emit, symbols, args)?;
53
68
    serialize_stack_to_output(ctx, emit, ty)
54
68
}
55

            
56
/// SETF in stack/value position: assign and LEAVE the assigned value on the
57
/// stack (so `(list … (setf n …))` / `(+ (setf n …) …)` work). A runtime-local
58
/// symbol place emits the value then `local.tee` (last pair) / `local.set`
59
/// (earlier pairs); const / struct-field places fall back to the eval-rebind
60
/// path, then materialize the resulting value.
61
272
pub(super) fn compile_setf_for_stack(
62
272
    ctx: &mut CompileContext,
63
272
    emit: &mut FunctionEmitter,
64
272
    symbols: &mut SymbolTable,
65
272
    args: &[Expr],
66
272
) -> Result<WasmType> {
67
272
    if !args.len().is_multiple_of(2) {
68
        return Err(Error::Compile(
69
            "SETF requires an even number of arguments (place value pairs)".to_string(),
70
        ));
71
272
    }
72
272
    let pairs: Vec<&[Expr]> = args.chunks(2).collect();
73
272
    let Some(last) = pairs.len().checked_sub(1) else {
74
        // `(setf)` with no pairs ≡ nil — push the falsy i31 (typed Bool so it
75
        // serializes as Nil), matching `setf`'s empty-arg result.
76
        emit.i32_const(0);
77
        return Ok(WasmType::Bool);
78
    };
79
    // Process each place/value pair INDIVIDUALLY (mirroring the effect path): a
80
    // runtime-local place emits a typed store; a const / struct-field place takes
81
    // the eval-rebind path (no wasm store). A whole-form fallback to `setf` would
82
    // silently DROP a runtime-local store in a MIXED form — `set_place` is a
83
    // no-op for a `WasmLocal` symbol, so its `local.set` would never be emitted.
84
272
    let mut result_ty = WasmType::Bool;
85
340
    for (i, pair) in pairs.iter().enumerate() {
86
340
        let place = &pair[0];
87
340
        let value_expr = &pair[1];
88
340
        let is_last = i == last;
89
340
        let runtime_local = match place {
90
340
            Expr::Symbol(name) => {
91
340
                symbols
92
340
                    .lookup(name)
93
340
                    .and_then(|s| s.value())
94
340
                    .and_then(|v| match v {
95
204
                        Expr::WasmLocal(idx, ty) => Some((*idx, *ty)),
96
136
                        _ => None,
97
340
                    })
98
            }
99
            _ => None,
100
        };
101
340
        match runtime_local {
102
204
            Some((idx, ty)) => {
103
                // Coerce the rhs to the LOCAL's declared type — a dimension-flexible
104
                // literal (`(setf ratio-local 1)`) crosses the Index↔Scalar
105
                // boundary and `nil` lands on the typed default; a genuine clash is
106
                // a clean compile error, not an invalid `local.set`.
107
204
                compile_for_stack_as(ctx, emit, symbols, value_expr, ty)?;
108
                // Reassigning a closure local invalidates any source body recorded
109
                // for it at bind time: the local now holds a DIFFERENT closure (or
110
                // nil), so inlining the old body in a later FOLD would apply the
111
                // wrong function. Forget AFTER compiling the rhs (it may FOLD the
112
                // OLD closure) and before the store — FOLD then `call_ref`s.
113
204
                if matches!(ty, WasmType::Closure(_)) {
114
                    ctx.forget_closure_body(idx);
115
204
                }
116
204
                if is_last {
117
136
                    emit.local_tee(idx);
118
136
                    result_ty = ty;
119
136
                } else {
120
68
                    emit.local_set(idx);
121
68
                }
122
            }
123
            None => {
124
                // const / struct-field place: eval-rebind (no runtime store for
125
                // the PLACE). But if the value EXPRESSION itself contains a nested
126
                // runtime-local `setf`, that store rides only the eval path —
127
                // which is a no-op for a WasmLocal — and would be silently
128
                // dropped. Emit the value for effect first so the nested store
129
                // is generated exactly once; take the place's compile-time value
130
                // from a clone so the live table isn't double-mutated.
131
136
                let value = if rhs_has_runtime_store(value_expr, symbols) {
132
68
                    compile_for_effect(ctx, emit, symbols, value_expr)?;
133
68
                    eval_value(&mut symbols.clone(), value_expr)?
134
                } else {
135
68
                    eval_value(symbols, value_expr)?
136
                };
137
136
                let assigned = set_place(symbols, place, value)?;
138
136
                if is_last {
139
136
                    result_ty = compile_for_stack(ctx, emit, symbols, &assigned)?;
140
                }
141
            }
142
        }
143
    }
144
272
    Ok(result_ty)
145
272
}
146

            
147
/// Whether `expr` contains a nested `(setf <place> …)` / `(set! …)` whose place
148
/// is a symbol bound to a runtime `WasmLocal`. Such a store must be EMITTED
149
/// (`local.set`) — the eval-rebind path that a const/struct-field SETF place
150
/// uses for its value is a no-op for a `WasmLocal` and would drop it.
151
136
pub(in crate::compiler) fn rhs_has_runtime_store(expr: &Expr, symbols: &SymbolTable) -> bool {
152
136
    let Expr::List(elems) = expr else {
153
68
        return false;
154
    };
155
68
    if let Some(Expr::Symbol(head)) = elems.first()
156
68
        && (head.eq_ignore_ascii_case("setf") || head.eq_ignore_ascii_case("set!"))
157
    {
158
68
        for pair in elems[1..].chunks(2) {
159
68
            if let Some(Expr::Symbol(place)) = pair.first()
160
                && matches!(
161
68
                    symbols.lookup(place).and_then(|s| s.value()),
162
                    Some(Expr::WasmLocal(_, _))
163
                )
164
            {
165
68
                return true;
166
            }
167
        }
168
    }
169
    elems.iter().any(|e| rhs_has_runtime_store(e, symbols))
170
136
}
171

            
172
399508
pub(super) fn defstruct(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
173
399508
    if args.is_empty() {
174
        return Err(Error::Compile(
175
            "DEFSTRUCT requires a structure name".to_string(),
176
        ));
177
399508
    }
178

            
179
399508
    let name = args[0].as_symbol().ok_or_else(|| {
180
        Error::Compile(format!(
181
            "DEFSTRUCT: expected structure name, got {:?}",
182
            args[0]
183
        ))
184
    })?;
185

            
186
399508
    let fields: Vec<String> = args[1..]
187
399508
        .iter()
188
2390112
        .map(|arg| {
189
2390112
            arg.as_symbol()
190
2390112
                .map(std::string::ToString::to_string)
191
2390112
                .ok_or_else(|| {
192
                    Error::Compile(format!("DEFSTRUCT: expected field name, got {arg:?}"))
193
                })
194
2390112
        })
195
399508
        .collect::<Result<_>>()?;
196
399508
    symbols.define_struct_fields(name.to_string(), fields.clone());
197

            
198
399508
    let constructor_name = format!("MAKE-{name}");
199
399508
    let constructor_params = LambdaParams {
200
399508
        required: Vec::new(),
201
399508
        optional: Vec::new(),
202
399508
        rest: None,
203
2390112
        key: fields.iter().map(|field| (field.clone(), None)).collect(),
204
399508
        aux: Vec::new(),
205
    };
206

            
207
399508
    let constructor_body = {
208
399508
        let field_values: Vec<Expr> = fields
209
399508
            .iter()
210
2390112
            .map(|field| Expr::Symbol(field.clone()))
211
399508
            .collect();
212

            
213
399508
        Expr::List(vec![
214
399508
            Expr::Symbol("MAKE-STRUCT-INSTANCE".to_string()),
215
399508
            Expr::Quote(Box::new(Expr::String(name.to_string()))),
216
399508
            Expr::Quote(Box::new(Expr::List(
217
2390112
                fields.iter().map(|f| Expr::String(f.clone())).collect(),
218
            ))),
219
399508
            Expr::List(
220
399508
                vec![Expr::Symbol("LIST".to_string())]
221
399508
                    .into_iter()
222
399508
                    .chain(field_values)
223
399508
                    .collect(),
224
399508
            ),
225
        ])
226
    };
227

            
228
399508
    let constructor = Expr::Lambda(constructor_params, Box::new(constructor_body));
229
399508
    symbols.define(
230
399508
        Symbol::new(&constructor_name, SymbolKind::Function).with_function(constructor.clone()),
231
    );
232

            
233
2390112
    for field in &fields {
234
2390112
        let accessor_name = format!("{name}-{field}");
235
2390112
        let accessor_params = LambdaParams::simple(vec!["INSTANCE".to_string()]);
236
2390112
        let accessor_body = Expr::List(vec![
237
2390112
            Expr::Symbol("STRUCT-FIELD".to_string()),
238
2390112
            Expr::Symbol("INSTANCE".to_string()),
239
2390112
            Expr::Quote(Box::new(Expr::String(field.clone()))),
240
2390112
        ]);
241
2390112
        let accessor = Expr::Lambda(accessor_params, Box::new(accessor_body));
242
2390112
        symbols.define(Symbol::new(&accessor_name, SymbolKind::Function).with_function(accessor));
243
2390112

            
244
2390112
        let setf_accessor_name = format!("(SETF {accessor_name})");
245
2390112
        let setf_params =
246
2390112
            LambdaParams::simple(vec!["INSTANCE".to_string(), "NEW-VALUE".to_string()]);
247
2390112
        let setf_body = Expr::List(vec![
248
2390112
            Expr::Symbol("STRUCT-SET-FIELD".to_string()),
249
2390112
            Expr::Symbol("INSTANCE".to_string()),
250
2390112
            Expr::Quote(Box::new(Expr::String(field.clone()))),
251
2390112
            Expr::Symbol("NEW-VALUE".to_string()),
252
2390112
        ]);
253
2390112
        let setf_function = Expr::Lambda(setf_params, Box::new(setf_body));
254
2390112
        symbols.define(
255
2390112
            Symbol::new(&setf_accessor_name, SymbolKind::Function).with_function(setf_function),
256
2390112
        );
257
2390112
    }
258

            
259
399508
    let predicate_name = format!("{name}-P");
260
399508
    let predicate_params = LambdaParams::simple(vec!["OBJECT".to_string()]);
261
399508
    let predicate_body = Expr::List(vec![
262
399508
        Expr::Symbol("STRUCT-P".to_string()),
263
399508
        Expr::Symbol("OBJECT".to_string()),
264
399508
        Expr::Quote(Box::new(Expr::String(name.to_string()))),
265
399508
    ]);
266
399508
    let predicate = Expr::Lambda(predicate_params, Box::new(predicate_body));
267
399508
    symbols.define(Symbol::new(&predicate_name, SymbolKind::Function).with_function(predicate));
268

            
269
399508
    Ok(Expr::Quote(Box::new(Expr::Symbol(name.to_string()))))
270
399508
}
271

            
272
1020
pub(super) fn setf(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
273
1020
    if !args.len().is_multiple_of(2) {
274
        return Err(Error::Compile(
275
            "SETF requires an even number of arguments (place value pairs)".to_string(),
276
        ));
277
1020
    }
278

            
279
1020
    if args.is_empty() {
280
        return Ok(Expr::Nil);
281
1020
    }
282

            
283
1020
    let mut last_value = Expr::Nil;
284
1088
    for pair in args.chunks(2) {
285
1088
        let place = &pair[0];
286
1088
        let value = eval_value(symbols, &pair[1])?;
287

            
288
1088
        last_value = set_place(symbols, place, value)?;
289
    }
290

            
291
1020
    Ok(last_value)
292
1020
}
293

            
294
1292
pub(in crate::compiler) fn set_place(
295
1292
    symbols: &mut SymbolTable,
296
1292
    place: &Expr,
297
1292
    value: Expr,
298
1292
) -> Result<Expr> {
299
68
    match place {
300
1224
        Expr::Symbol(name) => {
301
1224
            match symbols.lookup_mut(name) {
302
                // A var already materialized as a runtime wasm local keeps that
303
                // binding: the local IS its storage and codegen emits the
304
                // runtime store (`local.set`/`local.tee`). Overwriting it with
305
                // the assigned value's `WasmRuntime`/const placeholder would
306
                // drop the local index, so a later read or assign (e.g. the
307
                // codegen resolve pass over `(list … (setf n …))`) would compile
308
                // a producerless `WasmRuntime` → invalid wasm. `setf` still
309
                // returns the assigned value.
310
1088
                Some(sym) if matches!(sym.value(), Some(Expr::WasmLocal(_, _))) => {}
311
204
                Some(sym) => sym.set_value(value.clone()),
312
136
                None => symbols
313
136
                    .define(Symbol::new(name, SymbolKind::Variable).with_value(value.clone())),
314
            }
315
1224
            Ok(value)
316
        }
317

            
318
68
        Expr::List(exprs) if !exprs.is_empty() => {
319
68
            if let Expr::Symbol(func_name) = &exprs[0] {
320
68
                let setf_name = format!("(SETF {func_name})");
321
68
                if symbols.contains(&setf_name) {
322
68
                    let mut setf_args = exprs[1..].to_vec();
323
68
                    setf_args.push(value.clone());
324
68
                    let call_args: Vec<Expr> = [Expr::Symbol(setf_name)]
325
68
                        .into_iter()
326
68
                        .chain(setf_args)
327
68
                        .collect();
328
68
                    let new_whole = super::super::expr::call(symbols, &call_args)?;
329
                    // Common-Lisp semantics: the setf accessor returns the
330
                    // updated whole-object value. Rebind it back into the
331
                    // outermost variable place so e.g.
332
                    //   (setf (person-name p) "Jane")
333
                    // mutates p's binding to the new struct.
334
68
                    if let Some(Expr::Symbol(target)) = exprs.get(1)
335
68
                        && symbols.contains(target)
336
                    {
337
68
                        set_place(symbols, &Expr::Symbol(target.clone()), new_whole.clone())?;
338
                    }
339
68
                    Ok(new_whole)
340
                } else {
341
                    Err(Error::Compile(format!(
342
                        "No setf method defined for accessor '{func_name}'"
343
                    )))
344
                }
345
            } else {
346
                Err(Error::Compile(
347
                    "Invalid place in SETF - expected function call".to_string(),
348
                ))
349
            }
350
        }
351

            
352
        _ => Err(Error::Compile(format!(
353
            "Invalid place in SETF: {}",
354
            format_expr(place)
355
        ))),
356
    }
357
1292
}