Lines
68.71 %
Functions
22.22 %
Branches
100 %
//! Runtime codegen for the comparison shape. Each per-op
//! `compile_*_to_stack` routes through one of three helpers:
//! `compile_cmp_to_stack` (forward operand order, dispatched by
//! name), `compile_cmp_to_stack_reversed` (`>` ≡ swap-and-`<`),
//! or the per-op explicit form (`<=` / `>=` need an `i32_eqz` over
//! the strict comparator since `ratio_le` / `commodity_le` don't
//! exist).
use crate::ast::{Expr, Fraction, WasmType};
use crate::compiler::context::CompileContext;
use crate::compiler::emit::FunctionEmitter;
use crate::compiler::expr::{compile_for_stack, eval_value};
use crate::error::{Error, Result};
use crate::runtime::SymbolTable;
use super::super::shared::{
compile_for_stack_as_commodity, compile_for_stack_as_ratio, compile_for_stack_i32,
emit_i32_cmp, extract_numbers, resolve_all, validate_cmp_args,
};
pub(in crate::compiler::native::comparison) fn compile_eq_to_stack(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
symbols: &mut SymbolTable,
args: &[Expr],
) -> Result<WasmType> {
compile_cmp_to_stack(ctx, emit, symbols, args, "=", |a, b| a == b)
}
/// `EQL` / `EQUAL` / `EQ?` / `EQUAL?` — generic structural equality. Unlike the
/// numeric `=` (`compile_eq_to_stack`), this also compares strings: a runtime
/// `account-name` against a literal lowers to the shared `string_eq` byte
/// comparison. The result is a `Bool` truth value (Nil/Bool serialization), so
/// it composes inside `and` / `if` and as a value-position result.
pub(in crate::compiler::native::comparison) fn compile_equal_to_stack(
if args.len() != 2 {
return Err(Error::Arity {
name: "EQUAL".to_string(),
expected: 2,
actual: args.len(),
});
let a = eval_value(symbols, &args[0])?;
let b = eval_value(symbols, &args[1])?;
// Both compile-time-known: structural identity folds to a constant.
if !a.is_wasm_runtime() && !b.is_wasm_runtime() {
emit.i32_const(i32::from(a == b));
return Ok(WasmType::Bool);
let (a_str, b_str) = (is_string_typed(&a), is_string_typed(&b));
if a_str != b_str {
// A string and a non-string inhabit distinct strata — never equal.
emit.i32_const(0);
if a_str {
let ta = compile_for_stack(ctx, emit, symbols, &args[0])?;
let tb = compile_for_stack(ctx, emit, symbols, &args[1])?;
if ta != WasmType::StringRef || tb != WasmType::StringRef {
return Err(Error::Compile(format!(
"EQUAL: string comparison requires string operands, got {ta} and {tb}"
)));
emit.call(ctx.ids.string_eq);
compile_eq_to_stack(ctx, emit, symbols, args)
fn is_string_typed(expr: &Expr) -> bool {
matches!(expr, Expr::String(_)) || expr.wasm_type() == Some(WasmType::StringRef)
pub(in crate::compiler::native::comparison) fn compile_neq_to_stack(
if args.is_empty() {
return Err(Error::Compile(
"/= requires at least 1 argument".to_string(),
));
let resolved = resolve_all(symbols, args)?;
if let Some(nums) = extract_numbers(&resolved) {
let result = (0..nums.len()).all(|i| (i + 1..nums.len()).all(|j| nums[i] != nums[j]));
emit.i32_const(i32::from(result));
if args.len() == 2 {
let arg_ty = validate_cmp_args(&resolved, "/=")?;
match arg_ty {
WasmType::Ratio => {
compile_for_stack_as_ratio(ctx, emit, symbols, &args[0])?;
compile_for_stack_as_ratio(ctx, emit, symbols, &args[1])?;
emit.call(ctx.ids.ratio_eq);
emit.i32_eqz();
WasmType::I32 => {
compile_for_stack_i32(ctx, emit, symbols, &args[0])?;
compile_for_stack_i32(ctx, emit, symbols, &args[1])?;
emit.i32_ne();
WasmType::Commodity => {
compile_for_stack_as_commodity(ctx, emit, symbols, &args[0])?;
compile_for_stack_as_commodity(ctx, emit, symbols, &args[1])?;
emit.call(ctx.ids.commodity_eq);
WasmType::Bool
| WasmType::PairRef(_)
| WasmType::StringRef
| WasmType::EntityRef(_)
| WasmType::Closure(_)
| WasmType::AnyRef => {
"cannot compare cons cells with /=".to_string(),
Err(Error::Compile(
"/= with runtime args requires exactly 2 arguments".to_string(),
))
pub(in crate::compiler::native::comparison) fn compile_lt_to_stack(
compile_cmp_to_stack(ctx, emit, symbols, args, "<", |a, b| a < b)
pub(in crate::compiler::native::comparison) fn compile_gt_to_stack(
// a > b ≡ b < a
compile_cmp_to_stack_reversed(ctx, emit, symbols, args, ">", |a, b| a > b)
pub(in crate::compiler::native::comparison) fn compile_le_to_stack(
"<= requires exactly 2 arguments for runtime comparison".to_string(),
emit.i32_const(i32::from(nums[0] <= nums[1]));
let arg_ty = validate_cmp_args(&resolved, "<=")?;
emit.call(ctx.ids.ratio_lt);
Ok(WasmType::Bool)
emit.i32_le_s();
emit.call(ctx.ids.commodity_lt);
| WasmType::AnyRef => Err(Error::Compile(
"cannot compare cons cells with <=".to_string(),
)),
pub(in crate::compiler::native::comparison) fn compile_ge_to_stack(
">= requires exactly 2 arguments for runtime comparison".to_string(),
emit.i32_const(i32::from(nums[0] >= nums[1]));
let arg_ty = validate_cmp_args(&resolved, ">=")?;
emit.i32_ge_s();
"cannot compare cons cells with >=".to_string(),
pub(in crate::compiler::native::comparison) fn compile_cmp_to_stack_by_name(
name: &str,
const_cmp: fn(&Fraction, &Fraction) -> bool,
match name {
"=" => compile_eq_to_stack(ctx, emit, symbols, args),
"<" => compile_lt_to_stack(ctx, emit, symbols, args),
">" => compile_gt_to_stack(ctx, emit, symbols, args),
"<=" => compile_le_to_stack(ctx, emit, symbols, args),
">=" => compile_ge_to_stack(ctx, emit, symbols, args),
_ => compile_cmp_to_stack(ctx, emit, symbols, args, name, const_cmp),
fn compile_cmp_to_stack(
"{name} requires at least 1 argument"
let result = nums.windows(2).all(|w| const_cmp(&w[0], &w[1]));
"{name} with runtime args requires exactly 2 arguments"
let arg_ty = validate_cmp_args(&resolved, name)?;
emit.call(ctx.ids.ratio_cmp(name)?);
emit_i32_cmp(emit, name);
emit.call(ctx.ids.commodity_cmp(name)?);
| WasmType::AnyRef => Err(Error::Compile(format!(
"cannot compare cons cells with {name}"
))),
fn compile_cmp_to_stack_reversed(
// a > b ≡ b < a; commodity_lt traps on mismatched ids.
emit.i32_gt_s();