Lines
100 %
Functions
20 %
Branches
//! List-shaped natives: `LIST`, `CONS`, `CAR`, `CDR`, `MAP`, `FILTER`,
//! `FOLD`, `REVERSE`.
//!
//! Split by form:
//! - [`infer`] — `infer_pair_element` (static element-type inference
//! for CONS) + per-shape unit tests.
//! - [`list_fn`] — `LIST` eval + compile paths.
//! - [`cons`] — `CONS` eval + compile + the car/cdr push helpers.
//! - [`car_cdr`] — `CAR` / `CDR` eval + compile + the shared
//! `emit_pair_car_downcast` helper reused by `DOLIST`.
//! - [`map`] — `MAP` eval + compile + `extract_list_elements`.
//! - [`filter`] — `FILTER` eval + compile, predicate-truthy keeps.
//! - [`fold`] — `FOLD` eval + compile, left fold threading accumulator.
//! - [`reverse`] — `REVERSE` eval + compile, runtime path emits a
//! prepend-loop using `pair_new` as the cell allocator.
mod append;
mod car_cdr;
mod cons;
mod datum;
mod filter;
mod fold;
mod infer;
mod length;
mod list_fn;
mod map;
mod pair_p;
mod reverse;
#[cfg(test)]
mod tests;
use super::NativeSpec;
use crate::ast::{Expr, WasmType};
use crate::compiler::context::CompileContext;
use crate::compiler::expr::eval_value;
use crate::error::Result;
use crate::runtime::SymbolTable;
pub(in crate::compiler) use car_cdr::emit_pair_car_downcast;
/// The function expression FOLD should apply per element. If `fn_arg` resolves
/// to a let-bound closure whose source body was recorded, returns that
/// `(lambda …)` so FOLD INLINES it — binding each parameter to the actual
/// element/accumulator type — instead of `call_ref`ing the closure's fixed
/// (Ratio-default) signature, which mismatches a non-Ratio element list.
/// Otherwise returns `fn_arg` unchanged (an inline lambda already types from its
/// args; a non-recoverable runtime closure keeps `call_ref`).
///
/// FOLD-only on purpose: MAP / FILTER build a fresh result list whose element
/// type the eval surface sizes from the closure SIG, so inlining there (which
/// re-types per the actual element) drifts the eval-vs-codegen element type.
/// They keep the `call_ref` path, relying on `param_infer` to pin non-Ratio
/// params (a non-pinnable non-Ratio closure surfaces a clean type error there).
/// The body was vetted capture-free against its CREATION scope; inlining
/// recompiles it under THIS (fold-site) symbol table, so a global the body uses
/// (`+`, `cons`, …) that an inner binder has since shadowed would resolve to the
/// shadow instead of the creation-site definition the emitted closure used. Re-
/// run the capture-free check against the fold-site table: a now-shadowed global
/// resolves to a `Variable` here and fails it, so we keep `call_ref` (which calls
/// the closure value compiled at creation) — correct, not inlined-wrong.
fn inline_closure_fn(
ctx: &CompileContext,
symbols: &mut SymbolTable,
fn_arg: &Expr,
) -> Result<Expr> {
if let Expr::WasmLocal(idx, WasmType::Closure(_)) = eval_value(symbols, fn_arg)?
&& let Some((params, body)) = ctx.closure_body(idx)
&& crate::compiler::special::is_capture_free(symbols, params, body)
{
return Ok(Expr::Lambda(params.clone(), Box::new(body.clone())));
}
Ok(fn_arg.clone())
pub(in crate::compiler::native) const NATIVES: &[NativeSpec] = &[
NativeSpec {
name: "LIST",
eval: list_fn::list,
stack: Some(list_fn::compile_list_to_stack),
effect: Some(list_fn::compile_list),
},
name: "CONS",
eval: cons::cons,
stack: Some(cons::compile_cons_to_stack),
effect: Some(cons::compile_cons),
name: "CAR",
eval: car_cdr::car,
stack: Some(car_cdr::compile_car_to_stack),
effect: Some(car_cdr::compile_car),
name: "CDR",
eval: car_cdr::cdr,
stack: Some(car_cdr::compile_cdr_to_stack),
effect: Some(car_cdr::compile_cdr),
name: "MAP",
eval: map::map_fn,
stack: Some(map::compile_map_to_stack),
effect: Some(map::compile_map),
name: "FILTER",
eval: filter::filter,
stack: Some(filter::compile_filter_to_stack),
effect: Some(filter::compile_filter),
name: "FOLD",
eval: fold::fold,
stack: Some(fold::compile_fold_to_stack),
effect: Some(fold::compile_fold),
name: "REVERSE",
eval: reverse::reverse,
stack: Some(reverse::compile_reverse_to_stack),
effect: Some(reverse::compile_reverse),
name: "LENGTH",
eval: length::length,
stack: Some(length::compile_length_to_stack),
effect: Some(length::compile_length),
name: "APPEND",
eval: append::append,
stack: Some(append::compile_append_to_stack),
effect: Some(append::compile_append),
name: "PAIR?",
eval: pair_p::pair_p,
stack: Some(pair_p::compile_pair_p_to_stack),
effect: Some(pair_p::compile_pair_p),
];