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
6817
pub(super) fn car(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
18
6817
    if args.len() != 1 {
19
71
        return Err(Error::Arity {
20
71
            name: "CAR".to_string(),
21
71
            expected: 1,
22
71
            actual: args.len(),
23
71
        });
24
6746
    }
25
6746
    let arg = eval_value(symbols, &args[0])?;
26
6746
    if let Some(WasmType::PairRef(elem)) = arg.wasm_type() {
27
3835
        return Ok(Expr::WasmRuntime(elem.as_wasm_type()));
28
2911
    }
29
    match arg {
30
71
        Expr::Nil => Ok(Expr::Nil),
31
        Expr::RuntimeValue(crate::runtime::Value::Struct { name, .. }) => Ok(Expr::Symbol(name)),
32
284
        Expr::List(elems) => {
33
284
            if elems.is_empty() {
34
                Ok(Expr::Nil)
35
            } else {
36
284
                Ok(elems[0].clone())
37
            }
38
        }
39
        Expr::Cons(car, _) => Ok(*car),
40
2485
        Expr::Quote(inner) => match *inner {
41
2485
            Expr::List(elems) => {
42
2485
                if elems.is_empty() {
43
71
                    Ok(Expr::Nil)
44
                } else {
45
2414
                    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
71
        other => Err(Error::Compile(format!(
56
71
            "CAR expects a list, got {}",
57
71
            format_expr(&other)
58
71
        ))),
59
    }
60
6817
}
61

            
62
3621
pub(super) fn cdr(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
63
3621
    if args.len() != 1 {
64
71
        return Err(Error::Arity {
65
71
            name: "CDR".to_string(),
66
71
            expected: 1,
67
71
            actual: args.len(),
68
71
        });
69
3550
    }
70
3550
    let arg = eval_value(symbols, &args[0])?;
71
3550
    if let Some(WasmType::PairRef(elem)) = arg.wasm_type() {
72
1704
        return Ok(Expr::WasmRuntime(WasmType::PairRef(elem)));
73
1846
    }
74
    match arg {
75
71
        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
1704
        Expr::Quote(inner) => match *inner {
99
1704
            Expr::List(elems) => {
100
1704
                if elems.len() <= 1 {
101
426
                    Ok(Expr::Nil)
102
                } else {
103
1278
                    let tail = elems[1..].to_vec();
104
1278
                    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
71
        other => Err(Error::Compile(format!(
115
71
            "CDR expects a list, got {}",
116
71
            format_expr(&other)
117
71
        ))),
118
    }
119
3621
}
120

            
121
1349
pub(super) fn compile_car(
122
1349
    ctx: &mut CompileContext,
123
1349
    emit: &mut FunctionEmitter,
124
1349
    symbols: &mut SymbolTable,
125
1349
    args: &[Expr],
126
1349
) -> Result<()> {
127
1349
    let result = car(symbols, args)?;
128
1349
    if result.is_wasm_runtime() && args.len() == 1 {
129
710
        let arg = eval_value(symbols, &args[0])?;
130
710
        if matches!(arg.wasm_type(), Some(WasmType::PairRef(_))) {
131
710
            let ty = compile_car_to_stack(ctx, emit, symbols, args)?;
132
710
            serialize_stack_to_output(ctx, emit, ty)?;
133
710
            return Ok(());
134
        }
135
639
    }
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
639
    if is_datum_result(&result) {
140
142
        let ty = compile_folded_to_stack(ctx, emit, symbols, result)?;
141
142
        return serialize_stack_to_output(ctx, emit, ty);
142
497
    }
143
497
    compile_expr(ctx, emit, symbols, &result)
144
1349
}
145

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

            
165
2202
pub(super) fn compile_car_to_stack(
166
2202
    ctx: &mut CompileContext,
167
2202
    emit: &mut FunctionEmitter,
168
2202
    symbols: &mut SymbolTable,
169
2202
    args: &[Expr],
170
2202
) -> 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
2202
    let folded = car(symbols, args)?;
176
2202
    if !folded.is_wasm_runtime() {
177
426
        return compile_folded_to_stack(ctx, emit, symbols, folded);
178
1776
    }
179
1776
    let arg_ty = compile_for_stack(ctx, emit, symbols, &args[0])?;
180
1776
    let elem = match arg_ty {
181
1776
        WasmType::PairRef(e) => e,
182
        other => {
183
            return Err(Error::Compile(format!("CAR expects a pair, got {other}")));
184
        }
185
    };
186
1776
    emit.struct_get(ctx.ids.ty_pair, 0);
187
1776
    emit_pair_car_downcast(ctx, emit, elem);
188
1776
    Ok(elem.as_wasm_type())
189
2202
}
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
5894
pub(in crate::compiler) fn emit_pair_car_downcast(
196
5894
    ctx: &CompileContext,
197
5894
    emit: &mut FunctionEmitter,
198
5894
    elem: PairElement,
199
5894
) {
200
5894
    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
3479
        PairElement::I32 | PairElement::Bool => {
204
3479
            emit.ref_cast_i31();
205
3479
            emit.i31_get_s();
206
3479
        }
207
994
        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
924
        PairElement::Entity(kind) => emit.ref_cast(ctx.ids.entity_type(kind)),
211
497
        PairElement::AnyRef => {}
212
    }
213
5894
}
214

            
215
781
pub(super) fn compile_cdr_to_stack(
216
781
    ctx: &mut CompileContext,
217
781
    emit: &mut FunctionEmitter,
218
781
    symbols: &mut SymbolTable,
219
781
    args: &[Expr],
220
781
) -> 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
781
    let folded = cdr(symbols, args)?;
225
781
    if !folded.is_wasm_runtime() {
226
142
        return compile_folded_to_stack(ctx, emit, symbols, folded);
227
639
    }
228
639
    let arg_ty = compile_for_stack(ctx, emit, symbols, &args[0])?;
229
639
    let elem = match arg_ty {
230
639
        WasmType::PairRef(e) => e,
231
        other => {
232
            return Err(Error::Compile(format!("CDR expects a pair, got {other}")));
233
        }
234
    };
235
639
    emit.struct_get(ctx.ids.ty_pair, 1);
236
639
    Ok(WasmType::PairRef(elem))
237
781
}