1
//! `LIST` — multi-arg list constructor. Desugars to a `$pair` chain
2
//! when any argument is a runtime value (so the result rides a `$pair`
3
//! chain rather than being held as a compile-time `Expr::List`) by calling
4
//! the CONS builder functions DIRECTLY — never by synthesizing a `(CONS …)`
5
//! form, which would route through ordinary symbol dispatch and could hit a
6
//! user `(defun cons …)` shadow.
7

            
8
use crate::ast::{Expr, WasmType};
9
use crate::compiler::context::CompileContext;
10
use crate::compiler::emit::FunctionEmitter;
11
use crate::compiler::expr::{compile_expr, eval_value, serialize_stack_to_output};
12
use crate::error::Result;
13
use crate::runtime::SymbolTable;
14

            
15
use super::cons::{compile_pair_chain, cons};
16

            
17
9180
pub(super) fn list(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
18
9180
    let resolved: Vec<Expr> = args
19
9180
        .iter()
20
20468
        .map(|a| eval_value(symbols, a))
21
9180
        .collect::<Result<_>>()?;
22
    // A runtime element can't ride a quoted compile-time list: consumers
23
    // (CAR/CDR/…) would extract a bare `WasmRuntime` placeholder with no real
24
    // stack value and emit invalid wasm. Fold the ALREADY-RESOLVED elements
25
    // right-to-left through the CONS builder so the result is a proper
26
    // `WasmRuntime(PairRef(elem))` — exactly what CONS yields. Folding the
27
    // resolved values (not the source `args`) keeps element side effects
28
    // single-applied (they ran once during the eval above), and calling `cons`
29
    // directly bypasses any `CONS` symbol shadow.
30
9180
    if resolved.iter().any(Expr::is_wasm_runtime) {
31
1632
        let mut chain = Expr::Nil;
32
2244
        for elem in resolved.iter().rev() {
33
2244
            chain = cons(symbols, &[elem.clone(), chain])?;
34
        }
35
1632
        return Ok(chain);
36
7548
    }
37
7548
    Ok(Expr::Quote(Box::new(Expr::List(resolved))))
38
9180
}
39

            
40
544
pub(super) fn compile_list(
41
544
    ctx: &mut CompileContext,
42
544
    emit: &mut FunctionEmitter,
43
544
    symbols: &mut SymbolTable,
44
544
    args: &[Expr],
45
544
) -> Result<()> {
46
    // Classify runtime-ness on a CLONE so the live symbol table is mutated by
47
    // exactly ONE resolve pass — the chosen branch below. (Calling `list()` on
48
    // the live table here, then `compile_pair_chain` again, would resolve the
49
    // element expressions twice on the live table.)
50
544
    if any_runtime_arg(&mut symbols.clone(), args)? {
51
        let ty = compile_list_to_stack(ctx, emit, symbols, args)?;
52
        serialize_stack_to_output(ctx, emit, ty)?;
53
        return Ok(());
54
544
    }
55
544
    let folded = list(symbols, args)?;
56
544
    compile_expr(ctx, emit, symbols, &folded)
57
544
}
58

            
59
544
fn any_runtime_arg(symbols: &mut SymbolTable, args: &[Expr]) -> Result<bool> {
60
1224
    for arg in args {
61
1224
        if eval_value(symbols, arg)?.is_wasm_runtime() {
62
            return Ok(true);
63
1224
        }
64
    }
65
544
    Ok(false)
66
544
}
67

            
68
/// Emit `(list …)` as a `$pair` chain. Resolves each element ONCE, then folds
69
/// right-to-left through `compile_cons_to_stack` — building each pair as a
70
/// two-element arg slice `[car, <inner-chain>]` whose cdr is the already-built
71
/// inner chain expression. Calls the CONS builder directly (no `(CONS …)`
72
/// form, hence no symbol-dispatch / shadow surface) and threads the resolved
73
/// car values so element side effects are applied exactly once.
74
1156
pub(super) fn compile_list_to_stack(
75
1156
    ctx: &mut CompileContext,
76
1156
    emit: &mut FunctionEmitter,
77
1156
    symbols: &mut SymbolTable,
78
1156
    args: &[Expr],
79
1156
) -> Result<WasmType> {
80
1156
    compile_pair_chain(ctx, emit, symbols, args)
81
1156
}