1
//! `APPEND` — concatenate lists left to right. Constant-folds when every
2
//! argument is a compile-time list; otherwise lowers the two-list runtime
3
//! case by reversing the prefix onto an accumulator seeded with the
4
//! suffix chain (so the suffix is shared, not copied).
5

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

            
15
use crate::compiler::expr::emit_relabel_chain;
16

            
17
use super::datum::{compile_folded_to_stack, is_datum_result};
18
use super::infer::chain_pair_element;
19
use super::map::extract_list_elements;
20

            
21
1909
pub(super) fn append(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
22
2739
    if args.iter().any(|a| {
23
2739
        eval_value(symbols, a)
24
2739
            .map(|r| matches!(r.wasm_type(), Some(WasmType::PairRef(_))))
25
2739
            .unwrap_or(false)
26
2739
    }) {
27
1411
        return Ok(Expr::WasmRuntime(WasmType::PairRef(result_element(
28
1411
            symbols, args,
29
        )?)));
30
498
    }
31
498
    let mut out = Vec::new();
32
996
    for arg in args {
33
996
        let resolved = eval_value(symbols, arg)?;
34
996
        out.extend(extract_list_elements(&resolved).map_err(|_| {
35
            Error::Compile(format!(
36
                "APPEND expects lists, got {}",
37
                format_expr(&resolved)
38
            ))
39
        })?);
40
    }
41
498
    Ok(Expr::quoted_list(out))
42
1909
}
43

            
44
/// The element type of a runtime APPEND result: [`chain_pair_element`] over
45
/// every constant list's members plus one placeholder per runtime `PairRef`
46
/// chain, so a constant list is typed exactly as LIST types its cells. An
47
/// empty constant list / nil doesn't constrain the result.
48
2241
fn result_element(symbols: &mut SymbolTable, args: &[Expr]) -> Result<PairElement> {
49
2241
    let mut members = Vec::new();
50
4482
    for arg in args {
51
4482
        let resolved = eval_value(symbols, arg)?;
52
4482
        match resolved.wasm_type() {
53
2573
            Some(WasmType::PairRef(e)) => members.push(Expr::WasmRuntime(e.as_wasm_type())),
54
1909
            _ => members.extend(constant_list_members(&resolved)?),
55
        }
56
    }
57
2241
    chain_pair_element(&members)
58
2241
}
59

            
60
2739
fn constant_list_members(resolved: &Expr) -> Result<Vec<Expr>> {
61
2739
    extract_list_elements(resolved).map_err(|_| {
62
        Error::Compile(format!(
63
            "APPEND expects lists, got {}",
64
            format_expr(resolved)
65
        ))
66
    })
67
2739
}
68

            
69
664
pub(super) fn compile_append(
70
664
    ctx: &mut CompileContext,
71
664
    emit: &mut FunctionEmitter,
72
664
    symbols: &mut SymbolTable,
73
664
    args: &[Expr],
74
664
) -> Result<()> {
75
664
    let folded = append(symbols, args)?;
76
664
    if folded.is_wasm_runtime() {
77
415
        let ty = compile_append_to_stack(ctx, emit, symbols, args)?;
78
332
        return serialize_stack_to_output(ctx, emit, ty);
79
249
    }
80
249
    compile_expr(ctx, emit, symbols, &folded)
81
664
}
82

            
83
996
pub(super) fn compile_append_to_stack(
84
996
    ctx: &mut CompileContext,
85
996
    emit: &mut FunctionEmitter,
86
996
    symbols: &mut SymbolTable,
87
996
    args: &[Expr],
88
996
) -> Result<WasmType> {
89
    // All-constant APPEND folds to a single quoted list — render it as a datum
90
    // (matching CAR/CDR/REVERSE/CONS and the effect path) instead of forcing it
91
    // through the runtime materialization below, which can't represent symbols.
92
996
    let folded = append(symbols, args)?;
93
996
    if !folded.is_wasm_runtime() && is_datum_result(&folded) {
94
166
        return compile_folded_to_stack(ctx, emit, symbols, folded);
95
830
    }
96
830
    if args.len() != 2 {
97
        return Err(Error::Compile(
98
            "APPEND with a runtime list argument requires exactly 2 lists".to_string(),
99
        ));
100
830
    }
101
830
    let elem = result_element(symbols, args)?;
102
830
    let pair_idx = ctx.ids.ty_pair;
103
830
    let prefix_local = ctx.alloc_local(WasmType::PairRef(elem))?;
104
830
    let acc_local = ctx.alloc_local(WasmType::PairRef(elem))?;
105

            
106
    // acc ← suffix (shared tail); then prepend the prefix in reverse so
107
    // the final chain is prefix ++ suffix in original order.
108
830
    push_list_arg(ctx, emit, symbols, &args[1], elem)?;
109
830
    emit.local_set(acc_local);
110
830
    push_list_arg(ctx, emit, symbols, &args[0], elem)?;
111
747
    emit.local_set(prefix_local);
112

            
113
747
    let reversed_local = ctx.alloc_local(WasmType::PairRef(elem))?;
114
747
    reverse_into(ctx, emit, pair_idx, prefix_local, reversed_local);
115

            
116
    // Walk the reversed prefix, prepending each car onto acc (= suffix).
117
747
    emit.block_start();
118
747
    emit.loop_start();
119
747
    emit.local_get(reversed_local);
120
747
    emit.ref_is_null();
121
747
    emit.br_if(1);
122
747
    emit.local_get(reversed_local);
123
747
    emit.struct_get(pair_idx, 0);
124
747
    emit.local_get(acc_local);
125
747
    emit.call(ctx.ids.pair_new);
126
747
    emit.local_set(acc_local);
127
747
    emit.local_get(reversed_local);
128
747
    emit.struct_get(pair_idx, 1);
129
747
    emit.local_set(reversed_local);
130
747
    emit.br(0);
131
747
    emit.block_end();
132
747
    emit.block_end();
133

            
134
747
    emit.local_get(acc_local);
135
747
    Ok(WasmType::PairRef(elem))
136
996
}
137

            
138
/// Push an APPEND argument as a runtime `$pair` chain on the stack. A runtime
139
/// `PairRef` arg lowers directly; a constant list is materialized into a fresh
140
/// `$pair` chain whose cells ride the result element `elem` (so a concrete
141
/// result label never covers mismatched cells); nil / empty list becomes a
142
/// null pair.
143
1660
fn push_list_arg(
144
1660
    ctx: &mut CompileContext,
145
1660
    emit: &mut FunctionEmitter,
146
1660
    symbols: &mut SymbolTable,
147
1660
    arg: &Expr,
148
1660
    elem: PairElement,
149
1660
) -> Result<()> {
150
1660
    let resolved = eval_value(symbols, arg)?;
151
1660
    if let Some(WasmType::PairRef(arg_elem)) = resolved.wasm_type() {
152
830
        compile_for_stack(ctx, emit, symbols, arg)?;
153
830
        emit_relabel_chain(ctx, emit, arg_elem, elem);
154
830
        return Ok(());
155
830
    }
156
830
    let elements = constant_list_members(&resolved)?;
157
830
    super::cons::emit_constant_chain(ctx, emit, symbols, elements, elem)
158
1660
}
159

            
160
/// Reverses the chain in `src_local` into `dst_local` (a fresh null-seeded
161
/// accumulator), consuming `src_local`. Used so APPEND can prepend the
162
/// prefix onto the shared suffix in original order.
163
747
fn reverse_into(
164
747
    ctx: &mut CompileContext,
165
747
    emit: &mut FunctionEmitter,
166
747
    pair_idx: u32,
167
747
    src_local: u32,
168
747
    dst_local: u32,
169
747
) {
170
747
    emit.ref_null(pair_idx);
171
747
    emit.local_set(dst_local);
172
747
    emit.block_start();
173
747
    emit.loop_start();
174
747
    emit.local_get(src_local);
175
747
    emit.ref_is_null();
176
747
    emit.br_if(1);
177
747
    emit.local_get(src_local);
178
747
    emit.struct_get(pair_idx, 0);
179
747
    emit.local_get(dst_local);
180
747
    emit.call(ctx.ids.pair_new);
181
747
    emit.local_set(dst_local);
182
747
    emit.local_get(src_local);
183
747
    emit.struct_get(pair_idx, 1);
184
747
    emit.local_set(src_local);
185
747
    emit.br(0);
186
747
    emit.block_end();
187
747
    emit.block_end();
188
747
}