Lines
89.78 %
Functions
27.27 %
Branches
100 %
//! `APPEND` — concatenate lists left to right. Constant-folds when every
//! argument is a compile-time list; otherwise lowers the two-list runtime
//! case by reversing the prefix onto an accumulator seeded with the
//! suffix chain (so the suffix is shared, not copied).
use crate::ast::{Expr, PairElement, WasmType};
use crate::compiler::context::CompileContext;
use crate::compiler::emit::FunctionEmitter;
use crate::compiler::expr::{
compile_expr, compile_for_stack, eval_value, format_expr, serialize_stack_to_output,
};
use crate::error::{Error, Result};
use crate::runtime::SymbolTable;
use crate::compiler::expr::emit_relabel_chain;
use super::datum::{compile_folded_to_stack, is_datum_result};
use super::infer::chain_pair_element;
use super::map::extract_list_elements;
pub(super) fn append(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
if args.iter().any(|a| {
eval_value(symbols, a)
.map(|r| matches!(r.wasm_type(), Some(WasmType::PairRef(_))))
.unwrap_or(false)
}) {
return Ok(Expr::WasmRuntime(WasmType::PairRef(result_element(
symbols, args,
)?)));
}
let mut out = Vec::new();
for arg in args {
let resolved = eval_value(symbols, arg)?;
out.extend(extract_list_elements(&resolved).map_err(|_| {
Error::Compile(format!(
"APPEND expects lists, got {}",
format_expr(&resolved)
))
})?);
Ok(Expr::quoted_list(out))
/// The element type of a runtime APPEND result: [`chain_pair_element`] over
/// every constant list's members plus one placeholder per runtime `PairRef`
/// chain, so a constant list is typed exactly as LIST types its cells. An
/// empty constant list / nil doesn't constrain the result.
fn result_element(symbols: &mut SymbolTable, args: &[Expr]) -> Result<PairElement> {
let mut members = Vec::new();
match resolved.wasm_type() {
Some(WasmType::PairRef(e)) => members.push(Expr::WasmRuntime(e.as_wasm_type())),
_ => members.extend(constant_list_members(&resolved)?),
chain_pair_element(&members)
fn constant_list_members(resolved: &Expr) -> Result<Vec<Expr>> {
extract_list_elements(resolved).map_err(|_| {
format_expr(resolved)
})
pub(super) fn compile_append(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
symbols: &mut SymbolTable,
args: &[Expr],
) -> Result<()> {
let folded = append(symbols, args)?;
if folded.is_wasm_runtime() {
let ty = compile_append_to_stack(ctx, emit, symbols, args)?;
return serialize_stack_to_output(ctx, emit, ty);
compile_expr(ctx, emit, symbols, &folded)
pub(super) fn compile_append_to_stack(
) -> Result<WasmType> {
// All-constant APPEND folds to a single quoted list — render it as a datum
// (matching CAR/CDR/REVERSE/CONS and the effect path) instead of forcing it
// through the runtime materialization below, which can't represent symbols.
if !folded.is_wasm_runtime() && is_datum_result(&folded) {
return compile_folded_to_stack(ctx, emit, symbols, folded);
if args.len() != 2 {
return Err(Error::Compile(
"APPEND with a runtime list argument requires exactly 2 lists".to_string(),
));
let elem = result_element(symbols, args)?;
let pair_idx = ctx.ids.ty_pair;
let prefix_local = ctx.alloc_local(WasmType::PairRef(elem))?;
let acc_local = ctx.alloc_local(WasmType::PairRef(elem))?;
// acc ← suffix (shared tail); then prepend the prefix in reverse so
// the final chain is prefix ++ suffix in original order.
push_list_arg(ctx, emit, symbols, &args[1], elem)?;
emit.local_set(acc_local);
push_list_arg(ctx, emit, symbols, &args[0], elem)?;
emit.local_set(prefix_local);
let reversed_local = ctx.alloc_local(WasmType::PairRef(elem))?;
reverse_into(ctx, emit, pair_idx, prefix_local, reversed_local);
// Walk the reversed prefix, prepending each car onto acc (= suffix).
emit.block_start();
emit.loop_start();
emit.local_get(reversed_local);
emit.ref_is_null();
emit.br_if(1);
emit.struct_get(pair_idx, 0);
emit.local_get(acc_local);
emit.call(ctx.ids.pair_new);
emit.struct_get(pair_idx, 1);
emit.local_set(reversed_local);
emit.br(0);
emit.block_end();
Ok(WasmType::PairRef(elem))
/// Push an APPEND argument as a runtime `$pair` chain on the stack. A runtime
/// `PairRef` arg lowers directly; a constant list is materialized into a fresh
/// `$pair` chain whose cells ride the result element `elem` (so a concrete
/// result label never covers mismatched cells); nil / empty list becomes a
/// null pair.
fn push_list_arg(
arg: &Expr,
elem: PairElement,
if let Some(WasmType::PairRef(arg_elem)) = resolved.wasm_type() {
compile_for_stack(ctx, emit, symbols, arg)?;
emit_relabel_chain(ctx, emit, arg_elem, elem);
return Ok(());
let elements = constant_list_members(&resolved)?;
super::cons::emit_constant_chain(ctx, emit, symbols, elements, elem)
/// Reverses the chain in `src_local` into `dst_local` (a fresh null-seeded
/// accumulator), consuming `src_local`. Used so APPEND can prepend the
/// prefix onto the shared suffix in original order.
fn reverse_into(
pair_idx: u32,
src_local: u32,
dst_local: u32,
) {
emit.ref_null(pair_idx);
emit.local_set(dst_local);
emit.local_get(src_local);
emit.local_get(dst_local);
emit.local_set(src_local);