1
//! Runtime codegen for the comparison shape. Each per-op
2
//! `compile_*_to_stack` routes through one of three helpers:
3
//! `compile_cmp_to_stack` (forward operand order, dispatched by
4
//! name), `compile_cmp_to_stack_reversed` (`>` ≡ swap-and-`<`),
5
//! or the per-op explicit form (`<=` / `>=` need an `i32_eqz` over
6
//! the strict comparator since `ratio_le` / `commodity_le` don't
7
//! exist).
8

            
9
use crate::ast::{Expr, Fraction, WasmType};
10
use crate::compiler::context::CompileContext;
11
use crate::compiler::emit::FunctionEmitter;
12
use crate::compiler::expr::{compile_for_stack, eval_value};
13
use crate::error::{Error, Result};
14
use crate::runtime::SymbolTable;
15

            
16
use super::super::shared::{
17
    compile_for_stack_as_commodity, compile_for_stack_as_ratio, compile_for_stack_i32,
18
    emit_i32_cmp, extract_numbers, resolve_all, validate_cmp_args,
19
};
20

            
21
12482
pub(in crate::compiler::native::comparison) fn compile_eq_to_stack(
22
12482
    ctx: &mut CompileContext,
23
12482
    emit: &mut FunctionEmitter,
24
12482
    symbols: &mut SymbolTable,
25
12482
    args: &[Expr],
26
12482
) -> Result<WasmType> {
27
12482
    compile_cmp_to_stack(ctx, emit, symbols, args, "=", |a, b| a == b)
28
12482
}
29

            
30
/// `EQL` / `EQUAL` / `EQ?` / `EQUAL?` — generic structural equality. Unlike the
31
/// numeric `=` (`compile_eq_to_stack`), this also compares strings: a runtime
32
/// `account-name` against a literal lowers to the shared `string_eq` byte
33
/// comparison. The result is a `Bool` truth value (Nil/Bool serialization), so
34
/// it composes inside `and` / `if` and as a value-position result.
35
2584
pub(in crate::compiler::native::comparison) fn compile_equal_to_stack(
36
2584
    ctx: &mut CompileContext,
37
2584
    emit: &mut FunctionEmitter,
38
2584
    symbols: &mut SymbolTable,
39
2584
    args: &[Expr],
40
2584
) -> Result<WasmType> {
41
2584
    if args.len() != 2 {
42
204
        return Err(Error::Arity {
43
204
            name: "EQUAL".to_string(),
44
204
            expected: 2,
45
204
            actual: args.len(),
46
204
        });
47
2380
    }
48
2380
    let a = eval_value(symbols, &args[0])?;
49
2380
    let b = eval_value(symbols, &args[1])?;
50

            
51
    // Both compile-time-known: structural identity folds to a constant.
52
2380
    if !a.is_wasm_runtime() && !b.is_wasm_runtime() {
53
1632
        emit.i32_const(i32::from(a == b));
54
1632
        return Ok(WasmType::Bool);
55
748
    }
56

            
57
748
    let (a_str, b_str) = (is_string_typed(&a), is_string_typed(&b));
58
748
    if a_str != b_str {
59
        // A string and a non-string inhabit distinct strata — never equal.
60
        emit.i32_const(0);
61
        return Ok(WasmType::Bool);
62
748
    }
63
748
    if a_str {
64
544
        let ta = compile_for_stack(ctx, emit, symbols, &args[0])?;
65
544
        let tb = compile_for_stack(ctx, emit, symbols, &args[1])?;
66
544
        if ta != WasmType::StringRef || tb != WasmType::StringRef {
67
            return Err(Error::Compile(format!(
68
                "EQUAL: string comparison requires string operands, got {ta} and {tb}"
69
            )));
70
544
        }
71
544
        emit.call(ctx.ids.string_eq);
72
544
        return Ok(WasmType::Bool);
73
204
    }
74

            
75
204
    compile_eq_to_stack(ctx, emit, symbols, args)
76
2584
}
77

            
78
1496
fn is_string_typed(expr: &Expr) -> bool {
79
1496
    matches!(expr, Expr::String(_)) || expr.wasm_type() == Some(WasmType::StringRef)
80
1496
}
81

            
82
68
pub(in crate::compiler::native::comparison) fn compile_neq_to_stack(
83
68
    ctx: &mut CompileContext,
84
68
    emit: &mut FunctionEmitter,
85
68
    symbols: &mut SymbolTable,
86
68
    args: &[Expr],
87
68
) -> Result<WasmType> {
88
68
    if args.is_empty() {
89
        return Err(Error::Compile(
90
            "/= requires at least 1 argument".to_string(),
91
        ));
92
68
    }
93
68
    let resolved = resolve_all(symbols, args)?;
94
68
    if let Some(nums) = extract_numbers(&resolved) {
95
        let result = (0..nums.len()).all(|i| (i + 1..nums.len()).all(|j| nums[i] != nums[j]));
96
        emit.i32_const(i32::from(result));
97
        return Ok(WasmType::Bool);
98
68
    }
99
68
    if args.len() == 2 {
100
68
        let arg_ty = validate_cmp_args(&resolved, "/=")?;
101
68
        match arg_ty {
102
            WasmType::Ratio => {
103
68
                compile_for_stack_as_ratio(ctx, emit, symbols, &args[0])?;
104
68
                compile_for_stack_as_ratio(ctx, emit, symbols, &args[1])?;
105
68
                emit.call(ctx.ids.ratio_eq);
106
68
                emit.i32_eqz();
107
68
                return Ok(WasmType::Bool);
108
            }
109
            WasmType::I32 => {
110
                compile_for_stack_i32(ctx, emit, symbols, &args[0])?;
111
                compile_for_stack_i32(ctx, emit, symbols, &args[1])?;
112
                emit.i32_ne();
113
                return Ok(WasmType::Bool);
114
            }
115
            WasmType::Commodity => {
116
                compile_for_stack_as_commodity(ctx, emit, symbols, &args[0])?;
117
                compile_for_stack_as_commodity(ctx, emit, symbols, &args[1])?;
118
                emit.call(ctx.ids.commodity_eq);
119
                emit.i32_eqz();
120
                return Ok(WasmType::Bool);
121
            }
122
            WasmType::Bool
123
            | WasmType::PairRef(_)
124
            | WasmType::StringRef
125
            | WasmType::EntityRef(_)
126
            | WasmType::Closure(_)
127
            | WasmType::AnyRef => {
128
                return Err(Error::Compile(
129
                    "cannot compare cons cells with /=".to_string(),
130
                ));
131
            }
132
        }
133
    }
134
    Err(Error::Compile(
135
        "/= with runtime args requires exactly 2 arguments".to_string(),
136
    ))
137
68
}
138

            
139
2246
pub(in crate::compiler::native::comparison) fn compile_lt_to_stack(
140
2246
    ctx: &mut CompileContext,
141
2246
    emit: &mut FunctionEmitter,
142
2246
    symbols: &mut SymbolTable,
143
2246
    args: &[Expr],
144
2246
) -> Result<WasmType> {
145
2246
    compile_cmp_to_stack(ctx, emit, symbols, args, "<", |a, b| a < b)
146
2246
}
147

            
148
1366
pub(in crate::compiler::native::comparison) fn compile_gt_to_stack(
149
1366
    ctx: &mut CompileContext,
150
1366
    emit: &mut FunctionEmitter,
151
1366
    symbols: &mut SymbolTable,
152
1366
    args: &[Expr],
153
1366
) -> Result<WasmType> {
154
    // a > b ≡ b < a
155
1366
    compile_cmp_to_stack_reversed(ctx, emit, symbols, args, ">", |a, b| a > b)
156
1366
}
157

            
158
544
pub(in crate::compiler::native::comparison) fn compile_le_to_stack(
159
544
    ctx: &mut CompileContext,
160
544
    emit: &mut FunctionEmitter,
161
544
    symbols: &mut SymbolTable,
162
544
    args: &[Expr],
163
544
) -> Result<WasmType> {
164
544
    if args.len() != 2 {
165
        return Err(Error::Compile(
166
            "<= requires exactly 2 arguments for runtime comparison".to_string(),
167
        ));
168
544
    }
169
544
    let resolved = resolve_all(symbols, args)?;
170
544
    if let Some(nums) = extract_numbers(&resolved) {
171
        emit.i32_const(i32::from(nums[0] <= nums[1]));
172
        return Ok(WasmType::Bool);
173
544
    }
174
544
    let arg_ty = validate_cmp_args(&resolved, "<=")?;
175
544
    match arg_ty {
176
        WasmType::Ratio => {
177
544
            compile_for_stack_as_ratio(ctx, emit, symbols, &args[1])?;
178
544
            compile_for_stack_as_ratio(ctx, emit, symbols, &args[0])?;
179
544
            emit.call(ctx.ids.ratio_lt);
180
544
            emit.i32_eqz();
181
544
            Ok(WasmType::Bool)
182
        }
183
        WasmType::I32 => {
184
            compile_for_stack_i32(ctx, emit, symbols, &args[0])?;
185
            compile_for_stack_i32(ctx, emit, symbols, &args[1])?;
186
            emit.i32_le_s();
187
            Ok(WasmType::Bool)
188
        }
189
        WasmType::Commodity => {
190
            compile_for_stack_as_commodity(ctx, emit, symbols, &args[1])?;
191
            compile_for_stack_as_commodity(ctx, emit, symbols, &args[0])?;
192
            emit.call(ctx.ids.commodity_lt);
193
            emit.i32_eqz();
194
            Ok(WasmType::Bool)
195
        }
196
        WasmType::Bool
197
        | WasmType::PairRef(_)
198
        | WasmType::StringRef
199
        | WasmType::EntityRef(_)
200
        | WasmType::Closure(_)
201
        | WasmType::AnyRef => Err(Error::Compile(
202
            "cannot compare cons cells with <=".to_string(),
203
        )),
204
    }
205
544
}
206

            
207
3484
pub(in crate::compiler::native::comparison) fn compile_ge_to_stack(
208
3484
    ctx: &mut CompileContext,
209
3484
    emit: &mut FunctionEmitter,
210
3484
    symbols: &mut SymbolTable,
211
3484
    args: &[Expr],
212
3484
) -> Result<WasmType> {
213
3484
    if args.len() != 2 {
214
        return Err(Error::Compile(
215
            ">= requires exactly 2 arguments for runtime comparison".to_string(),
216
        ));
217
3484
    }
218
3484
    let resolved = resolve_all(symbols, args)?;
219
3484
    if let Some(nums) = extract_numbers(&resolved) {
220
        emit.i32_const(i32::from(nums[0] >= nums[1]));
221
        return Ok(WasmType::Bool);
222
3484
    }
223
3484
    let arg_ty = validate_cmp_args(&resolved, ">=")?;
224
3484
    match arg_ty {
225
        WasmType::Ratio => {
226
136
            compile_for_stack_as_ratio(ctx, emit, symbols, &args[0])?;
227
136
            compile_for_stack_as_ratio(ctx, emit, symbols, &args[1])?;
228
136
            emit.call(ctx.ids.ratio_lt);
229
136
            emit.i32_eqz();
230
136
            Ok(WasmType::Bool)
231
        }
232
        WasmType::I32 => {
233
3348
            compile_for_stack_i32(ctx, emit, symbols, &args[0])?;
234
3348
            compile_for_stack_i32(ctx, emit, symbols, &args[1])?;
235
3348
            emit.i32_ge_s();
236
3348
            Ok(WasmType::Bool)
237
        }
238
        WasmType::Commodity => {
239
            compile_for_stack_as_commodity(ctx, emit, symbols, &args[0])?;
240
            compile_for_stack_as_commodity(ctx, emit, symbols, &args[1])?;
241
            emit.call(ctx.ids.commodity_lt);
242
            emit.i32_eqz();
243
            Ok(WasmType::Bool)
244
        }
245
        WasmType::Bool
246
        | WasmType::PairRef(_)
247
        | WasmType::StringRef
248
        | WasmType::EntityRef(_)
249
        | WasmType::Closure(_)
250
        | WasmType::AnyRef => Err(Error::Compile(
251
            "cannot compare cons cells with >=".to_string(),
252
        )),
253
    }
254
3484
}
255

            
256
2584
pub(in crate::compiler::native::comparison) fn compile_cmp_to_stack_by_name(
257
2584
    ctx: &mut CompileContext,
258
2584
    emit: &mut FunctionEmitter,
259
2584
    symbols: &mut SymbolTable,
260
2584
    args: &[Expr],
261
2584
    name: &str,
262
2584
    const_cmp: fn(&Fraction, &Fraction) -> bool,
263
2584
) -> Result<WasmType> {
264
2584
    match name {
265
2584
        "=" => compile_eq_to_stack(ctx, emit, symbols, args),
266
748
        "<" => compile_lt_to_stack(ctx, emit, symbols, args),
267
340
        ">" => compile_gt_to_stack(ctx, emit, symbols, args),
268
272
        "<=" => compile_le_to_stack(ctx, emit, symbols, args),
269
136
        ">=" => compile_ge_to_stack(ctx, emit, symbols, args),
270
        _ => compile_cmp_to_stack(ctx, emit, symbols, args, name, const_cmp),
271
    }
272
2584
}
273

            
274
14728
fn compile_cmp_to_stack(
275
14728
    ctx: &mut CompileContext,
276
14728
    emit: &mut FunctionEmitter,
277
14728
    symbols: &mut SymbolTable,
278
14728
    args: &[Expr],
279
14728
    name: &str,
280
14728
    const_cmp: fn(&Fraction, &Fraction) -> bool,
281
14728
) -> Result<WasmType> {
282
14728
    if args.is_empty() {
283
        return Err(Error::Compile(format!(
284
            "{name} requires at least 1 argument"
285
        )));
286
14728
    }
287
14728
    let resolved = resolve_all(symbols, args)?;
288
14728
    if let Some(nums) = extract_numbers(&resolved) {
289
272
        let result = nums.windows(2).all(|w| const_cmp(&w[0], &w[1]));
290
272
        emit.i32_const(i32::from(result));
291
272
        return Ok(WasmType::Bool);
292
14456
    }
293
14456
    if args.len() != 2 {
294
        return Err(Error::Compile(format!(
295
            "{name} with runtime args requires exactly 2 arguments"
296
        )));
297
14456
    }
298
14456
    let arg_ty = validate_cmp_args(&resolved, name)?;
299
14116
    match arg_ty {
300
        WasmType::Ratio => {
301
3878
            compile_for_stack_as_ratio(ctx, emit, symbols, &args[0])?;
302
3878
            compile_for_stack_as_ratio(ctx, emit, symbols, &args[1])?;
303
3878
            emit.call(ctx.ids.ratio_cmp(name)?);
304
3878
            Ok(WasmType::Bool)
305
        }
306
        WasmType::I32 => {
307
10238
            compile_for_stack_i32(ctx, emit, symbols, &args[0])?;
308
10238
            compile_for_stack_i32(ctx, emit, symbols, &args[1])?;
309
10238
            emit_i32_cmp(emit, name);
310
10238
            Ok(WasmType::Bool)
311
        }
312
        WasmType::Commodity => {
313
            compile_for_stack_as_commodity(ctx, emit, symbols, &args[0])?;
314
            compile_for_stack_as_commodity(ctx, emit, symbols, &args[1])?;
315
            emit.call(ctx.ids.commodity_cmp(name)?);
316
            Ok(WasmType::Bool)
317
        }
318
        WasmType::Bool
319
        | WasmType::PairRef(_)
320
        | WasmType::StringRef
321
        | WasmType::EntityRef(_)
322
        | WasmType::Closure(_)
323
        | WasmType::AnyRef => Err(Error::Compile(format!(
324
            "cannot compare cons cells with {name}"
325
        ))),
326
    }
327
14728
}
328

            
329
1366
fn compile_cmp_to_stack_reversed(
330
1366
    ctx: &mut CompileContext,
331
1366
    emit: &mut FunctionEmitter,
332
1366
    symbols: &mut SymbolTable,
333
1366
    args: &[Expr],
334
1366
    name: &str,
335
1366
    const_cmp: fn(&Fraction, &Fraction) -> bool,
336
1366
) -> Result<WasmType> {
337
1366
    if args.is_empty() {
338
        return Err(Error::Compile(format!(
339
            "{name} requires at least 1 argument"
340
        )));
341
1366
    }
342
1366
    let resolved = resolve_all(symbols, args)?;
343
1366
    if let Some(nums) = extract_numbers(&resolved) {
344
        let result = nums.windows(2).all(|w| const_cmp(&w[0], &w[1]));
345
        emit.i32_const(i32::from(result));
346
        return Ok(WasmType::Bool);
347
1366
    }
348
1366
    if args.len() != 2 {
349
        return Err(Error::Compile(format!(
350
            "{name} with runtime args requires exactly 2 arguments"
351
        )));
352
1366
    }
353
1366
    let arg_ty = validate_cmp_args(&resolved, name)?;
354
1366
    match arg_ty {
355
        WasmType::Commodity => {
356
            // a > b ≡ b < a; commodity_lt traps on mismatched ids.
357
            compile_for_stack_as_commodity(ctx, emit, symbols, &args[1])?;
358
            compile_for_stack_as_commodity(ctx, emit, symbols, &args[0])?;
359
            emit.call(ctx.ids.commodity_lt);
360
            Ok(WasmType::Bool)
361
        }
362
        WasmType::Bool
363
        | WasmType::PairRef(_)
364
        | WasmType::StringRef
365
        | WasmType::EntityRef(_)
366
        | WasmType::Closure(_)
367
        | WasmType::AnyRef => Err(Error::Compile(format!(
368
            "cannot compare cons cells with {name}"
369
        ))),
370
        WasmType::Ratio => {
371
            // a > b ≡ b < a
372
614
            compile_for_stack_as_ratio(ctx, emit, symbols, &args[1])?;
373
614
            compile_for_stack_as_ratio(ctx, emit, symbols, &args[0])?;
374
614
            emit.call(ctx.ids.ratio_lt);
375
614
            Ok(WasmType::Bool)
376
        }
377
        WasmType::I32 => {
378
752
            compile_for_stack_i32(ctx, emit, symbols, &args[0])?;
379
752
            compile_for_stack_i32(ctx, emit, symbols, &args[1])?;
380
752
            emit.i32_gt_s();
381
752
            Ok(WasmType::Bool)
382
        }
383
    }
384
1366
}