1
//! Top-level `compile_expr` / `compile_quoted_expr` dispatch + the
2
//! `serialize_stack_to_output` helper they hand off to for runtime
3
//! values.
4

            
5
use tracing::debug;
6

            
7
use crate::ast::{Expr, WasmType};
8
use crate::compiler::context::CompileContext;
9
use crate::compiler::emit::FunctionEmitter;
10
use crate::compiler::layout::GcLocals;
11
use crate::error::{Error, Result};
12
use crate::runtime::{SymbolTable, Value};
13

            
14
use super::atoms::{compile_bool, compile_nil, compile_number, compile_string, compile_symbol};
15
use super::call::compile_call;
16
use super::eval::resolve_arg;
17
use super::format::{format_expr, format_runtime_value};
18
use super::quasiquote::expand_quasiquote;
19
use super::{LOCAL_GC_ARR, LOCAL_IDX, LOCAL_TEMP_I32, LOCAL_TEMP_RATIO};
20

            
21
155735
pub(in crate::compiler) fn compile_expr(
22
155735
    ctx: &mut CompileContext,
23
155735
    emit: &mut FunctionEmitter,
24
155735
    symbols: &mut SymbolTable,
25
155735
    expr: &Expr,
26
155735
) -> Result<()> {
27
111047
    match expr {
28
        Expr::Nil => {
29
613
            debug!("compiling nil");
30
613
            compile_nil(ctx, emit);
31
613
            Ok(())
32
        }
33
548
        Expr::Bool(b) => {
34
548
            debug!(value = b, "compiling bool");
35
548
            compile_bool(ctx, emit, *b);
36
548
            Ok(())
37
        }
38
4150
        Expr::Number(n) => {
39
4150
            debug!(numer = *n.numer(), denom = *n.denom(), "compiling number");
40
4150
            compile_number(ctx, emit, *n.numer(), *n.denom());
41
4150
            Ok(())
42
        }
43
2517
        Expr::String(s) => {
44
2517
            debug!(len = s.len(), "compiling string");
45
2517
            compile_string(ctx, emit, s)
46
        }
47
6258
        Expr::Symbol(name) => {
48
6258
            debug!(symbol = %name, "resolving symbol");
49
6258
            let resolved = resolve_arg(symbols, expr)?;
50
5849
            compile_expr(ctx, emit, symbols, &resolved)
51
        }
52
68
        Expr::Keyword(name) => {
53
68
            debug!(keyword = %name, "compiling keyword");
54
68
            compile_symbol(ctx, emit, &format!(":{name}"))
55
        }
56
25502
        Expr::Quote(inner) => {
57
25502
            debug!("compiling quote");
58
25502
            compile_quoted_expr(ctx, emit, inner)
59
        }
60
111047
        Expr::List(elems) if elems.is_empty() => {
61
136
            debug!("compiling empty list as nil");
62
136
            compile_nil(ctx, emit);
63
136
            Ok(())
64
        }
65
110911
        Expr::List(elems) => compile_call(ctx, emit, symbols, elems),
66
340
        Expr::Quasiquote(inner) => {
67
340
            let expanded = expand_quasiquote(symbols, inner)?;
68
340
            compile_expr(ctx, emit, symbols, &expanded)
69
        }
70
        Expr::Unquote(_) | Expr::UnquoteSplicing(_) => {
71
136
            Err(Error::Compile("unquote outside of quasiquote".to_string()))
72
        }
73
        Expr::Lambda(params, body) => {
74
            if let Some(sig) = crate::compiler::special::try_emit_lambda_for_value(
75
                ctx, emit, symbols, params, body,
76
            )? {
77
                serialize_stack_to_output(ctx, emit, WasmType::Closure(sig))?;
78
                return Ok(());
79
            }
80
            let s = format_expr(expr);
81
            compile_string(ctx, emit, &s)
82
        }
83
        Expr::RuntimeValue(_) => {
84
272
            if let Expr::RuntimeValue(Value::Struct { name, fields }) = expr
85
272
                && name == "TAG"
86
            {
87
                let name_val = fields.first().cloned().unwrap_or(Value::Nil);
88
                let value_val = fields.get(1).cloned().unwrap_or(Value::Nil);
89
                let name_str = match name_val {
90
                    Value::String(s) => s,
91
                    Value::Symbol(s) => s,
92
                    Value::Nil => String::new(),
93
                    other => {
94
                        return Err(Error::Compile(format!(
95
                            "TAG name must be string/symbol, got {}",
96
                            other.type_name()
97
                        )));
98
                    }
99
                };
100
                let value_str = match value_val {
101
                    Value::String(s) => s,
102
                    Value::Symbol(s) => s,
103
                    Value::Nil => String::new(),
104
                    other => {
105
                        return Err(Error::Compile(format!(
106
                            "TAG value must be string/symbol, got {}",
107
                            other.type_name()
108
                        )));
109
                    }
110
                };
111
                // A TAG-valued program result is serialized through the SAME
112
                // runtime create-tag path (parent -1, unattached) as `create-tag`
113
                // so every tag entity shares one output-append + string-pool
114
                // convention — no separate static tag writer to drift.
115
                let args = [
116
                    Expr::Number(crate::ast::Fraction::from_integer(-1)),
117
                    Expr::String(name_str),
118
                    Expr::String(value_str),
119
                ];
120
                crate::compiler::native::compile_create_tag(ctx, emit, symbols, &args)?;
121
                return Ok(());
122
272
            }
123
272
            if let Expr::RuntimeValue(val) = expr {
124
272
                compile_string(ctx, emit, &format_runtime_value(val))
125
            } else {
126
                unreachable!()
127
            }
128
        }
129
        Expr::WasmRuntime(ty) => serialize_stack_to_output(ctx, emit, *ty),
130
4284
        Expr::WasmLocal(idx, ty) => {
131
4284
            emit.local_get(*idx);
132
4284
            serialize_stack_to_output(ctx, emit, *ty)
133
        }
134
        _ => Err(Error::Compile(format!("unsupported expression: {expr:?}"))),
135
    }
136
155735
}
137

            
138
25638
pub(in crate::compiler) fn compile_quoted_expr(
139
25638
    ctx: &mut CompileContext,
140
25638
    emit: &mut FunctionEmitter,
141
25638
    inner: &Expr,
142
25638
) -> Result<()> {
143
25638
    match inner {
144
        Expr::Nil => {
145
68
            compile_nil(ctx, emit);
146
68
            Ok(())
147
        }
148
68
        Expr::Bool(b) => {
149
68
            compile_bool(ctx, emit, *b);
150
68
            Ok(())
151
        }
152
204
        Expr::Number(n) => {
153
204
            compile_number(ctx, emit, *n.numer(), *n.denom());
154
204
            Ok(())
155
        }
156
136
        Expr::String(s) => compile_string(ctx, emit, s),
157
22306
        Expr::Symbol(s) => compile_symbol(ctx, emit, s),
158
        Expr::Keyword(s) => compile_symbol(ctx, emit, &format!(":{s}")),
159
2788
        Expr::List(elems) => {
160
2788
            let s = format!(
161
                "({})",
162
2788
                elems.iter().map(format_expr).collect::<Vec<_>>().join(" ")
163
            );
164
2788
            compile_string(ctx, emit, &s)
165
        }
166
68
        Expr::Cons(car, cdr) => compile_string(
167
68
            ctx,
168
68
            emit,
169
68
            &format!("({} . {})", format_expr(car), format_expr(cdr)),
170
        ),
171
        Expr::Quasiquote(e) => compile_string(ctx, emit, &format!("`{}", format_expr(e))),
172
        Expr::Unquote(e) => compile_string(ctx, emit, &format!(",{}", format_expr(e))),
173
        Expr::UnquoteSplicing(e) => compile_string(ctx, emit, &format!(",@{}", format_expr(e))),
174
        Expr::RuntimeValue(_) | Expr::WasmRuntime(_) | Expr::WasmLocal(_, _) => Err(
175
            Error::Compile("runtime values cannot be quoted in WASM".to_string()),
176
        ),
177
        _ => Err(Error::Compile(format!(
178
            "unsupported quoted expression: {inner:?}"
179
        ))),
180
    }
181
25638
}
182

            
183
32028
pub(in crate::compiler) fn serialize_stack_to_output(
184
32028
    ctx: &mut CompileContext,
185
32028
    emit: &mut FunctionEmitter,
186
32028
    ty: WasmType,
187
32028
) -> Result<()> {
188
32028
    let ratio_idx = ctx.ids.ty_ratio;
189
32028
    match ty {
190
4896
        WasmType::Ratio => {
191
4896
            ctx.serializer()
192
4896
                .write_debug_number_from_stack(emit, ratio_idx, LOCAL_TEMP_RATIO);
193
4896
        }
194
14688
        WasmType::I32 => {
195
14688
            ctx.serializer()
196
14688
                .write_debug_i32_from_stack(emit, LOCAL_TEMP_I32);
197
14688
        }
198
6596
        WasmType::Bool => {
199
6596
            // A runtime truth value: 0 → Nil, nonzero → Bool(true), matching
200
6596
            // the const-fold path (a raw I32 above serializes as Number).
201
6596
            ctx.serializer()
202
6596
                .write_debug_bool_value_from_stack(emit, LOCAL_TEMP_I32);
203
6596
        }
204
2788
        WasmType::PairRef(_) => {
205
2788
            // Debug-mode serializer for $pair surfaces a non-null marker
206
2788
            // so the script-mode caller can tell list construction
207
2788
            // succeeded. Walking the cell chain for full debug output
208
2788
            // lands in a follow-up slice alongside CAR/CDR refinement.
209
2788
            emit.ref_is_null();
210
2788
            emit.i32_eqz();
211
2788
            ctx.serializer()
212
2788
                .write_debug_bool_from_stack(emit, LOCAL_TEMP_I32);
213
2788
        }
214
2244
        WasmType::StringRef => {
215
2244
            let gc = GcLocals {
216
2244
                type_idx: ctx.ids.ty_i8_array,
217
2244
                arr: LOCAL_GC_ARR,
218
2244
                idx: LOCAL_IDX,
219
2244
            };
220
2244
            ctx.serializer().write_debug_string_from_stack(emit, &gc);
221
2244
        }
222
        WasmType::Commodity => {
223
            // Debug serializer for Commodity not yet wired; drop the
224
            // value off the stack so the consumer doesn't end up with a
225
            // dangling commodity_ref. Real debug output rides 1c.
226
            emit.drop_value();
227
        }
228
        WasmType::EntityRef(_) => {
229
            // Per-entity debug serializer lands in A5 alongside the
230
            // list/get-X migration to typed pairs. Until then, drop the
231
            // ref so the script-mode caller doesn't stash a dangling
232
            // entity-typed value.
233
            emit.drop_value();
234
        }
235
        WasmType::Closure(_) => {
236
408
            let literal_idx = ctx.closure_literal_data_idx()?;
237
408
            let gc = GcLocals {
238
408
                type_idx: ctx.ids.ty_i8_array,
239
408
                arr: LOCAL_GC_ARR,
240
408
                idx: LOCAL_IDX,
241
408
            };
242
408
            ctx.serializer().write_debug_closure_from_stack(
243
408
                emit,
244
408
                literal_idx,
245
                crate::compiler::context::CLOSURE_LITERAL_LEN,
246
408
                &gc,
247
            );
248
        }
249
408
        WasmType::AnyRef => {
250
408
            // Heterogeneous payload — debug serializer surfaces a non-null
251
408
            // marker so the script-mode caller can distinguish a bound
252
408
            // value from `nil`. Element-aware printing waits for typed
253
408
            // accessor natives (`ok?`, `err-code`) to land.
254
408
            emit.ref_is_null();
255
408
            emit.i32_eqz();
256
408
            ctx.serializer()
257
408
                .write_debug_bool_from_stack(emit, LOCAL_TEMP_I32);
258
408
        }
259
    }
260
32028
    Ok(())
261
32028
}