use cel::common::ast::{CallExpr, operators};
use ferricel_types::functions::RuntimeFunction;
use walrus::{InstrSeqBuilder, ValType};
use crate::compiler::{
context::{CompilerContext, CompilerEnv},
expr::compile_expr,
helpers,
};
pub(crate) fn compile_binary_op(
call_expr: &CallExpr,
op_name: &str,
runtime_fn: RuntimeFunction,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
if call_expr.args.len() != 2 {
anyhow::bail!("{} operator expects 2 arguments", op_name);
}
compile_expr(&call_expr.args[0].expr, body, env, ctx, module)?;
compile_expr(&call_expr.args[1].expr, body, env, ctx, module)?;
body.call(env.get(runtime_fn));
Ok(())
}
pub(crate) fn compile_unary_op(
call_expr: &CallExpr,
op_name: &str,
runtime_fn: RuntimeFunction,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
if call_expr.args.len() != 1 {
anyhow::bail!("{} operator expects 1 argument", op_name);
}
compile_expr(&call_expr.args[0].expr, body, env, ctx, module)?;
body.call(env.get(runtime_fn));
Ok(())
}
fn compile_logical_and(
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!("Logical AND operator expects 2 arguments");
}
compile_expr(&call_expr.args[0].expr, body, env, ctx, module)?;
let left_local = module.locals.add(ValType::I32); body.local_tee(left_local);
body.call(env.get(RuntimeFunction::IsStrictlyFalse));
let then_seq = body.dangling_instr_seq(Some(ValType::I32));
let then_id = then_seq.id();
let else_seq = body.dangling_instr_seq(Some(ValType::I32));
let else_id = else_seq.id();
body.instr(walrus::ir::IfElse {
consequent: then_id,
alternative: else_id,
});
body.instr_seq(then_id).local_get(left_local);
let mut else_body = body.instr_seq(else_id);
compile_expr(&call_expr.args[1].expr, &mut else_body, env, ctx, module)?;
let right_local = module.locals.add(ValType::I32);
else_body.local_set(right_local); else_body.local_get(left_local); else_body.local_get(right_local); else_body.call(env.get(RuntimeFunction::BoolAnd));
Ok(())
}
fn compile_logical_or(
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!("Logical OR operator expects 2 arguments");
}
compile_expr(&call_expr.args[0].expr, body, env, ctx, module)?;
let left_local = module.locals.add(ValType::I32); body.local_tee(left_local);
body.call(env.get(RuntimeFunction::IsStrictlyTrue));
let then_seq = body.dangling_instr_seq(Some(ValType::I32));
let then_id = then_seq.id();
let else_seq = body.dangling_instr_seq(Some(ValType::I32));
let else_id = else_seq.id();
body.instr(walrus::ir::IfElse {
consequent: then_id,
alternative: else_id,
});
body.instr_seq(then_id).local_get(left_local);
let mut else_body = body.instr_seq(else_id);
compile_expr(&call_expr.args[1].expr, &mut else_body, env, ctx, module)?;
let right_local = module.locals.add(ValType::I32);
else_body.local_set(right_local); else_body.local_get(left_local); else_body.local_get(right_local); else_body.call(env.get(RuntimeFunction::BoolOr));
Ok(())
}
fn compile_conditional(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
if call_expr.args.len() != 3 {
anyhow::bail!("Conditional operator expects 3 arguments");
}
compile_expr(&call_expr.args[0].expr, body, env, ctx, module)?;
let cond_local = module.locals.add(ValType::I32);
body.local_tee(cond_local);
body.call(env.get(RuntimeFunction::IsError));
let is_error_seq = body.dangling_instr_seq(Some(ValType::I32));
let is_error_id = is_error_seq.id();
let not_error_seq = body.dangling_instr_seq(Some(ValType::I32));
let not_error_id = not_error_seq.id();
body.instr(walrus::ir::IfElse {
consequent: is_error_id,
alternative: not_error_id,
});
body.instr_seq(is_error_id).local_get(cond_local);
let mut not_error_body = body.instr_seq(not_error_id);
not_error_body.local_get(cond_local);
not_error_body.call(env.get(RuntimeFunction::ValueToBool)); not_error_body.unop(walrus::ir::UnaryOp::I32WrapI64);
let true_branch_seq = not_error_body.dangling_instr_seq(Some(ValType::I32));
let true_branch_id = true_branch_seq.id();
let false_branch_seq = not_error_body.dangling_instr_seq(Some(ValType::I32));
let false_branch_id = false_branch_seq.id();
not_error_body.instr(walrus::ir::IfElse {
consequent: true_branch_id,
alternative: false_branch_id,
});
let mut true_body = not_error_body.instr_seq(true_branch_id);
compile_expr(&call_expr.args[1].expr, &mut true_body, env, ctx, module)?;
let mut false_body = not_error_body.instr_seq(false_branch_id);
compile_expr(&call_expr.args[2].expr, &mut false_body, env, ctx, module)?;
Ok(())
}
fn compile_index(
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!("Index operator _[_] expects 2 arguments (container, index)");
}
compile_expr(&call_expr.args[0].expr, body, env, ctx, module)?;
compile_expr(&call_expr.args[1].expr, body, env, ctx, module)?;
body.call(env.get(RuntimeFunction::ValueIndex));
Ok(())
}
fn compile_opt_index(
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!("Optional index operator _[?_] expects 2 arguments (container, index)");
}
compile_expr(&call_expr.args[0].expr, body, env, ctx, module)?;
compile_expr(&call_expr.args[1].expr, body, env, ctx, module)?;
body.call(env.get(RuntimeFunction::OptionalIndex));
Ok(())
}
fn compile_opt_select(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
use cel::common::ast::Expr as CelExpr;
if call_expr.args.len() != 2 {
anyhow::bail!("Optional select operator _?._ expects 2 arguments");
}
let field_name = match &call_expr.args[1].expr {
CelExpr::Ident(name) => name.clone(),
CelExpr::Literal(cel::common::ast::LiteralValue::String(s)) => s.to_string(),
_ => anyhow::bail!("Optional select _?._ second argument must be a field name identifier"),
};
compile_expr(&call_expr.args[0].expr, body, env, ctx, module)?;
let receiver_local = module.locals.add(ValType::I32);
body.local_set(receiver_local);
let memory_id = helpers::get_memory_id(module)?;
let field_ptr_local = module.locals.add(ValType::I32);
helpers::emit_string_const(&field_name, body, env, memory_id, module);
let field_len_local = module.locals.add(ValType::I32);
body.local_set(field_len_local);
body.local_set(field_ptr_local);
body.local_get(receiver_local);
body.local_get(field_ptr_local);
body.local_get(field_len_local);
body.call(env.get(RuntimeFunction::OptionalSelect));
Ok(())
}
pub fn compile_operator(
func_name: &str,
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<bool, anyhow::Error> {
match func_name {
operators::ADD => compile_binary_op(
call_expr,
"Add",
RuntimeFunction::ValueAdd,
body,
env,
ctx,
module,
)?,
operators::SUBSTRACT => compile_binary_op(
call_expr,
"Subtract",
RuntimeFunction::ValueSub,
body,
env,
ctx,
module,
)?,
operators::MULTIPLY => compile_binary_op(
call_expr,
"Multiply",
RuntimeFunction::ValueMul,
body,
env,
ctx,
module,
)?,
operators::DIVIDE => compile_binary_op(
call_expr,
"Divide",
RuntimeFunction::ValueDiv,
body,
env,
ctx,
module,
)?,
operators::MODULO => compile_binary_op(
call_expr,
"Modulo",
RuntimeFunction::ValueMod,
body,
env,
ctx,
module,
)?,
operators::NEGATE => compile_unary_op(
call_expr,
"Negation",
RuntimeFunction::ValueNegate,
body,
env,
ctx,
module,
)?,
operators::EQUALS => compile_binary_op(
call_expr,
"Equals",
RuntimeFunction::ValueEq,
body,
env,
ctx,
module,
)?,
operators::NOT_EQUALS => compile_binary_op(
call_expr,
"Not equals",
RuntimeFunction::ValueNe,
body,
env,
ctx,
module,
)?,
operators::GREATER => compile_binary_op(
call_expr,
"Greater than",
RuntimeFunction::ValueGt,
body,
env,
ctx,
module,
)?,
operators::LESS => compile_binary_op(
call_expr,
"Less than",
RuntimeFunction::ValueLt,
body,
env,
ctx,
module,
)?,
operators::GREATER_EQUALS => compile_binary_op(
call_expr,
"Greater or equal",
RuntimeFunction::ValueGte,
body,
env,
ctx,
module,
)?,
operators::LESS_EQUALS => compile_binary_op(
call_expr,
"Less or equal",
RuntimeFunction::ValueLte,
body,
env,
ctx,
module,
)?,
operators::IN => compile_binary_op(
call_expr,
"'in'",
RuntimeFunction::ValueIn,
body,
env,
ctx,
module,
)?,
operators::LOGICAL_AND => compile_logical_and(call_expr, body, env, ctx, module)?,
operators::LOGICAL_OR => compile_logical_or(call_expr, body, env, ctx, module)?,
operators::LOGICAL_NOT => compile_unary_op(
call_expr,
"Logical NOT",
RuntimeFunction::BoolNot,
body,
env,
ctx,
module,
)?,
operators::NOT_STRICTLY_FALSE => compile_unary_op(
call_expr,
"@not_strictly_false",
RuntimeFunction::NotStrictlyFalse,
body,
env,
ctx,
module,
)?,
operators::CONDITIONAL => compile_conditional(call_expr, body, env, ctx, module)?,
operators::INDEX => compile_index(call_expr, body, env, ctx, module)?,
operators::OPT_INDEX => compile_opt_index(call_expr, body, env, ctx, module)?,
operators::OPT_SELECT => compile_opt_select(call_expr, body, env, ctx, module)?,
_ => return Ok(false),
}
Ok(true)
}