Lines
75.14 %
Functions
37.5 %
Branches
100 %
//! `CAR` / `CDR` selectors. Eval path resolves through quote / cons /
//! list shapes; compile path goes through `struct.get $pair` plus the
//! shared `emit_pair_car_downcast` helper so the car comes back at
//! the type the static `PairRef(elem)` recorded.
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 super::datum::{compile_folded_to_stack, is_datum_result};
pub(super) fn car(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
if args.len() != 1 {
return Err(Error::Arity {
name: "CAR".to_string(),
expected: 1,
actual: args.len(),
});
}
let arg = eval_value(symbols, &args[0])?;
if let Some(WasmType::PairRef(elem)) = arg.wasm_type() {
return Ok(Expr::WasmRuntime(elem.as_wasm_type()));
match arg {
Expr::Nil => Ok(Expr::Nil),
Expr::RuntimeValue(crate::runtime::Value::Struct { name, .. }) => Ok(Expr::Symbol(name)),
Expr::List(elems) => {
if elems.is_empty() {
Ok(Expr::Nil)
} else {
Ok(elems[0].clone())
Expr::Cons(car, _) => Ok(*car),
Expr::Quote(inner) => match *inner {
other => Err(Error::Compile(format!(
"CAR expects a list, got {}",
format_expr(&other)
))),
},
pub(super) fn cdr(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
name: "CDR".to_string(),
return Ok(Expr::WasmRuntime(WasmType::PairRef(elem)));
Expr::RuntimeValue(crate::runtime::Value::Struct { fields, .. }) => {
let tail = fields
.into_iter()
.map(|v| match v {
crate::runtime::Value::Nil => Expr::Nil,
crate::runtime::Value::Bool(b) => Expr::Bool(b),
crate::runtime::Value::Number(n) => Expr::Number(n),
crate::runtime::Value::String(s) => Expr::String(s),
crate::runtime::Value::Symbol(s) => Expr::Symbol(s),
other => Expr::RuntimeValue(other),
})
.collect::<Vec<_>>();
Ok(Expr::Quote(Box::new(Expr::List(tail))))
if elems.len() <= 1 {
Ok(Expr::Quote(Box::new(Expr::List(elems[1..].to_vec()))))
Expr::Cons(_, cdr) => Ok(*cdr),
let tail = elems[1..].to_vec();
"CDR expects a list, got {}",
pub(super) fn compile_car(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
symbols: &mut SymbolTable,
args: &[Expr],
) -> Result<()> {
let result = car(symbols, args)?;
if result.is_wasm_runtime() && args.len() == 1 {
if matches!(arg.wasm_type(), Some(WasmType::PairRef(_))) {
let ty = compile_car_to_stack(ctx, emit, symbols, args)?;
serialize_stack_to_output(ctx, emit, ty)?;
return Ok(());
// A const-folded datum result (a bare Symbol / List / Cons from a quoted
// source, or a quoted tail) renders as DATA — same as the stack path — so
// the two compile surfaces agree. Atoms still lower as themselves.
if is_datum_result(&result) {
let ty = compile_folded_to_stack(ctx, emit, symbols, result)?;
return serialize_stack_to_output(ctx, emit, ty);
compile_expr(ctx, emit, symbols, &result)
pub(super) fn compile_cdr(
let result = cdr(symbols, args)?;
if matches!(result.wasm_type(), Some(WasmType::PairRef(_))) {
let ty = compile_cdr_to_stack(ctx, emit, symbols, args)?;
pub(super) fn compile_car_to_stack(
) -> Result<WasmType> {
// Const-fold first (mirrors `compile_car`): `(car '(1 2 3))` folds to the
// element `1`, which lowers directly. Only a genuine runtime pair reaches
// the `struct.get $pair` path. Without this, a quoted-constant arg would
// hit `compile_for_stack`'s catch-all and trap.
let folded = car(symbols, args)?;
if !folded.is_wasm_runtime() {
return compile_folded_to_stack(ctx, emit, symbols, folded);
let arg_ty = compile_for_stack(ctx, emit, symbols, &args[0])?;
let elem = match arg_ty {
WasmType::PairRef(e) => e,
other => {
return Err(Error::Compile(format!("CAR expects a pair, got {other}")));
emit.struct_get(ctx.ids.ty_pair, 0);
emit_pair_car_downcast(ctx, emit, elem);
Ok(elem.as_wasm_type())
/// Downcasts an `anyref` (just popped from `$pair.car`) to the
/// element-specific wasm type recorded in the `PairRef(elem)` static
/// info. Single source of truth for the i31/struct downcast pattern —
/// reused by CAR, DOLIST's body, and the eval-mode pair capture.
pub(in crate::compiler) fn emit_pair_car_downcast(
ctx: &CompileContext,
elem: PairElement,
) {
match elem {
// I32 and Bool share the i31-boxed car: same downcast, the slot only
// differs in how the extracted value serializes (Number vs Nil/Bool).
PairElement::I32 | PairElement::Bool => {
emit.ref_cast_i31();
emit.i31_get_s();
PairElement::Ratio => emit.ref_cast(ctx.ids.ty_ratio),
PairElement::Commodity => emit.ref_cast(ctx.ids.ty_commodity),
PairElement::StringRef => emit.ref_cast(ctx.ids.ty_i8_array),
PairElement::Entity(kind) => emit.ref_cast(ctx.ids.entity_type(kind)),
PairElement::AnyRef => {}
pub(super) fn compile_cdr_to_stack(
// Const-fold first (mirrors `compile_cdr`): a quoted-constant arg folds to
// a quoted tail list, which lowers as a quoted datum rather than tripping
// `compile_for_stack`'s catch-all.
let folded = cdr(symbols, args)?;
return Err(Error::Compile(format!("CDR expects a pair, got {other}")));
emit.struct_get(ctx.ids.ty_pair, 1);
Ok(WasmType::PairRef(elem))