1
//! `CAR` / `CDR` selectors. Eval path resolves through quote / cons /
2
//! list shapes; compile path goes through `struct.get $pair` plus the
3
//! shared `emit_pair_car_downcast` helper so the car comes back at
4
//! the type the static `PairRef(elem)` recorded.
5

            
6
use wasm_encoder::BlockType;
7

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

            
17
use super::datum::{compile_folded_to_stack, is_datum_result};
18

            
19
11538
pub(super) fn car(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
20
11538
    if args.len() != 1 {
21
83
        return Err(Error::Arity {
22
83
            name: "CAR".to_string(),
23
83
            expected: 1,
24
83
            actual: args.len(),
25
83
        });
26
11455
    }
27
11455
    let arg = eval_value(symbols, &args[0])?;
28
11455
    if let Some(WasmType::PairRef(elem)) = arg.wasm_type() {
29
8052
        return Ok(Expr::WasmRuntime(elem.as_wasm_type()));
30
3403
    }
31
    match arg {
32
166
        Expr::Nil => Ok(Expr::Nil),
33
        Expr::RuntimeValue(crate::runtime::Value::Struct { name, .. }) => Ok(Expr::Symbol(name)),
34
332
        Expr::List(elems) => {
35
332
            if elems.is_empty() {
36
                Ok(Expr::Nil)
37
            } else {
38
332
                Ok(elems[0].clone())
39
            }
40
        }
41
        Expr::Cons(car, _) => Ok(*car),
42
2822
        Expr::Quote(inner) => match *inner {
43
2822
            Expr::List(elems) => {
44
2822
                if elems.is_empty() {
45
                    Ok(Expr::Nil)
46
                } else {
47
2822
                    Ok(elems[0].clone())
48
                }
49
            }
50
            Expr::Cons(car, _) => Ok(*car),
51
            Expr::Nil => Ok(Expr::Nil),
52
            other => Err(Error::Compile(format!(
53
                "CAR expects a list, got {}",
54
                format_expr(&other)
55
            ))),
56
        },
57
83
        other => Err(Error::Compile(format!(
58
83
            "CAR expects a list, got {}",
59
83
            format_expr(&other)
60
83
        ))),
61
    }
62
11538
}
63

            
64
5727
pub(super) fn cdr(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
65
5727
    if args.len() != 1 {
66
83
        return Err(Error::Arity {
67
83
            name: "CDR".to_string(),
68
83
            expected: 1,
69
83
            actual: args.len(),
70
83
        });
71
5644
    }
72
5644
    let arg = eval_value(symbols, &args[0])?;
73
5644
    if let Some(WasmType::PairRef(elem)) = arg.wasm_type() {
74
3486
        return Ok(Expr::WasmRuntime(WasmType::PairRef(elem)));
75
2158
    }
76
    match arg {
77
166
        Expr::Nil => Ok(Expr::Nil),
78
        Expr::RuntimeValue(crate::runtime::Value::Struct { fields, .. }) => {
79
            let tail = fields
80
                .into_iter()
81
                .map(|v| match v {
82
                    crate::runtime::Value::Nil => Expr::Nil,
83
                    crate::runtime::Value::T => Expr::T,
84
                    crate::runtime::Value::Number(n) => Expr::Number(n),
85
                    crate::runtime::Value::String(s) => Expr::String(s),
86
                    crate::runtime::Value::Symbol(s) => Expr::Symbol(s),
87
                    other => Expr::RuntimeValue(other),
88
                })
89
                .collect::<Vec<_>>();
90
            Ok(Expr::quoted_list(tail))
91
        }
92
        Expr::List(elems) => {
93
            if elems.len() <= 1 {
94
                Ok(Expr::Nil)
95
            } else {
96
                Ok(Expr::quoted_list(elems[1..].to_vec()))
97
            }
98
        }
99
        Expr::Cons(_, cdr) => Ok(*cdr),
100
1909
        Expr::Quote(inner) => match *inner {
101
1909
            Expr::List(elems) => {
102
1909
                if elems.len() <= 1 {
103
415
                    Ok(Expr::Nil)
104
                } else {
105
1494
                    let tail = elems[1..].to_vec();
106
1494
                    Ok(Expr::quoted_list(tail))
107
                }
108
            }
109
            Expr::Cons(_, cdr) => Ok(*cdr),
110
            Expr::Nil => Ok(Expr::Nil),
111
            other => Err(Error::Compile(format!(
112
                "CDR expects a list, got {}",
113
                format_expr(&other)
114
            ))),
115
        },
116
83
        other => Err(Error::Compile(format!(
117
83
            "CDR expects a list, got {}",
118
83
            format_expr(&other)
119
83
        ))),
120
    }
121
5727
}
122

            
123
1577
pub(super) fn compile_car(
124
1577
    ctx: &mut CompileContext,
125
1577
    emit: &mut FunctionEmitter,
126
1577
    symbols: &mut SymbolTable,
127
1577
    args: &[Expr],
128
1577
) -> Result<()> {
129
1577
    let result = car(symbols, args)?;
130
1577
    if result.is_wasm_runtime() && args.len() == 1 {
131
830
        let arg = eval_value(symbols, &args[0])?;
132
830
        if matches!(arg.wasm_type(), Some(WasmType::PairRef(_))) {
133
830
            let ty = compile_car_to_stack(ctx, emit, symbols, args)?;
134
830
            serialize_stack_to_output(ctx, emit, ty)?;
135
830
            return Ok(());
136
        }
137
747
    }
138
    // A const-folded datum result (a bare Symbol / List / Cons from a quoted
139
    // source, or a quoted tail) renders as DATA — same as the stack path — so
140
    // the two compile surfaces agree. Atoms still lower as themselves.
141
747
    if is_datum_result(&result) {
142
166
        let ty = compile_folded_to_stack(ctx, emit, symbols, result)?;
143
166
        return serialize_stack_to_output(ctx, emit, ty);
144
581
    }
145
581
    compile_expr(ctx, emit, symbols, &result)
146
1577
}
147

            
148
415
pub(super) fn compile_cdr(
149
415
    ctx: &mut CompileContext,
150
415
    emit: &mut FunctionEmitter,
151
415
    symbols: &mut SymbolTable,
152
415
    args: &[Expr],
153
415
) -> Result<()> {
154
415
    let result = cdr(symbols, args)?;
155
415
    if matches!(result.wasm_type(), Some(WasmType::PairRef(_))) {
156
83
        let ty = compile_cdr_to_stack(ctx, emit, symbols, args)?;
157
83
        serialize_stack_to_output(ctx, emit, ty)?;
158
83
        return Ok(());
159
332
    }
160
332
    if is_datum_result(&result) {
161
332
        let ty = compile_folded_to_stack(ctx, emit, symbols, result)?;
162
332
        return serialize_stack_to_output(ctx, emit, ty);
163
    }
164
    compile_expr(ctx, emit, symbols, &result)
165
415
}
166

            
167
4649
pub(super) fn compile_car_to_stack(
168
4649
    ctx: &mut CompileContext,
169
4649
    emit: &mut FunctionEmitter,
170
4649
    symbols: &mut SymbolTable,
171
4649
    args: &[Expr],
172
4649
) -> Result<WasmType> {
173
    // Const-fold first (mirrors `compile_car`): `(car '(1 2 3))` folds to the
174
    // element `1`, which lowers directly. Only a genuine runtime pair reaches
175
    // the `struct.get $pair` path. Without this, a quoted-constant arg would
176
    // hit `compile_for_stack`'s catch-all and trap.
177
4649
    let folded = car(symbols, args)?;
178
4649
    if !folded.is_wasm_runtime() {
179
498
        return compile_folded_to_stack(ctx, emit, symbols, folded);
180
4151
    }
181
4151
    let arg_ty = compile_for_stack(ctx, emit, symbols, &args[0])?;
182
4151
    let elem = match arg_ty {
183
4151
        WasmType::PairRef(e) => e,
184
        other => {
185
            return Err(Error::Compile(format!("CAR expects a pair, got {other}")));
186
        }
187
    };
188
4151
    let pair_local = ctx.alloc_local(arg_ty)?;
189
4151
    let car_ty = elem.as_wasm_type();
190
4151
    emit.local_tee(pair_local);
191
4151
    emit.ref_is_null();
192
4151
    emit.if_block(BlockType::Result(ctx.wasm_val_type(car_ty)));
193
4151
    emit_nil_car(ctx, emit, elem);
194
4151
    emit.else_block();
195
4151
    emit.local_get(pair_local);
196
4151
    emit.struct_get(ctx.ids.ty_pair, 0);
197
4151
    emit_pair_car_downcast(ctx, emit, elem);
198
4151
    emit.block_end();
199
4151
    Ok(car_ty)
200
4649
}
201

            
202
/// The car of a null pair (`(car nil)`), typed for its element: `nil` is a bool
203
/// zero or a null reference. An index, scalar or money value is never nil, so
204
/// a numeric list throws a catchable `CAR-OF-NIL` rather than trapping.
205
4151
fn emit_nil_car(ctx: &CompileContext, emit: &mut FunctionEmitter, elem: PairElement) {
206
4151
    match elem {
207
1909
        PairElement::I32 | PairElement::Ratio | PairElement::Commodity => {
208
1909
            emit.call(ctx.ids.car_of_nil);
209
1909
            emit.unreachable();
210
1909
        }
211
415
        PairElement::Bool => emit.i32_const(0),
212
1162
        PairElement::AnyRef => emit.ref_null_any(),
213
166
        PairElement::StringRef => emit.ref_null(ctx.ids.ty_i8_array),
214
499
        PairElement::Entity(kind) => emit.ref_null(ctx.ids.entity_type(kind)),
215
    }
216
4151
}
217

            
218
/// Downcasts an `anyref` (just popped from `$pair.car`) to the
219
/// element-specific wasm type recorded in the `PairRef(elem)` static
220
/// info. Single source of truth for the i31/struct downcast pattern —
221
/// reused by CAR, DOLIST's body, and the eval-mode pair capture.
222
10376
pub(in crate::compiler) fn emit_pair_car_downcast(
223
10376
    ctx: &CompileContext,
224
10376
    emit: &mut FunctionEmitter,
225
10376
    elem: PairElement,
226
10376
) {
227
10376
    match elem {
228
        // I32 and Bool share the i31-boxed car: same downcast, the slot only
229
        // differs in how the extracted value serializes (Number vs Nil/Bool).
230
5810
        PairElement::I32 | PairElement::Bool => {
231
5810
            emit.ref_cast_i31();
232
5810
            emit.i31_get_s();
233
5810
        }
234
1577
        PairElement::Ratio => emit.ref_cast(ctx.ids.ty_ratio),
235
83
        PairElement::Commodity => emit.ref_cast(ctx.ids.ty_commodity),
236
166
        PairElement::StringRef => emit.ref_cast(ctx.ids.ty_i8_array),
237
1080
        PairElement::Entity(kind) => emit.ref_cast(ctx.ids.entity_type(kind)),
238
1660
        PairElement::AnyRef => {}
239
    }
240
10376
}
241

            
242
1494
pub(super) fn compile_cdr_to_stack(
243
1494
    ctx: &mut CompileContext,
244
1494
    emit: &mut FunctionEmitter,
245
1494
    symbols: &mut SymbolTable,
246
1494
    args: &[Expr],
247
1494
) -> Result<WasmType> {
248
    // Const-fold first (mirrors `compile_cdr`): a quoted-constant arg folds to
249
    // a quoted tail list, which lowers as a quoted datum rather than tripping
250
    // `compile_for_stack`'s catch-all.
251
1494
    let folded = cdr(symbols, args)?;
252
1494
    if !folded.is_wasm_runtime() {
253
166
        return compile_folded_to_stack(ctx, emit, symbols, folded);
254
1328
    }
255
1328
    let arg_ty = compile_for_stack(ctx, emit, symbols, &args[0])?;
256
1328
    if !matches!(arg_ty, WasmType::PairRef(_)) {
257
        return Err(Error::Compile(format!("CDR expects a pair, got {arg_ty}")));
258
1328
    }
259
    // `(cdr nil)` is nil: a null pair stays null rather than trapping.
260
1328
    let pair_local = ctx.alloc_local(arg_ty)?;
261
1328
    emit.local_tee(pair_local);
262
1328
    emit.ref_is_null();
263
1328
    emit.if_block(BlockType::Result(ctx.wasm_val_type(arg_ty)));
264
1328
    emit.ref_null(ctx.ids.ty_pair);
265
1328
    emit.else_block();
266
1328
    emit.local_get(pair_local);
267
1328
    emit.struct_get(ctx.ids.ty_pair, 1);
268
1328
    emit.block_end();
269
1328
    Ok(arg_ty)
270
1494
}