use cel::common::ast::{CallExpr, Expr, LiteralValue};
use walrus::{InstrSeqBuilder, ValType};
use crate::compiler::{
access::compile_ident,
context::{CompilerContext, CompilerEnv},
expr::compile_expr,
};
pub fn compile_cel_block(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
if call_expr.args.len() != 2 {
anyhow::bail!(
"cel.block() expects exactly 2 arguments (bindings, body), got {}",
call_expr.args.len()
);
}
let bindings_expr = &call_expr.args[0].expr;
let body_expr = &call_expr.args[1].expr;
let slots = match bindings_expr {
Expr::List(list) => &list.elements,
_ => anyhow::bail!("cel.block() first argument must be a list literal"),
};
let mut current_ctx;
let mut ctx_ref: &CompilerContext = ctx;
let mut owned_ctxs: Vec<CompilerContext> = Vec::with_capacity(slots.len());
for (i, slot_expr) in slots.iter().enumerate() {
compile_expr(&slot_expr.expr, body, env, ctx_ref, module)?;
let local = module.locals.add(ValType::I32);
body.local_set(local);
current_ctx = ctx_ref.with_local(format!("@index{}", i), local);
owned_ctxs.push(current_ctx);
ctx_ref = owned_ctxs.last().unwrap();
}
compile_expr(body_expr, body, env, ctx_ref, module)
}
pub fn compile_cel_index(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
let n = extract_non_negative_int_arg(call_expr, "cel.index", 0)?;
let var_name = format!("@index{}", n);
compile_ident(&var_name, body, env, ctx, module)
}
pub fn compile_cel_iter_var(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
if call_expr.args.len() != 2 {
anyhow::bail!(
"cel.iterVar() expects exactly 2 arguments (N, M), got {}",
call_expr.args.len()
);
}
let n = extract_non_negative_int_arg(call_expr, "cel.iterVar", 0)?;
let m = extract_non_negative_int_arg(call_expr, "cel.iterVar", 1)?;
let var_name = format!("@it:{}:{}", n, m);
compile_ident(&var_name, body, env, ctx, module)
}
pub fn compile_cel_accu_var(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
if call_expr.args.len() != 2 {
anyhow::bail!(
"cel.accuVar() expects exactly 2 arguments (N, M), got {}",
call_expr.args.len()
);
}
let n = extract_non_negative_int_arg(call_expr, "cel.accuVar", 0)?;
let m = extract_non_negative_int_arg(call_expr, "cel.accuVar", 1)?;
let var_name = format!("@ac:{}:{}", n, m);
compile_ident(&var_name, body, env, ctx, module)
}
fn extract_non_negative_int_arg(
call_expr: &CallExpr,
func: &str,
pos: usize,
) -> Result<u64, anyhow::Error> {
let arg = call_expr.args.get(pos).ok_or_else(|| {
anyhow::anyhow!(
"{}() argument {} is missing (got {} args)",
func,
pos,
call_expr.args.len()
)
})?;
match &arg.expr {
Expr::Literal(lit) => match lit {
LiteralValue::Int(n) if *n.inner() >= 0 => Ok(*n.inner() as u64),
LiteralValue::UInt(n) => Ok(*n.inner()),
_ => anyhow::bail!(
"{}() argument {} must be a non-negative integer literal",
func,
pos
),
},
_ => anyhow::bail!("{}() argument {} must be an integer literal", func, pos),
}
}