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 crate::ast::{Expr, PairElement, WasmType};
7
use crate::compiler::context::CompileContext;
8
use crate::compiler::emit::FunctionEmitter;
9
use crate::compiler::expr::{
10
    compile_expr, compile_for_stack, eval_value, format_expr, serialize_stack_to_output,
11
};
12
use crate::error::{Error, Result};
13
use crate::runtime::SymbolTable;
14

            
15
use super::datum::{compile_folded_to_stack, is_datum_result};
16

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

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

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

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

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

            
191
/// Downcasts an `anyref` (just popped from `$pair.car`) to the
192
/// element-specific wasm type recorded in the `PairRef(elem)` static
193
/// info. Single source of truth for the i31/struct downcast pattern —
194
/// reused by CAR, DOLIST's body, and the eval-mode pair capture.
195
5597
pub(in crate::compiler) fn emit_pair_car_downcast(
196
5597
    ctx: &CompileContext,
197
5597
    emit: &mut FunctionEmitter,
198
5597
    elem: PairElement,
199
5597
) {
200
5597
    match elem {
201
        // I32 and Bool share the i31-boxed car: same downcast, the slot only
202
        // differs in how the extracted value serializes (Number vs Nil/Bool).
203
3352
        PairElement::I32 | PairElement::Bool => {
204
3352
            emit.ref_cast_i31();
205
3352
            emit.i31_get_s();
206
3352
        }
207
952
        PairElement::Ratio => emit.ref_cast(ctx.ids.ty_ratio),
208
        PairElement::Commodity => emit.ref_cast(ctx.ids.ty_commodity),
209
        PairElement::StringRef => emit.ref_cast(ctx.ids.ty_i8_array),
210
817
        PairElement::Entity(kind) => emit.ref_cast(ctx.ids.entity_type(kind)),
211
476
        PairElement::AnyRef => {}
212
    }
213
5597
}
214

            
215
750
pub(super) fn compile_cdr_to_stack(
216
750
    ctx: &mut CompileContext,
217
750
    emit: &mut FunctionEmitter,
218
750
    symbols: &mut SymbolTable,
219
750
    args: &[Expr],
220
750
) -> Result<WasmType> {
221
    // Const-fold first (mirrors `compile_cdr`): a quoted-constant arg folds to
222
    // a quoted tail list, which lowers as a quoted datum rather than tripping
223
    // `compile_for_stack`'s catch-all.
224
750
    let folded = cdr(symbols, args)?;
225
750
    if !folded.is_wasm_runtime() {
226
136
        return compile_folded_to_stack(ctx, emit, symbols, folded);
227
614
    }
228
614
    let arg_ty = compile_for_stack(ctx, emit, symbols, &args[0])?;
229
614
    let elem = match arg_ty {
230
614
        WasmType::PairRef(e) => e,
231
        other => {
232
            return Err(Error::Compile(format!("CDR expects a pair, got {other}")));
233
        }
234
    };
235
614
    emit.struct_get(ctx.ids.ty_pair, 1);
236
614
    Ok(WasmType::PairRef(elem))
237
750
}