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_nil, compile_number, compile_string, compile_symbol, compile_t};
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
190334
pub(in crate::compiler) fn compile_expr(
22
190334
    ctx: &mut CompileContext,
23
190334
    emit: &mut FunctionEmitter,
24
190334
    symbols: &mut SymbolTable,
25
190334
    expr: &Expr,
26
190334
) -> Result<()> {
27
135376
    match expr {
28
        Expr::Nil => {
29
1246
            debug!("compiling nil");
30
1246
            compile_nil(ctx, emit);
31
1246
            Ok(())
32
        }
33
        Expr::T => {
34
751
            debug!("compiling t");
35
751
            compile_t(ctx, emit);
36
751
            Ok(())
37
        }
38
5148
        Expr::Number(n) => {
39
5148
            debug!(numer = *n.numer(), denom = *n.denom(), "compiling number");
40
5148
            compile_number(ctx, emit, *n.numer(), *n.denom());
41
5148
            Ok(())
42
        }
43
3072
        Expr::String(s) => {
44
3072
            debug!(len = s.len(), "compiling string");
45
3072
            compile_string(ctx, emit, s)
46
        }
47
7638
        Expr::Symbol(name) => {
48
7638
            debug!(symbol = %name, "resolving symbol");
49
7638
            let resolved = resolve_arg(symbols, expr)?;
50
7139
            compile_expr(ctx, emit, symbols, &resolved)
51
        }
52
83
        Expr::Keyword(name) => {
53
83
            debug!(keyword = %name, "compiling keyword");
54
83
            compile_symbol(ctx, emit, &format!(":{name}"))
55
        }
56
30878
        Expr::Quote(inner) => {
57
30878
            debug!("compiling quote");
58
30878
            compile_quoted_expr(ctx, emit, inner)
59
        }
60
135376
        Expr::List(elems) if elems.is_empty() => {
61
            debug!("compiling empty list as nil");
62
            compile_nil(ctx, emit);
63
            Ok(())
64
        }
65
135376
        Expr::List(elems) => compile_call(ctx, emit, symbols, elems),
66
415
        Expr::Quasiquote(inner) => {
67
415
            let expanded = expand_quasiquote(symbols, inner)?;
68
415
            compile_expr(ctx, emit, symbols, &expanded)
69
        }
70
        Expr::Unquote(_) | Expr::UnquoteSplicing(_) => {
71
166
            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
332
            if let Expr::RuntimeValue(Value::Struct { name, fields }) = expr
85
332
                && 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
332
            }
123
332
            if let Expr::RuntimeValue(val) = expr {
124
332
                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
5229
        Expr::WasmLocal(idx, ty) => {
131
5229
            emit.local_get(*idx);
132
5229
            serialize_stack_to_output(ctx, emit, *ty)
133
        }
134
        _ => Err(Error::Compile(format!("unsupported expression: {expr:?}"))),
135
    }
136
190334
}
137

            
138
31044
pub(in crate::compiler) fn compile_quoted_expr(
139
31044
    ctx: &mut CompileContext,
140
31044
    emit: &mut FunctionEmitter,
141
31044
    inner: &Expr,
142
31044
) -> Result<()> {
143
31044
    match inner {
144
        Expr::Nil => {
145
            compile_nil(ctx, emit);
146
            Ok(())
147
        }
148
        Expr::T => {
149
            compile_t(ctx, emit);
150
            Ok(())
151
        }
152
249
        Expr::Number(n) => {
153
249
            compile_number(ctx, emit, *n.numer(), *n.denom());
154
249
            Ok(())
155
        }
156
166
        Expr::String(s) => compile_string(ctx, emit, s),
157
27226
        Expr::Symbol(s) => compile_symbol(ctx, emit, s),
158
        Expr::Keyword(s) => compile_symbol(ctx, emit, &format!(":{s}")),
159
        Expr::List(_)
160
        | Expr::Cons(_, _)
161
        | Expr::Quasiquote(_)
162
        | Expr::Unquote(_)
163
3403
        | Expr::UnquoteSplicing(_) => compile_string(ctx, emit, &format_expr(inner)),
164
        Expr::RuntimeValue(_) | Expr::WasmRuntime(_) | Expr::WasmLocal(_, _) => Err(
165
            Error::Compile("runtime values cannot be quoted in WASM".to_string()),
166
        ),
167
        _ => Err(Error::Compile(format!(
168
            "unsupported quoted expression: {inner:?}"
169
        ))),
170
    }
171
31044
}
172

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