use cel::common::ast::{CallExpr, Expr};
use ferricel_types::functions::RuntimeFunction;
use walrus::{InstrSeqBuilder, ValType};
use crate::compiler::{
context::{CompilerContext, CompilerEnv},
expr::compile_expr,
helpers::{compile_call_binary, compile_call_unary},
};
fn compile_optional_none(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
) -> Result<(), anyhow::Error> {
if !call_expr.args.is_empty() {
anyhow::bail!("optional.none() expects 0 arguments");
}
body.call(env.get(RuntimeFunction::OptionalNone));
Ok(())
}
fn compile_optional_of(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
if call_expr.args.len() != 1 {
anyhow::bail!("optional.of() expects 1 argument");
}
compile_expr(&call_expr.args[0].expr, body, env, ctx, module)?;
body.call(env.get(RuntimeFunction::OptionalOf));
Ok(())
}
fn compile_optional_of_non_zero_value(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
if call_expr.args.len() != 1 {
anyhow::bail!("optional.ofNonZeroValue() expects 1 argument");
}
compile_expr(&call_expr.args[0].expr, body, env, ctx, module)?;
body.call(env.get(RuntimeFunction::OptionalOfNonZeroValue));
Ok(())
}
fn compile_opt_map(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
let target = call_expr
.target
.as_ref()
.ok_or_else(|| anyhow::anyhow!("optMap requires a target (receiver)"))?;
if call_expr.args.len() != 2 {
anyhow::bail!("optMap() expects 2 arguments (var, body)");
}
let var_name = match &call_expr.args[0].expr {
Expr::Ident(name) => name.clone(),
_ => anyhow::bail!("optMap first argument must be an identifier"),
};
compile_expr(&target.expr, body, env, ctx, module)?;
let opt_local = module.locals.add(ValType::I32);
body.local_tee(opt_local);
body.call(env.get(RuntimeFunction::OptionalHasValue));
body.call(env.get(RuntimeFunction::ValueToBool));
body.unop(walrus::ir::UnaryOp::I32WrapI64);
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,
});
{
let mut then_body = body.instr_seq(then_id);
then_body.local_get(opt_local);
then_body.call(env.get(RuntimeFunction::OptionalValue));
let inner_local = module.locals.add(ValType::I32);
then_body.local_set(inner_local);
let inner_ctx = ctx.with_local(var_name, inner_local);
compile_expr(
&call_expr.args[1].expr,
&mut then_body,
env,
&inner_ctx,
module,
)?;
then_body.call(env.get(RuntimeFunction::OptionalOf));
}
{
body.instr_seq(else_id)
.call(env.get(RuntimeFunction::OptionalNone));
}
Ok(())
}
fn compile_opt_flat_map(
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
let target = call_expr
.target
.as_ref()
.ok_or_else(|| anyhow::anyhow!("optFlatMap requires a target (receiver)"))?;
if call_expr.args.len() != 2 {
anyhow::bail!("optFlatMap() expects 2 arguments (var, body)");
}
let var_name = match &call_expr.args[0].expr {
Expr::Ident(name) => name.clone(),
_ => anyhow::bail!("optFlatMap first argument must be an identifier"),
};
compile_expr(&target.expr, body, env, ctx, module)?;
let opt_local = module.locals.add(ValType::I32);
body.local_tee(opt_local);
body.call(env.get(RuntimeFunction::OptionalHasValue));
body.call(env.get(RuntimeFunction::ValueToBool));
body.unop(walrus::ir::UnaryOp::I32WrapI64);
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,
});
{
let mut then_body = body.instr_seq(then_id);
then_body.local_get(opt_local);
then_body.call(env.get(RuntimeFunction::OptionalValue));
let inner_local = module.locals.add(ValType::I32);
then_body.local_set(inner_local);
let inner_ctx = ctx.with_local(var_name, inner_local);
compile_expr(
&call_expr.args[1].expr,
&mut then_body,
env,
&inner_ctx,
module,
)?;
}
{
body.instr_seq(else_id)
.call(env.get(RuntimeFunction::OptionalNone));
}
Ok(())
}
fn is_optional_namespace_call(call_expr: &CallExpr) -> bool {
matches!(
&call_expr.target,
Some(t) if matches!(&t.expr, Expr::Ident(name) if name == "optional")
)
}
pub fn compile_optional_function(
func_name: &str,
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<bool, anyhow::Error> {
match func_name {
"none" if is_optional_namespace_call(call_expr) => {
compile_optional_none(call_expr, body, env)?;
}
"of" if is_optional_namespace_call(call_expr) => {
compile_optional_of(call_expr, body, env, ctx, module)?;
}
"ofNonZeroValue" if is_optional_namespace_call(call_expr) => {
compile_optional_of_non_zero_value(call_expr, body, env, ctx, module)?;
}
"hasValue" => {
compile_call_unary(
call_expr,
func_name,
RuntimeFunction::OptionalHasValue,
body,
env,
ctx,
module,
)?;
}
"value" => {
compile_call_unary(
call_expr,
func_name,
RuntimeFunction::OptionalValue,
body,
env,
ctx,
module,
)?;
}
"orValue" => {
compile_call_binary(
call_expr,
func_name,
RuntimeFunction::OptionalOrValue,
body,
env,
ctx,
module,
)?;
}
"or" if call_expr.target.is_some() => {
compile_call_binary(
call_expr,
func_name,
RuntimeFunction::OptionalOr,
body,
env,
ctx,
module,
)?;
}
"optMap" => {
compile_opt_map(call_expr, body, env, ctx, module)?;
}
"optFlatMap" => {
compile_opt_flat_map(call_expr, body, env, ctx, module)?;
}
_ => return Ok(false),
}
Ok(true)
}