1
//! `MAP` eval + compile.
2
//!
3
//! Three paths:
4
//! - **Constant-fold**: when the function and every list arg resolves
5
//!   at compile time, `map_fn` walks element-by-element via the eval
6
//!   `call` pipeline and returns the resulting list literal.
7
//! - **Runtime closure, literal list**: emit a per-element FUNCALL,
8
//!   stash each result in a fresh local, then walk the locals in
9
//!   reverse and prepend each car onto the accumulator via `pair_new`.
10
//! - **Runtime closure or runtime `PairRef` list**: walk the input
11
//!   chain at runtime, calling the function on each car, prepend onto
12
//!   a reversed accumulator, and run `emit_reverse_loop` once at the
13
//!   end so the output preserves input order.
14

            
15
use crate::ast::{Expr, PairElement, WasmType};
16
use crate::compiler::context::CompileContext;
17
use crate::compiler::emit::FunctionEmitter;
18
use crate::compiler::expr::{
19
    as_literal_arg, call, compile_expr, compile_for_stack, compile_for_stack_as, eval_value,
20
    format_expr,
21
};
22
use crate::error::{Error, Result};
23
use crate::runtime::SymbolTable;
24

            
25
use super::infer::{chain_pair_element, literal_pair_element};
26
use super::reverse::emit_reverse_loop;
27
use crate::compiler::expr::emit_box_cell;
28

            
29
1411
pub(super) fn map_fn(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
30
1411
    if args.len() < 2 {
31
83
        return Err(Error::Arity {
32
83
            name: "MAP".to_string(),
33
83
            expected: 2,
34
83
            actual: args.len(),
35
83
        });
36
1328
    }
37
1328
    let fn_resolved = eval_value(symbols, &args[0])?;
38
1328
    if let Some(WasmType::Closure(sig)) = fn_resolved.wasm_type() {
39
        // MAP's OUTPUT element is the closure's RESULT type (it transforms each
40
        // element) — NOT the input element. The closure-emit site records the
41
        // result so this eval prediction agrees with codegen (`compile_map_*`
42
        // uses the per-row closure result). Fall back to the input element only
43
        // when the result isn't recorded or isn't a pair-cell type (e.g. a
44
        // nested-pair result, which codegen rejects anyway).
45
166
        let elem = symbols
46
166
            .closure_result(sig)
47
166
            .and_then(PairElement::from_wasm_type)
48
166
            .or(runtime_pair_input_element(symbols, &args[1..])?)
49
166
            .unwrap_or(PairElement::Ratio);
50
166
        return Ok(Expr::WasmRuntime(WasmType::PairRef(elem)));
51
1162
    }
52
1162
    if list_args_have_runtime_pair(symbols, &args[1..])? {
53
166
        let elem = runtime_pair_input_element(symbols, &args[1..])?
54
166
            .ok_or_else(|| Error::Compile("MAP: runtime list element type unknown".to_string()))?;
55
166
        return Ok(Expr::WasmRuntime(WasmType::PairRef(elem)));
56
996
    }
57
    // Static fn + constant list(s): try to fully reduce. If the body produces
58
    // a runtime value per element (a side-effecting native, or a runtime
59
    // result), MAP ISN'T constant-foldable — surface a runtime placeholder
60
    // typed by the body's per-element result so the compile path lowers each
61
    // element call at runtime instead of baking placeholders into a list.
62
996
    let folded = constant_fold_map(symbols, &args[0], &args[1..])?;
63
913
    if let Some(elem) = mapped_runtime_element(&folded) {
64
        return Ok(Expr::WasmRuntime(WasmType::PairRef(elem)));
65
913
    }
66
913
    Ok(folded)
67
1411
}
68

            
69
/// If a folded MAP result is a list whose elements are runtime placeholders
70
/// (the body produced runtime values), returns the unified `PairElement` of
71
/// those results — the signal that MAP must lower at runtime. `None` when the
72
/// fold produced a genuine constant list.
73
1494
fn mapped_runtime_element(folded: &Expr) -> Option<PairElement> {
74
1494
    let elems = match folded {
75
1328
        Expr::Quote(inner) => match inner.as_ref() {
76
1328
            Expr::List(elems) => elems,
77
            _ => return None,
78
        },
79
        Expr::List(elems) => elems,
80
166
        _ => return None,
81
    };
82
1328
    if !elems.iter().any(Expr::is_wasm_runtime) {
83
996
        return None;
84
332
    }
85
332
    Some(chain_pair_element(elems).unwrap_or(PairElement::AnyRef))
86
1494
}
87

            
88
1909
fn constant_fold_map(
89
1909
    symbols: &mut SymbolTable,
90
1909
    function_arg: &Expr,
91
1909
    list_args: &[Expr],
92
1909
) -> Result<Expr> {
93
1909
    let lists: Vec<Vec<Expr>> = list_args
94
1909
        .iter()
95
2324
        .map(|arg| {
96
2324
            let resolved = eval_value(symbols, arg)?;
97
2324
            extract_list_elements(&resolved)
98
2324
        })
99
1909
        .collect::<Result<_>>()?;
100

            
101
1826
    let min_len = lists.iter().map(std::vec::Vec::len).min().unwrap_or(0);
102

            
103
1826
    let mut results = Vec::with_capacity(min_len);
104
3735
    for i in 0..min_len {
105
3735
        let mut call_args = vec![function_arg.clone()];
106
4648
        for list in &lists {
107
4648
            call_args.push(as_literal_arg(&list[i]));
108
4648
        }
109
3735
        let result = call(symbols, &call_args)?;
110
3403
        let resolved_result = eval_value(symbols, &result)?;
111
3403
        results.push(resolved_result);
112
    }
113
1494
    Ok(Expr::quoted_list(results))
114
1909
}
115

            
116
2573
fn list_args_have_runtime_pair(symbols: &mut SymbolTable, list_args: &[Expr]) -> Result<bool> {
117
2988
    for arg in list_args {
118
2988
        let resolved = eval_value(symbols, arg)?;
119
2988
        if matches!(resolved.wasm_type(), Some(WasmType::PairRef(_))) {
120
415
            return Ok(true);
121
2573
        }
122
    }
123
2158
    Ok(false)
124
2573
}
125

            
126
332
fn runtime_pair_input_element(
127
332
    symbols: &mut SymbolTable,
128
332
    list_args: &[Expr],
129
332
) -> Result<Option<PairElement>> {
130
332
    for arg in list_args {
131
332
        let resolved = eval_value(symbols, arg)?;
132
332
        if let Some(WasmType::PairRef(elem)) = resolved.wasm_type() {
133
166
            return Ok(Some(elem));
134
166
        }
135
    }
136
166
    Ok(None)
137
332
}
138

            
139
7968
pub(super) fn extract_list_elements(expr: &Expr) -> Result<Vec<Expr>> {
140
7968
    match expr {
141
        Expr::List(elems) => Ok(elems.clone()),
142
1079
        Expr::Nil => Ok(vec![]),
143
6806
        Expr::Quote(inner) => match inner.as_ref() {
144
6806
            Expr::List(elems) => Ok(elems.clone()),
145
            Expr::Nil => Ok(vec![]),
146
            other => Err(Error::Compile(format!(
147
                "MAP expects list arguments, got quoted {}",
148
                format_expr(other)
149
            ))),
150
        },
151
83
        other => Err(Error::Compile(format!(
152
83
            "MAP expects list arguments, got {}",
153
83
            format_expr(other)
154
83
        ))),
155
    }
156
7968
}
157

            
158
1411
pub(super) fn compile_map(
159
1411
    ctx: &mut CompileContext,
160
1411
    emit: &mut FunctionEmitter,
161
1411
    symbols: &mut SymbolTable,
162
1411
    args: &[Expr],
163
1411
) -> Result<()> {
164
1411
    if args.len() < 2 {
165
        return Err(Error::Arity {
166
            name: "MAP".to_string(),
167
            expected: 2,
168
            actual: args.len(),
169
        });
170
1411
    }
171
1411
    let fn_resolved = eval_value(symbols, &args[0])?;
172
1411
    let runtime_closure = matches!(fn_resolved.wasm_type(), Some(WasmType::Closure(_)));
173
1411
    let runtime_list = list_args_have_runtime_pair(symbols, &args[1..])?;
174

            
175
1411
    if runtime_closure || runtime_list {
176
498
        let ty = compile_map_to_stack(ctx, emit, symbols, args)?;
177
498
        return crate::compiler::expr::serialize_stack_to_output(ctx, emit, ty);
178
913
    }
179
913
    let result = constant_fold_map(symbols, &args[0], &args[1..])?;
180
581
    if mapped_runtime_element(&result).is_some() {
181
        // Body isn't constant-foldable — lower the static fn + constant list
182
        // element-by-element via the runtime path.
183
332
        let ty = compile_map_to_stack(ctx, emit, symbols, args)?;
184
332
        return crate::compiler::expr::serialize_stack_to_output(ctx, emit, ty);
185
249
    }
186
249
    compile_expr(ctx, emit, symbols, &result)
187
1411
}
188

            
189
1992
pub(super) fn compile_map_to_stack(
190
1992
    ctx: &mut CompileContext,
191
1992
    emit: &mut FunctionEmitter,
192
1992
    symbols: &mut SymbolTable,
193
1992
    args: &[Expr],
194
1992
) -> Result<WasmType> {
195
1992
    if args.len() < 2 {
196
        return Err(Error::Compile(
197
            "MAP: stack-position lowering requires a function and at least one list".to_string(),
198
        ));
199
1992
    }
200
    // Single runtime `PairRef` list: walk the chain at runtime.
201
1992
    if args.len() == 2
202
1909
        && let Some(WasmType::PairRef(elem)) = eval_value(symbols, &args[1])?.wasm_type()
203
    {
204
996
        return compile_map_runtime_list(ctx, emit, symbols, &args[0], &args[1], elem);
205
996
    }
206
    // One or more constant lists with a callable whose body isn't constant-
207
    // foldable (runtime/side-effecting). Zip the constant lists and apply the
208
    // function per row via FUNCALL — serves a closure OR a bare lambda, and any
209
    // arity of lists. (A multi-list MAP over RUNTIME pair chains is not
210
    // supported here — only the single-list runtime chain is.)
211
996
    let lists: Vec<Vec<Expr>> = args[1..]
212
996
        .iter()
213
1079
        .map(|arg| {
214
1079
            let resolved = eval_value(symbols, arg)?;
215
1079
            if matches!(resolved.wasm_type(), Some(WasmType::PairRef(_))) {
216
                return Err(Error::Compile(
217
                    "MAP: a multi-list mapping requires constant lists; a runtime list \
218
                     is only supported as the sole list argument"
219
                        .to_string(),
220
                ));
221
1079
            }
222
1079
            extract_list_elements(&resolved)
223
1079
        })
224
996
        .collect::<Result<_>>()?;
225
996
    compile_map_literal(ctx, emit, symbols, &args[0], &lists)
226
1992
}
227

            
228
/// Lower `(map <fn> '(a0 a1 ...) '(b0 b1 ...) ...)` row-by-row over one or more
229
/// constant lists (zipped to the shortest). Each per-row call rides FUNCALL (→
230
/// `call_ref` for a closure, inline for a bare lambda); results land in fresh
231
/// per-row locals. We then walk the locals in reverse and prepend each car onto
232
/// the accumulator via `pair_new` so the output keeps input order without an
233
/// extra reverse pass.
234
996
fn compile_map_literal(
235
996
    ctx: &mut CompileContext,
236
996
    emit: &mut FunctionEmitter,
237
996
    symbols: &mut SymbolTable,
238
996
    fn_arg: &Expr,
239
996
    lists: &[Vec<Expr>],
240
996
) -> Result<WasmType> {
241
996
    let rows = lists.iter().map(Vec::len).min().unwrap_or(0);
242
996
    if rows == 0 {
243
        emit.ref_null(ctx.ids.ty_pair);
244
        return Ok(WasmType::PairRef(PairElement::Ratio));
245
996
    }
246
2241
    let calls: Vec<Expr> = (0..rows).map(|row| row_call(fn_arg, lists, row)).collect();
247
    // The rows' resolved values decide the chain by the LIST rule — the same
248
    // values the eval mirror `mapped_runtime_element` unifies.
249
996
    let closure_result = match eval_value(&mut symbols.clone(), fn_arg)?.wasm_type() {
250
498
        Some(WasmType::Closure(sig)) => Some(ctx.closure_sig(sig).result),
251
498
        _ => None,
252
    };
253
996
    let values = calls
254
996
        .iter()
255
2241
        .map(|call| row_value(symbols, call, closure_result))
256
996
        .collect::<Result<Vec<_>>>()?;
257
996
    let chain_elem = chain_pair_element(&values).map_err(|_| unpairable_row_error())?;
258
996
    let mut element_locals: Vec<(u32, PairElement)> = Vec::with_capacity(rows);
259
2241
    for (call, value) in calls.iter().zip(&values) {
260
2241
        let elem = compile_map_row(ctx, emit, symbols, call, value, chain_elem)?;
261
2241
        let local = ctx.alloc_local(elem.as_wasm_type())?;
262
2241
        emit.local_set(local);
263
2241
        element_locals.push((local, elem));
264
    }
265
996
    let pair_idx = ctx.ids.ty_pair;
266
996
    let acc_local = ctx.alloc_local(WasmType::PairRef(chain_elem))?;
267
996
    emit.ref_null(pair_idx);
268
996
    emit.local_set(acc_local);
269
2241
    for (local, elem_ty) in element_locals.iter().rev() {
270
2241
        emit.local_get(*local);
271
2241
        // Box each car for the CHAIN's element slot: an AnyRef chain needs
272
2241
        // every car widened to anyref (i31 for i32/bool); a homogeneous chain
273
2241
        // boxes per the element's own type.
274
2241
        emit_box_cell(ctx, emit, *elem_ty, chain_elem);
275
2241
        emit.local_get(acc_local);
276
2241
        emit.call(ctx.ids.pair_new);
277
2241
        emit.local_set(acc_local);
278
2241
    }
279
996
    emit.local_get(acc_local);
280
996
    Ok(WasmType::PairRef(chain_elem))
281
996
}
282

            
283
/// A row's value as the chain rule sees it: a closure call yields its
284
/// signature's result (the eval `call` cannot apply a closure local), any
285
/// other callable resolves on a clone.
286
2241
fn row_value(symbols: &SymbolTable, call: &Expr, closure_result: Option<WasmType>) -> Result<Expr> {
287
2241
    match closure_result {
288
1162
        Some(result) => Ok(Expr::WasmRuntime(result)),
289
1079
        None => eval_value(&mut symbols.clone(), call),
290
    }
291
2241
}
292

            
293
2241
fn row_call(fn_arg: &Expr, lists: &[Vec<Expr>], row: usize) -> Expr {
294
2241
    let mut call = vec![Expr::Symbol("FUNCALL".to_string()), fn_arg.clone()];
295
2407
    call.extend(lists.iter().map(|list| as_literal_arg(&list[row])));
296
2241
    Expr::List(call)
297
2241
}
298

            
299
/// Compiles one row's call onto the chain: a literal result takes the chain's
300
/// cell type (its own slot in an `AnyRef` chain); a runtime result must match
301
/// the element its resolved value predicted.
302
2241
fn compile_map_row(
303
2241
    ctx: &mut CompileContext,
304
2241
    emit: &mut FunctionEmitter,
305
2241
    symbols: &mut SymbolTable,
306
2241
    call: &Expr,
307
2241
    value: &Expr,
308
2241
    chain_elem: PairElement,
309
2241
) -> Result<PairElement> {
310
2241
    let cell = match (literal_pair_element(value), chain_elem) {
311
        (Some(slot), PairElement::AnyRef) => Some(slot),
312
332
        (Some(_), chain) => Some(chain),
313
1909
        (None, _) => None,
314
    };
315
2241
    let ty = match cell {
316
332
        Some(elem) => {
317
332
            compile_for_stack_as(ctx, emit, symbols, call, elem.as_wasm_type())?;
318
332
            elem.as_wasm_type()
319
        }
320
1909
        None => compile_for_stack(ctx, emit, symbols, call)?,
321
    };
322
2241
    let elem = PairElement::from_wasm_type(ty).ok_or_else(unpairable_row_error)?;
323
2241
    if chain_elem != PairElement::AnyRef && elem != chain_elem {
324
        return Err(Error::Compile(format!(
325
            "MAP: row result {elem} disagrees with the mapped element {chain_elem}"
326
        )));
327
2241
    }
328
2241
    Ok(elem)
329
2241
}
330

            
331
fn unpairable_row_error() -> Error {
332
    Error::Compile(
333
        "MAP: closure result type can't ride a typed pair; flatten via let-bind first".to_string(),
334
    )
335
}
336

            
337
/// Walks a runtime `PairRef(elem)` input. Builds the result reversed
338
/// (one prepend per car) by calling the function on each car, then
339
/// reverses once at the end so the output preserves input order.
340
996
fn compile_map_runtime_list(
341
996
    ctx: &mut CompileContext,
342
996
    emit: &mut FunctionEmitter,
343
996
    symbols: &mut SymbolTable,
344
996
    fn_arg: &Expr,
345
996
    list_expr: &Expr,
346
996
    elem: PairElement,
347
996
) -> Result<WasmType> {
348
996
    let result_elem = elem;
349
996
    let pair_idx = ctx.ids.ty_pair;
350
996
    let pair_local = ctx.alloc_local(WasmType::PairRef(result_elem))?;
351
996
    let acc_local = ctx.alloc_local(WasmType::PairRef(result_elem))?;
352
996
    let car_local = ctx.alloc_local(elem.as_wasm_type())?;
353

            
354
996
    compile_for_stack(ctx, emit, symbols, list_expr)?;
355
996
    emit.local_set(pair_local);
356

            
357
996
    emit.ref_null(pair_idx);
358
996
    emit.local_set(acc_local);
359

            
360
996
    emit.block_start();
361
996
    emit.loop_start();
362

            
363
996
    emit.local_get(pair_local);
364
996
    emit.ref_is_null();
365
996
    emit.br_if(1);
366

            
367
996
    emit.local_get(pair_local);
368
996
    emit.struct_get(pair_idx, 0);
369
996
    crate::compiler::native::list::emit_pair_car_downcast(ctx, emit, elem);
370
996
    emit.local_set(car_local);
371

            
372
996
    let mapped_ty = compile_map_call_with_local(ctx, emit, symbols, fn_arg, car_local, elem)?;
373
830
    let actual_elem = PairElement::from_wasm_type(mapped_ty).ok_or_else(|| {
374
        Error::Compile(format!(
375
            "MAP: closure result type {mapped_ty} can't ride a typed pair; \
376
             flatten via let-bind first"
377
        ))
378
    })?;
379
830
    if actual_elem != result_elem {
380
        return Err(Error::Compile(format!(
381
            "MAP: closure result element {actual_elem} doesn't match input element {result_elem}; \
382
             heterogeneous mapping isn't supported yet"
383
        )));
384
830
    }
385
830
    emit_box_cell(ctx, emit, actual_elem, result_elem);
386
830
    emit.local_get(acc_local);
387
830
    emit.call(ctx.ids.pair_new);
388
830
    emit.local_set(acc_local);
389

            
390
830
    emit.local_get(pair_local);
391
830
    emit.struct_get(pair_idx, 1);
392
830
    emit.local_set(pair_local);
393

            
394
830
    emit.br(0);
395
830
    emit.block_end();
396
830
    emit.block_end();
397

            
398
    // The accumulator is in reverse order — walk it once more through
399
    // the standard reverse loop so the output preserves input order.
400
830
    let acc_expr = Expr::WasmLocal(acc_local, WasmType::PairRef(result_elem));
401
830
    emit_reverse_loop(ctx, emit, symbols, &acc_expr, result_elem)?;
402
830
    Ok(WasmType::PairRef(result_elem))
403
996
}
404

            
405
/// Emit a per-iteration call of `fn_arg` against the value held in
406
/// `car_local`. A quoted function name (`'g2`) designates that function, so it
407
/// is called by name; any other callable rides FUNCALL.
408
996
fn compile_map_call_with_local(
409
996
    ctx: &mut CompileContext,
410
996
    emit: &mut FunctionEmitter,
411
996
    symbols: &mut SymbolTable,
412
996
    fn_arg: &Expr,
413
996
    car_local: u32,
414
996
    elem: PairElement,
415
996
) -> Result<WasmType> {
416
996
    let car_expr = Expr::WasmLocal(car_local, elem.as_wasm_type());
417
996
    let call = match fn_arg {
418
166
        Expr::Quote(designator) if matches!(designator.as_ref(), Expr::Symbol(_)) => {
419
166
            vec![designator.as_ref().clone(), car_expr]
420
        }
421
830
        _ => vec![
422
830
            Expr::Symbol("FUNCALL".to_string()),
423
830
            fn_arg.clone(),
424
830
            car_expr,
425
        ],
426
    };
427
996
    compile_for_stack(ctx, emit, symbols, &Expr::List(call))
428
996
}