1
//! Shared helpers for the comparison family — the `NATIVES` registry
2
//! const, eval/compile dispatchers, the bool-emit helpers, and the
3
//! type-classification and stack-coercion helpers consumed by both
4
//! `ordering` and `predicates`.
5

            
6
use crate::ast::{Expr, Fraction, WasmType};
7
use crate::compiler::context::CompileContext;
8
use crate::compiler::emit::FunctionEmitter;
9
use crate::compiler::expr::{
10
    compile_bool, compile_for_stack, compile_for_stack_as, compile_nil, eval_value, format_expr,
11
};
12
use crate::error::{Error, Result};
13
use crate::runtime::SymbolTable;
14

            
15
use super::super::NativeSpec;
16

            
17
use super::ordering::{
18
    compile_eq, compile_eq_to_stack, compile_eql, compile_equal, compile_equal_to_stack,
19
    compile_ge, compile_ge_to_stack, compile_gt, compile_gt_to_stack, compile_le,
20
    compile_le_to_stack, compile_lt, compile_lt_to_stack, compile_neq_to_stack, compile_num_neq,
21
    eql, equal, eval_eq, eval_ge, eval_gt, eval_le, eval_lt, num_neq,
22
};
23
use super::predicates::{
24
    compile_not, compile_not_to_stack, compile_null_p, compile_null_p_to_stack, not_fn, null_p,
25
};
26

            
27
pub(in crate::compiler::native) const NATIVES: &[NativeSpec] = &[
28
    NativeSpec {
29
        name: "=",
30
        eval: eval_eq,
31
        stack: Some(compile_eq_to_stack),
32
        effect: Some(compile_eq),
33
    },
34
    NativeSpec {
35
        name: "/=",
36
        eval: num_neq,
37
        stack: Some(compile_neq_to_stack),
38
        effect: Some(compile_num_neq),
39
    },
40
    NativeSpec {
41
        name: "<",
42
        eval: eval_lt,
43
        stack: Some(compile_lt_to_stack),
44
        effect: Some(compile_lt),
45
    },
46
    NativeSpec {
47
        name: ">",
48
        eval: eval_gt,
49
        stack: Some(compile_gt_to_stack),
50
        effect: Some(compile_gt),
51
    },
52
    NativeSpec {
53
        name: "<=",
54
        eval: eval_le,
55
        stack: Some(compile_le_to_stack),
56
        effect: Some(compile_le),
57
    },
58
    NativeSpec {
59
        name: ">=",
60
        eval: eval_ge,
61
        stack: Some(compile_ge_to_stack),
62
        effect: Some(compile_ge),
63
    },
64
    NativeSpec {
65
        name: "EQL",
66
        eval: eql,
67
        stack: Some(compile_equal_to_stack),
68
        effect: Some(compile_eql),
69
    },
70
    NativeSpec {
71
        name: "EQUAL",
72
        eval: equal,
73
        stack: Some(compile_equal_to_stack),
74
        effect: Some(compile_equal),
75
    },
76
    // Scheme-spelled aliases of the CL equality predicates — same
77
    // structural comparison, registered so the documented `EQ?` / `EQUAL?`
78
    // natives are not dead names the compiler rejects at codegen.
79
    NativeSpec {
80
        name: "EQ?",
81
        eval: eql,
82
        stack: Some(compile_equal_to_stack),
83
        effect: Some(compile_eql),
84
    },
85
    NativeSpec {
86
        name: "EQUAL?",
87
        eval: equal,
88
        stack: Some(compile_equal_to_stack),
89
        effect: Some(compile_equal),
90
    },
91
    NativeSpec {
92
        name: "NOT",
93
        eval: not_fn,
94
        stack: Some(compile_not_to_stack),
95
        effect: Some(compile_not),
96
    },
97
    NativeSpec {
98
        name: "NULL?",
99
        eval: null_p,
100
        stack: Some(compile_null_p_to_stack),
101
        effect: Some(compile_null_p),
102
    },
103
];
104

            
105
53108
pub(in crate::compiler::native) fn bool_result(value: bool) -> Expr {
106
53108
    if value { Expr::Bool(true) } else { Expr::Nil }
107
53108
}
108

            
109
1768
pub(super) fn emit_bool(ctx: &mut CompileContext, emit: &mut FunctionEmitter, value: bool) {
110
1768
    if value {
111
1020
        compile_bool(ctx, emit, true);
112
1020
    } else {
113
748
        compile_nil(ctx, emit);
114
748
    }
115
1768
}
116

            
117
21884
pub(super) fn has_runtime(resolved: &[Expr]) -> bool {
118
21884
    resolved.iter().any(crate::ast::Expr::is_wasm_runtime)
119
21884
}
120

            
121
99194
pub(super) fn resolve_all(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Vec<Expr>> {
122
199408
    args.iter().map(|arg| eval_value(symbols, arg)).collect()
123
99194
}
124

            
125
98990
pub(super) fn extract_numbers(resolved: &[Expr]) -> Option<Vec<Fraction>> {
126
98990
    resolved
127
98990
        .iter()
128
160970
        .map(|e| match e {
129
116516
            Expr::Number(n) => Some(*n),
130
44454
            _ => None,
131
160970
        })
132
98990
        .collect()
133
98990
}
134

            
135
41802
pub(super) fn validate_cmp_args(resolved: &[Expr], name: &str) -> Result<WasmType> {
136
41802
    let mut seen_ratio = false;
137
41802
    let mut seen_i32 = false;
138
41802
    let mut seen_int_const = false;
139
41802
    let mut seen_commodity = false;
140
83468
    for r in resolved {
141
36808
        match r {
142
36808
            Expr::Number(n) if *n.denom() == 1 => seen_int_const = true,
143
204
            Expr::Number(_) => seen_ratio = true,
144
46660
            _ if r.wasm_type() == Some(WasmType::Ratio) => seen_ratio = true,
145
34000
            _ if r.wasm_type() == Some(WasmType::Commodity) => seen_commodity = true,
146
34000
            _ if r.wasm_type() == Some(WasmType::I32) => seen_i32 = true,
147
272
            other => {
148
272
                return Err(Error::Compile(format!(
149
272
                    "{name} expects numeric arguments, got {}",
150
272
                    format_expr(other)
151
272
                )));
152
            }
153
        }
154
    }
155
41530
    if seen_commodity && (seen_ratio || seen_i32 || seen_int_const) {
156
        return Err(Error::Compile(format!(
157
            "{name} cannot compare a commodity-bearing value with a pure-rational \
158
             or index value (a bare literal has no currency); compare two \
159
             same-currency money values, or `(convert-commodity ...)` to bridge"
160
        )));
161
41530
    }
162
41530
    if seen_commodity {
163
        return Ok(WasmType::Commodity);
164
41530
    }
165
    // ADR-0028: a RUNTIME index (count) never implicitly promotes to Scalar.
166
    // Mixing it with a Scalar is a compile error (the implicit I32→Ratio bridge
167
    // is removed); cross explicitly with `(index->scalar ...)`.
168
41530
    if seen_ratio && seen_i32 {
169
68
        return Err(Error::Compile(format!(
170
68
            "{name} cannot compare a runtime index (count) with a scalar; \
171
68
             bridge explicitly with `(index->scalar ...)`"
172
68
        )));
173
41462
    }
174
41462
    if seen_i32 || (seen_int_const && !seen_ratio) {
175
29074
        Ok(WasmType::I32)
176
    } else {
177
12388
        Ok(WasmType::Ratio)
178
    }
179
41802
}
180

            
181
/// Push a comparison operand as a raw i32 Index. ADR-0028: a runtime I32 / an
182
/// integer literal is accepted; delegates to the canonical `compile_for_stack_as`
183
/// (clone-probe, non-literal effect preservation, i32-range check) — the single
184
/// source of truth. `validate_cmp_args` has already rejected non-numeric/Bool/Nil
185
/// operands before this is reached.
186
28676
pub(super) fn compile_for_stack_i32(
187
28676
    ctx: &mut CompileContext,
188
28676
    emit: &mut FunctionEmitter,
189
28676
    symbols: &mut SymbolTable,
190
28676
    expr: &Expr,
191
28676
) -> Result<()> {
192
28676
    compile_for_stack_as(ctx, emit, symbols, expr, WasmType::I32)
193
28676
}
194

            
195
/// Push a comparison operand as a Ratio. ADR-0028: a numeric LITERAL coerces
196
/// across the Index↔Scalar boundary, but a RUNTIME index never does (the
197
/// implicit I32→Ratio bridge is removed) — delegates to the canonical
198
/// `compile_for_stack_as`, the single source of truth for that coercion.
199
10480
pub(super) fn compile_for_stack_as_ratio(
200
10480
    ctx: &mut CompileContext,
201
10480
    emit: &mut FunctionEmitter,
202
10480
    symbols: &mut SymbolTable,
203
10480
    expr: &Expr,
204
10480
) -> Result<()> {
205
10480
    compile_for_stack_as(ctx, emit, symbols, expr, WasmType::Ratio)
206
10480
}
207

            
208
/// Push a value onto the WASM stack asserting it is a Commodity.
209
/// Refuses every other type — commodity comparisons must be homogeneous.
210
pub(super) fn compile_for_stack_as_commodity(
211
    ctx: &mut CompileContext,
212
    emit: &mut FunctionEmitter,
213
    symbols: &mut SymbolTable,
214
    expr: &Expr,
215
) -> Result<()> {
216
    let ty = compile_for_stack(ctx, emit, symbols, expr)?;
217
    match ty {
218
        WasmType::Commodity => Ok(()),
219
        _ => Err(Error::Compile(format!(
220
            "commodity comparison requires commodity-bearing operand, got {ty}"
221
        ))),
222
    }
223
}
224

            
225
10238
pub(super) fn emit_i32_cmp(emit: &mut FunctionEmitter, name: &str) {
226
10238
    match name {
227
10238
        "=" => emit.i32_eq(),
228
1020
        "<" => emit.i32_lt_s(),
229
        _ => unreachable!("unsupported i32 comparison: {name}"),
230
    }
231
10238
}