Lines
68.53 %
Functions
33.33 %
Branches
100 %
//! Top-level `compile_expr` / `compile_quoted_expr` dispatch + the
//! `serialize_stack_to_output` helper they hand off to for runtime
//! values.
use tracing::debug;
use crate::ast::{Expr, WasmType};
use crate::compiler::context::CompileContext;
use crate::compiler::emit::FunctionEmitter;
use crate::compiler::layout::GcLocals;
use crate::error::{Error, Result};
use crate::runtime::{SymbolTable, Value};
use super::atoms::{compile_bool, compile_nil, compile_number, compile_string, compile_symbol};
use super::call::compile_call;
use super::eval::resolve_arg;
use super::format::{format_expr, format_runtime_value};
use super::quasiquote::expand_quasiquote;
use super::{LOCAL_GC_ARR, LOCAL_IDX, LOCAL_TEMP_I32, LOCAL_TEMP_RATIO};
pub(in crate::compiler) fn compile_expr(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
symbols: &mut SymbolTable,
expr: &Expr,
) -> Result<()> {
match expr {
Expr::Nil => {
debug!("compiling nil");
compile_nil(ctx, emit);
Ok(())
}
Expr::Bool(b) => {
debug!(value = b, "compiling bool");
compile_bool(ctx, emit, *b);
Expr::Number(n) => {
debug!(numer = *n.numer(), denom = *n.denom(), "compiling number");
compile_number(ctx, emit, *n.numer(), *n.denom());
Expr::String(s) => {
debug!(len = s.len(), "compiling string");
compile_string(ctx, emit, s)
Expr::Symbol(name) => {
debug!(symbol = %name, "resolving symbol");
let resolved = resolve_arg(symbols, expr)?;
compile_expr(ctx, emit, symbols, &resolved)
Expr::Keyword(name) => {
debug!(keyword = %name, "compiling keyword");
compile_symbol(ctx, emit, &format!(":{name}"))
Expr::Quote(inner) => {
debug!("compiling quote");
compile_quoted_expr(ctx, emit, inner)
Expr::List(elems) if elems.is_empty() => {
debug!("compiling empty list as nil");
Expr::List(elems) => compile_call(ctx, emit, symbols, elems),
Expr::Quasiquote(inner) => {
let expanded = expand_quasiquote(symbols, inner)?;
compile_expr(ctx, emit, symbols, &expanded)
Expr::Unquote(_) | Expr::UnquoteSplicing(_) => {
Err(Error::Compile("unquote outside of quasiquote".to_string()))
Expr::Lambda(params, body) => {
if let Some(sig) = crate::compiler::special::try_emit_lambda_for_value(
ctx, emit, symbols, params, body,
)? {
serialize_stack_to_output(ctx, emit, WasmType::Closure(sig))?;
return Ok(());
let s = format_expr(expr);
compile_string(ctx, emit, &s)
Expr::RuntimeValue(_) => {
if let Expr::RuntimeValue(Value::Struct { name, fields }) = expr
&& name == "TAG"
{
let name_val = fields.first().cloned().unwrap_or(Value::Nil);
let value_val = fields.get(1).cloned().unwrap_or(Value::Nil);
let name_str = match name_val {
Value::String(s) => s,
Value::Symbol(s) => s,
Value::Nil => String::new(),
other => {
return Err(Error::Compile(format!(
"TAG name must be string/symbol, got {}",
other.type_name()
)));
};
let value_str = match value_val {
"TAG value must be string/symbol, got {}",
// A TAG-valued program result is serialized through the SAME
// runtime create-tag path (parent -1, unattached) as `create-tag`
// so every tag entity shares one output-append + string-pool
// convention — no separate static tag writer to drift.
let args = [
Expr::Number(crate::ast::Fraction::from_integer(-1)),
Expr::String(name_str),
Expr::String(value_str),
];
crate::compiler::native::compile_create_tag(ctx, emit, symbols, &args)?;
if let Expr::RuntimeValue(val) = expr {
compile_string(ctx, emit, &format_runtime_value(val))
} else {
unreachable!()
Expr::WasmRuntime(ty) => serialize_stack_to_output(ctx, emit, *ty),
Expr::WasmLocal(idx, ty) => {
emit.local_get(*idx);
serialize_stack_to_output(ctx, emit, *ty)
_ => Err(Error::Compile(format!("unsupported expression: {expr:?}"))),
pub(in crate::compiler) fn compile_quoted_expr(
inner: &Expr,
match inner {
Expr::String(s) => compile_string(ctx, emit, s),
Expr::Symbol(s) => compile_symbol(ctx, emit, s),
Expr::Keyword(s) => compile_symbol(ctx, emit, &format!(":{s}")),
Expr::List(elems) => {
let s = format!(
"({})",
elems.iter().map(format_expr).collect::<Vec<_>>().join(" ")
);
Expr::Cons(car, cdr) => compile_string(
ctx,
emit,
&format!("({} . {})", format_expr(car), format_expr(cdr)),
),
Expr::Quasiquote(e) => compile_string(ctx, emit, &format!("`{}", format_expr(e))),
Expr::Unquote(e) => compile_string(ctx, emit, &format!(",{}", format_expr(e))),
Expr::UnquoteSplicing(e) => compile_string(ctx, emit, &format!(",@{}", format_expr(e))),
Expr::RuntimeValue(_) | Expr::WasmRuntime(_) | Expr::WasmLocal(_, _) => Err(
Error::Compile("runtime values cannot be quoted in WASM".to_string()),
_ => Err(Error::Compile(format!(
"unsupported quoted expression: {inner:?}"
))),
pub(in crate::compiler) fn serialize_stack_to_output(
ty: WasmType,
let ratio_idx = ctx.ids.ty_ratio;
match ty {
WasmType::Ratio => {
ctx.serializer()
.write_debug_number_from_stack(emit, ratio_idx, LOCAL_TEMP_RATIO);
WasmType::I32 => {
.write_debug_i32_from_stack(emit, LOCAL_TEMP_I32);
WasmType::Bool => {
// A runtime truth value: 0 → Nil, nonzero → Bool(true), matching
// the const-fold path (a raw I32 above serializes as Number).
.write_debug_bool_value_from_stack(emit, LOCAL_TEMP_I32);
WasmType::PairRef(_) => {
// Debug-mode serializer for $pair surfaces a non-null marker
// so the script-mode caller can tell list construction
// succeeded. Walking the cell chain for full debug output
// lands in a follow-up slice alongside CAR/CDR refinement.
emit.ref_is_null();
emit.i32_eqz();
.write_debug_bool_from_stack(emit, LOCAL_TEMP_I32);
WasmType::StringRef => {
let gc = GcLocals {
type_idx: ctx.ids.ty_i8_array,
arr: LOCAL_GC_ARR,
idx: LOCAL_IDX,
ctx.serializer().write_debug_string_from_stack(emit, &gc);
WasmType::Commodity => {
// Debug serializer for Commodity not yet wired; drop the
// value off the stack so the consumer doesn't end up with a
// dangling commodity_ref. Real debug output rides 1c.
emit.drop_value();
WasmType::EntityRef(_) => {
// Per-entity debug serializer lands in A5 alongside the
// list/get-X migration to typed pairs. Until then, drop the
// ref so the script-mode caller doesn't stash a dangling
// entity-typed value.
WasmType::Closure(_) => {
let literal_idx = ctx.closure_literal_data_idx()?;
ctx.serializer().write_debug_closure_from_stack(
literal_idx,
crate::compiler::context::CLOSURE_LITERAL_LEN,
&gc,
WasmType::AnyRef => {
// Heterogeneous payload — debug serializer surfaces a non-null
// marker so the script-mode caller can distinguish a bound
// value from `nil`. Element-aware printing waits for typed
// accessor natives (`ok?`, `err-code`) to land.