1
//! `CONS` constructor. Eval path constant-folds chains; compile path
2
//! pushes typed car/cdr onto the stack and calls `pair_new`.
3
//! `push_pair_car` boxes i32 values (indices range-checked by
4
//! `box_index`); `push_pair_cdr` emits `ref.null pair` for terminal `nil`,
5
//! materializes a constant list, or validates a typed `PairRef(elem)`
6
//! matches the declared element.
7

            
8
use crate::ast::{Expr, PairElement, WasmType};
9
use crate::compiler::context::CompileContext;
10
use crate::compiler::emit::FunctionEmitter;
11
use crate::compiler::expr::{
12
    compile_expr, compile_for_effect, compile_for_stack, compile_for_stack_as, emit_box_anyref,
13
    emit_box_cell, emit_relabel_chain, eval_value, serialize_stack_to_output,
14
};
15
use crate::error::{Error, Result};
16
use crate::runtime::SymbolTable;
17

            
18
use super::datum::{compile_folded_to_stack, is_datum_result};
19
use super::infer::{chain_pair_element, infer_pair_element, is_scalar_literal};
20

            
21
44156
pub(super) fn cons(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
22
44156
    if args.len() != 2 {
23
166
        return Err(Error::Arity {
24
166
            name: "CONS".to_string(),
25
166
            expected: 2,
26
166
            actual: args.len(),
27
166
        });
28
43990
    }
29
43990
    let car = eval_value(symbols, &args[0])?;
30
43741
    let cdr = eval_value(symbols, &args[1])?;
31
43741
    if car.is_wasm_runtime()
32
9379
        || cdr.is_wasm_runtime()
33
7470
        || matches!(cdr.wasm_type(), Some(WasmType::PairRef(_)))
34
    {
35
36271
        let elem = infer_pair_element(&car, &cdr)?;
36
36271
        return Ok(Expr::WasmRuntime(WasmType::PairRef(elem)));
37
7470
    }
38
7470
    match cdr {
39
3984
        Expr::Quote(inner) => match *inner {
40
3984
            Expr::List(mut elems) => {
41
3984
                elems.insert(0, car);
42
3984
                Ok(Expr::quoted_list(elems))
43
            }
44
            Expr::Nil => Ok(Expr::quoted_list(vec![car])),
45
            other => Ok(Expr::Quote(Box::new(Expr::cons(car, other)))),
46
        },
47
3320
        Expr::Nil => Ok(Expr::quoted_list(vec![car])),
48
166
        other => Ok(Expr::Quote(Box::new(Expr::cons(car, other)))),
49
    }
50
44156
}
51

            
52
1577
pub(super) fn compile_cons(
53
1577
    ctx: &mut CompileContext,
54
1577
    emit: &mut FunctionEmitter,
55
1577
    symbols: &mut SymbolTable,
56
1577
    args: &[Expr],
57
1577
) -> Result<()> {
58
1577
    let result = cons(symbols, args)?;
59
1411
    if matches!(result.wasm_type(), Some(WasmType::PairRef(_))) {
60
664
        let ty = compile_cons_to_stack(ctx, emit, symbols, args)?;
61
664
        serialize_stack_to_output(ctx, emit, ty)?;
62
664
        return Ok(());
63
747
    }
64
747
    compile_expr(ctx, emit, symbols, &result)
65
1577
}
66

            
67
10873
pub(super) fn compile_cons_to_stack(
68
10873
    ctx: &mut CompileContext,
69
10873
    emit: &mut FunctionEmitter,
70
10873
    symbols: &mut SymbolTable,
71
10873
    args: &[Expr],
72
10873
) -> Result<WasmType> {
73
    // A fully-constant cons folds to a quoted list datum (e.g. (cons 0 '(1 2 3))
74
    // → '(0 1 2 3)); render it as a datum so value position agrees with the
75
    // effect path instead of trapping when push_pair_cdr meets the quoted cdr.
76
    // Only a runtime car/cdr (PairRef result) takes the pair_new path below.
77
10873
    let folded = cons(symbols, args)?;
78
10873
    if !matches!(folded.wasm_type(), Some(WasmType::PairRef(_))) && is_datum_result(&folded) {
79
498
        return compile_folded_to_stack(ctx, emit, symbols, folded);
80
10375
    }
81
    // If an argument transfers control before the pair is built (a
82
    // `(return-from …)` / `(error …)` — args evaluate left-to-right before the
83
    // call), the rest is dead: the pair is never constructed, so a non-list cdr
84
    // must NOT be rejected. Emit the live prefix (which performs the exit) and
85
    // return a placeholder element. Classify divergence on a CLONE (the exit is
86
    // still recorded by the single emit below).
87
10375
    if crate::compiler::special::form_diverges_for_test(&mut symbols.clone(), &args[0])? {
88
83
        let ty = compile_for_stack(ctx, emit, symbols, &args[0])?;
89
83
        return Ok(WasmType::PairRef(
90
83
            PairElement::from_wasm_type(ty).unwrap_or(PairElement::AnyRef),
91
83
        ));
92
10292
    }
93
10292
    if crate::compiler::special::form_diverges_for_test(&mut symbols.clone(), &args[1])? {
94
        // Car is evaluated (for its effects) then the cdr exits before
95
        // `pair_new`; the car value is dead, so compile it for effect.
96
83
        compile_for_effect(ctx, emit, symbols, &args[0])?;
97
83
        let ty = compile_for_stack(ctx, emit, symbols, &args[1])?;
98
83
        return Ok(WasmType::PairRef(
99
83
            PairElement::from_wasm_type(ty).unwrap_or(PairElement::AnyRef),
100
83
        ));
101
10209
    }
102
    // Decide the element type up front via the eval pipeline so we can
103
    // reject heterogeneous mixing before emitting any wasm.
104
10209
    let car_resolved = eval_value(symbols, &args[0])?;
105
10209
    let cdr_resolved = eval_value(symbols, &args[1])?;
106
10209
    let elem = infer_pair_element(&car_resolved, &cdr_resolved)?;
107
10209
    push_pair_car(ctx, emit, symbols, &args[0], elem)?;
108
10126
    push_pair_cdr(ctx, emit, symbols, &args[1], elem, &cdr_resolved)?;
109
10043
    emit.call(ctx.ids.pair_new);
110
10043
    Ok(WasmType::PairRef(elem))
111
10873
}
112

            
113
/// Emit `elem_args` as a nul-terminated `$pair` chain. LIST uses this so it
114
/// never synthesizes a `(CONS …)` form — that would route through symbol
115
/// dispatch and could hit a user `(defun cons …)` shadow. `elem_args` are the
116
/// ORIGINAL element expressions (emitted once via `push_pair_car`); the
117
/// chain's element type is decided up front from their resolved values by
118
/// [`chain_pair_element`] — the same rule the eval `list` reports — so every
119
/// cell is emitted homogeneously.
120
6972
pub(super) fn compile_pair_chain(
121
6972
    ctx: &mut CompileContext,
122
6972
    emit: &mut FunctionEmitter,
123
6972
    symbols: &mut SymbolTable,
124
6972
    elem_args: &[Expr],
125
6972
) -> Result<WasmType> {
126
6972
    let resolved: Vec<Expr> = elem_args
127
6972
        .iter()
128
10790
        .map(|a| eval_value(symbols, a))
129
6972
        .collect::<Result<_>>()?;
130
6972
    let elem = chain_pair_element(&resolved)?;
131
6972
    emit_pair_chain(ctx, emit, symbols, elem_args, elem)?;
132
6889
    Ok(WasmType::PairRef(elem))
133
6972
}
134

            
135
/// Materialize a constant list's `elements` (DATA, not expressions) as a
136
/// `$pair` chain whose cells ride `elem`, the element its consumer declared.
137
/// A member the cell push would otherwise RESOLVE (a bare symbol → variable
138
/// lookup, a nested list → a call) is quoted so it stays literal; one with no
139
/// runtime representation then surfaces "cannot compile to WASM stack value".
140
3901
pub(super) fn emit_constant_chain(
141
3901
    ctx: &mut CompileContext,
142
3901
    emit: &mut FunctionEmitter,
143
3901
    symbols: &mut SymbolTable,
144
3901
    elements: Vec<Expr>,
145
3901
    elem: PairElement,
146
3901
) -> Result<()> {
147
3901
    let members: Vec<Expr> = elements
148
3901
        .into_iter()
149
5727
        .map(|e| match e {
150
5561
            Expr::Number(_) | Expr::String(_) | Expr::T | Expr::Nil => e,
151
166
            other => Expr::Quote(Box::new(other)),
152
5727
        })
153
3901
        .collect();
154
3901
    emit_pair_chain(ctx, emit, symbols, &members, elem)
155
3901
}
156

            
157
/// Emit the nested `pair_new` cells for `elem_args` (original expressions),
158
/// every car at the unified `elem` slot. Innermost (`nil`) cdr first via
159
/// recursion, so the wasm stack order per cell is `[car, cdr_ref]` → `pair_new`.
160
27058
fn emit_pair_chain(
161
27058
    ctx: &mut CompileContext,
162
27058
    emit: &mut FunctionEmitter,
163
27058
    symbols: &mut SymbolTable,
164
27058
    elem_args: &[Expr],
165
27058
    elem: PairElement,
166
27058
) -> Result<()> {
167
27058
    let Some((car, rest)) = elem_args.split_first() else {
168
10707
        emit.ref_null(ctx.ids.ty_pair);
169
10707
        return Ok(());
170
    };
171
16351
    push_pair_car(ctx, emit, symbols, car, elem)?;
172
16185
    emit_pair_chain(ctx, emit, symbols, rest, elem)?;
173
16185
    emit.call(ctx.ids.pair_new);
174
16185
    Ok(())
175
27058
}
176

            
177
26560
fn push_pair_car(
178
26560
    ctx: &mut CompileContext,
179
26560
    emit: &mut FunctionEmitter,
180
26560
    symbols: &mut SymbolTable,
181
26560
    arg: &Expr,
182
26560
    elem: PairElement,
183
26560
) -> Result<()> {
184
26560
    let resolved = eval_value(&mut symbols.clone(), arg)?;
185
26560
    if crate::compiler::expr::is_quoted_datum(&resolved) {
186
249
        return Err(Error::Compile(format!(
187
249
            "cannot compile to WASM stack value: {} has no list cell representation",
188
249
            crate::compiler::expr::format_expr(&resolved)
189
249
        )));
190
26311
    }
191
26311
    match elem {
192
5312
        PairElement::AnyRef => push_anyref_car(ctx, emit, symbols, arg),
193
        // Value cells: a dimension-flexible integer literal coerces to the
194
        // cell's stratum (Index or Scalar) rather than mismatching the slot.
195
        PairElement::I32 | PairElement::Bool | PairElement::Ratio => {
196
19671
            compile_for_stack_as(ctx, emit, symbols, arg, elem.as_wasm_type())?;
197
19671
            emit_box_cell(ctx, emit, elem, elem);
198
19671
            Ok(())
199
        }
200
        // Reference cells (string / entity / commodity): keep the strict match
201
        // so a `nil` or wrong-typed car stays a COMPILE error rather than a
202
        // typed null that `CAR`'s non-null cast would trap on at runtime.
203
        _ => {
204
1328
            let actual = compile_for_stack(ctx, emit, symbols, arg)?;
205
1328
            if PairElement::from_wasm_type(actual) != Some(elem) {
206
                return Err(Error::Compile(format!(
207
                    "CONS car: expected {elem} to match the inferred pair element, got {actual}"
208
                )));
209
1328
            }
210
1328
            Ok(())
211
        }
212
    }
213
26560
}
214

            
215
/// A heterogeneous cell: `nil` rides as a null ref (never an i31 zero), a
216
/// scalar literal as a ratio (never a truncated index), anything else as its
217
/// own value boxed to anyref.
218
5312
fn push_anyref_car(
219
5312
    ctx: &mut CompileContext,
220
5312
    emit: &mut FunctionEmitter,
221
5312
    symbols: &mut SymbolTable,
222
5312
    arg: &Expr,
223
5312
) -> Result<()> {
224
5312
    match eval_value(&mut symbols.clone(), arg)? {
225
        Expr::Nil => {
226
830
            if !matches!(arg, Expr::Nil) {
227
                compile_for_effect(ctx, emit, symbols, arg)?;
228
830
            }
229
830
            emit.ref_null_any();
230
        }
231
4482
        resolved if is_scalar_literal(&resolved) => {
232
166
            compile_for_stack_as(ctx, emit, symbols, arg, WasmType::Ratio)?;
233
        }
234
        _ => {
235
4316
            let actual = compile_for_stack(ctx, emit, symbols, arg)?;
236
4316
            emit_box_anyref(ctx, emit, actual);
237
        }
238
    }
239
5312
    Ok(())
240
5312
}
241

            
242
10126
fn push_pair_cdr(
243
10126
    ctx: &mut CompileContext,
244
10126
    emit: &mut FunctionEmitter,
245
10126
    symbols: &mut SymbolTable,
246
10126
    arg: &Expr,
247
10126
    elem: PairElement,
248
10126
    resolved: &Expr,
249
10126
) -> Result<()> {
250
10126
    if matches!(resolved, Expr::Nil) {
251
3320
        emit.ref_null(ctx.ids.ty_pair);
252
3320
        return Ok(());
253
6806
    }
254
6806
    if let Expr::Quote(inner) = resolved
255
498
        && let Expr::List(members) = inner.as_ref()
256
    {
257
498
        if !matches!(arg, Expr::Quote(_)) {
258
415
            compile_for_effect(ctx, emit, symbols, arg)?;
259
83
        }
260
498
        return emit_constant_chain(ctx, emit, symbols, members.clone(), elem);
261
6308
    }
262
6308
    let actual = compile_for_stack(ctx, emit, symbols, arg)?;
263
1162
    match actual {
264
6225
        WasmType::PairRef(actual_elem) if actual_elem == elem => Ok(()),
265
        // The widened-AnyRef case accepts any typed pair as the cdr —
266
        // `$pair` already carries `anyref` cars, so no per-element
267
        // adjustment is needed once we've decided to ride the
268
        // heterogeneous variant.
269
1162
        WasmType::PairRef(actual_elem) if elem == PairElement::AnyRef => {
270
1162
            emit_relabel_chain(ctx, emit, actual_elem, elem);
271
1162
            Ok(())
272
        }
273
        WasmType::PairRef(actual_elem) => Err(Error::Compile(format!(
274
            "CONS cdr element type mismatch — expected pair<{elem}>, got pair<{actual_elem}>"
275
        ))),
276
83
        other => Err(Error::Compile(format!(
277
83
            "CONS cdr must be a pair or nil, got {other}"
278
83
        ))),
279
    }
280
10126
}