Lines
67.62 %
Functions
16.92 %
Branches
100 %
use anodized::spec;
use super::super::context::CompileContext;
use super::super::emit::FunctionEmitter;
use super::super::expr::{
compile_for_stack_ratio, compile_number, eval_value, format_expr, push_ratio,
serialize_stack_to_output,
};
use crate::ast::{Expr, Fraction, WasmType};
use crate::error::{Error, Result};
use crate::runtime::SymbolTable;
fn try_fold(symbols: &mut SymbolTable, args: &[Expr]) -> Option<Vec<Fraction>> {
args.iter()
.map(|arg| {
eval_value(symbols, arg).ok().and_then(|e| match e {
Expr::Number(n) => Some(n),
_ => None,
})
.collect()
}
fn validate_ratio_args(resolved: &[Expr], name: &str) -> Result<()> {
for r in resolved {
match r {
Expr::Number(_) => {}
_ if r.wasm_type() == Some(WasmType::Ratio) => {}
_ if r.wasm_type() == Some(WasmType::I32) => {
return Err(Error::Compile(format!(
"{name} requires ratio values, not indices"
)));
other => {
"{name} expects number arguments, got {}",
format_expr(other)
Ok(())
#[spec(ensures: [output.as_ref().map_or(true, |v| v.len() == args.len())])]
pub(super) fn resolve_numbers(
symbols: &mut SymbolTable,
args: &[Expr],
name: &str,
) -> Result<Vec<Fraction>> {
let resolved = eval_value(symbols, arg)?;
match resolved {
Expr::Number(n) => Ok(n),
other => Err(Error::Compile(format!(
format_expr(&other)
))),
// --- Eval functions (compile-time evaluation, WasmRuntime-aware) ---
pub(super) fn add(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
let resolved: Vec<Expr> = args
.iter()
.map(|arg| eval_value(symbols, arg))
.collect::<Result<_>>()?;
if let Some(nums) = try_fold_resolved(&resolved) {
let sum = nums
.into_iter()
.fold(Fraction::from_integer(0), |a, b| a + b);
return Ok(Expr::Number(sum));
validate_ratio_args(&resolved, "+")?;
Ok(Expr::WasmRuntime(WasmType::Ratio))
pub(super) fn sub(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
if args.is_empty() {
return Err(Error::Compile("- requires at least 1 argument".to_string()));
let result = if nums.len() == 1 {
-nums[0]
} else {
nums[1..].iter().fold(nums[0], |a, b| a - b)
return Ok(Expr::Number(result));
validate_ratio_args(&resolved, "-")?;
pub(super) fn mul(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
let product = nums
.fold(Fraction::from_integer(1), |a, b| a * b);
return Ok(Expr::Number(product));
validate_ratio_args(&resolved, "*")?;
pub(super) fn div(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
return Err(Error::Compile("/ requires at least 1 argument".to_string()));
if *nums[0].numer() == 0 {
return Err(Error::Compile("division by zero".to_string()));
nums[0].recip()
let mut acc = nums[0];
for &n in &nums[1..] {
if *n.numer() == 0 {
acc /= n;
acc
validate_ratio_args(&resolved, "/")?;
pub(super) fn modulo(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
if args.len() != 2 {
return Err(Error::Arity {
name: "MOD".to_string(),
expected: 2,
actual: args.len(),
});
if *nums[1].numer() == 0 {
return Err(Error::Compile("division by zero in MOD".to_string()));
let quotient = (nums[0] / nums[1]).floor();
let result = nums[0] - nums[1] * quotient;
validate_ratio_args(&resolved, "MOD")?;
fn try_fold_resolved(resolved: &[Expr]) -> Option<Vec<Fraction>> {
resolved
.map(|e| match e {
Expr::Number(n) => Some(*n),
// --- Compile functions (serializing, for top-level output) ---
pub(super) fn compile_add(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
) -> Result<()> {
if let Some(nums) = try_fold(symbols, args) {
compile_number(ctx, emit, *sum.numer(), *sum.denom());
return Ok(());
let ty = compile_add_to_stack(ctx, emit, symbols, args)?;
serialize_stack_to_output(ctx, emit, ty);
pub(super) fn compile_sub(
compile_number(ctx, emit, *result.numer(), *result.denom());
let ty = compile_sub_to_stack(ctx, emit, symbols, args)?;
pub(super) fn compile_mul(
compile_number(ctx, emit, *product.numer(), *product.denom());
let ty = compile_mul_to_stack(ctx, emit, symbols, args)?;
pub(super) fn compile_div(
let ty = compile_div_to_stack(ctx, emit, symbols, args)?;
pub(super) fn compile_mod(
let ty = compile_mod_to_stack(ctx, emit, symbols, args)?;
// --- Stack compile functions (for sub-expressions) ---
pub(super) fn compile_add_to_stack(
) -> Result<WasmType> {
push_ratio(ctx, emit, *sum.numer(), *sum.denom());
return Ok(WasmType::Ratio);
push_ratio(ctx, emit, 0, 1);
compile_for_stack_ratio(ctx, emit, symbols, &args[0])?;
for arg in &args[1..] {
compile_for_stack_ratio(ctx, emit, symbols, arg)?;
emit.call(ctx.func("ratio_add"));
Ok(WasmType::Ratio)
pub(super) fn compile_sub_to_stack(
push_ratio(ctx, emit, *result.numer(), *result.denom());
if args.len() == 1 {
emit.call(ctx.func("ratio_sub"));
pub(super) fn compile_mul_to_stack(
push_ratio(ctx, emit, *product.numer(), *product.denom());
push_ratio(ctx, emit, 1, 1);
emit.call(ctx.func("ratio_mul"));
pub(super) fn compile_div_to_stack(
emit.call(ctx.func("ratio_div"));
pub(super) fn compile_mod_to_stack(
// MOD runtime: a - b * floor(a/b)
// For simplicity, use: a/b -> floor -> *b -> a - result
// But we don't have floor for ratios yet.
// For now, emit the components using ratio arithmetic
compile_for_stack_ratio(ctx, emit, symbols, &args[1])?;
// We need: a - b * floor(a / b)
// This requires a floor function we don't have yet for runtime.
// For Phase 0, MOD with runtime args is not supported.
Err(Error::Compile(
"MOD with runtime arguments is not yet supported".to_string(),
))