1
//! `LIST` — multi-arg list constructor. Lowers to a `$pair` chain when any
2
//! argument is a runtime value (so the result rides a `$pair` chain rather
3
//! than being held as a compile-time `Expr::List`) through the CONS cell
4
//! builders DIRECTLY — never by synthesizing a `(CONS …)` form, which would
5
//! route through ordinary symbol dispatch and could hit a user
6
//! `(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;
16
use super::infer::chain_pair_element;
17

            
18
26311
pub(super) fn list(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
19
26311
    let resolved: Vec<Expr> = args
20
26311
        .iter()
21
46314
        .map(|a| eval_value(symbols, a))
22
26311
        .collect::<Result<_>>()?;
23
    // A runtime element can't ride a quoted compile-time list: consumers
24
    // (CAR/CDR/…) would extract a bare `WasmRuntime` placeholder with no real
25
    // stack value and emit invalid wasm. Report the chain `compile_pair_chain`
26
    // emits, typed from the ALREADY-RESOLVED elements so their side effects
27
    // stay single-applied.
28
26311
    if resolved.iter().any(Expr::is_wasm_runtime) {
29
10292
        return Ok(Expr::WasmRuntime(WasmType::PairRef(chain_pair_element(
30
10292
            &resolved,
31
        )?)));
32
16019
    }
33
16019
    Ok(Expr::quoted_list(resolved))
34
26311
}
35

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

            
55
664
fn any_runtime_arg(symbols: &mut SymbolTable, args: &[Expr]) -> Result<bool> {
56
1494
    for arg in args {
57
1494
        if eval_value(symbols, arg)?.is_wasm_runtime() {
58
            return Ok(true);
59
1494
        }
60
    }
61
664
    Ok(false)
62
664
}
63

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