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
55451
pub(in crate::compiler::native) fn bool_result(value: bool) -> Expr {
106
55451
    if value { Expr::Bool(true) } else { Expr::Nil }
107
55451
}
108

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

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

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

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

            
135
43527
pub(super) fn validate_cmp_args(resolved: &[Expr], name: &str) -> Result<WasmType> {
136
43527
    let mut seen_ratio = false;
137
43527
    let mut seen_i32 = false;
138
43527
    let mut seen_int_const = false;
139
43527
    let mut seen_commodity = false;
140
86912
    for r in resolved {
141
38344
        match r {
142
38344
            Expr::Number(n) if *n.denom() == 1 => seen_int_const = true,
143
213
            Expr::Number(_) => seen_ratio = true,
144
48568
            _ if r.wasm_type() == Some(WasmType::Ratio) => seen_ratio = true,
145
35358
            _ if r.wasm_type() == Some(WasmType::Commodity) => seen_commodity = true,
146
35358
            _ if r.wasm_type() == Some(WasmType::I32) => seen_i32 = true,
147
284
            other => {
148
284
                return Err(Error::Compile(format!(
149
284
                    "{name} expects numeric arguments, got {}",
150
284
                    format_expr(other)
151
284
                )));
152
            }
153
        }
154
    }
155
43243
    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
43243
    }
162
43243
    if seen_commodity {
163
        return Ok(WasmType::Commodity);
164
43243
    }
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
43243
    if seen_ratio && seen_i32 {
169
71
        return Err(Error::Compile(format!(
170
71
            "{name} cannot compare a runtime index (count) with a scalar; \
171
71
             bridge explicitly with `(index->scalar ...)`"
172
71
        )));
173
43172
    }
174
43172
    if seen_i32 || (seen_int_const && !seen_ratio) {
175
30246
        Ok(WasmType::I32)
176
    } else {
177
12926
        Ok(WasmType::Ratio)
178
    }
179
43527
}
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
29820
pub(super) fn compile_for_stack_i32(
187
29820
    ctx: &mut CompileContext,
188
29820
    emit: &mut FunctionEmitter,
189
29820
    symbols: &mut SymbolTable,
190
29820
    expr: &Expr,
191
29820
) -> Result<()> {
192
29820
    compile_for_stack_as(ctx, emit, symbols, expr, WasmType::I32)
193
29820
}
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
10934
pub(super) fn compile_for_stack_as_ratio(
200
10934
    ctx: &mut CompileContext,
201
10934
    emit: &mut FunctionEmitter,
202
10934
    symbols: &mut SymbolTable,
203
10934
    expr: &Expr,
204
10934
) -> Result<()> {
205
10934
    compile_for_stack_as(ctx, emit, symbols, expr, WasmType::Ratio)
206
10934
}
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
10650
pub(super) fn emit_i32_cmp(emit: &mut FunctionEmitter, name: &str) {
226
10650
    match name {
227
10650
        "=" => emit.i32_eq(),
228
1065
        "<" => emit.i32_lt_s(),
229
        _ => unreachable!("unsupported i32 comparison: {name}"),
230
    }
231
10650
}