use cel::common::ast::{CallExpr, Expr};
use ferricel_types::{extensions::BuilderStep, functions::RuntimeFunction};
use walrus::InstrSeqBuilder;
use crate::compiler::{
context::{CompilerContext, CompilerEnv},
expr::compile_expr,
helpers::{emit_string_const, get_memory_id},
};
pub fn compile_builder_entry(
step: &BuilderStep,
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
let (state_keys, output_type) = match step {
BuilderStep::Entry {
state_keys,
output_type,
..
} => (state_keys, output_type.as_str()),
_ => anyhow::bail!("compile_builder_entry called with non-Entry step"),
};
if call_expr.args.len() != state_keys.len() {
anyhow::bail!(
"Builder entry '{}' expects {} argument(s), got {}",
call_expr.func_name,
state_keys.len(),
call_expr.args.len()
);
}
let mem = get_memory_id(module)?;
for (i, key) in state_keys.iter().enumerate() {
if i == 0 {
body.i32_const(0);
}
emit_string_const(output_type, body, env, mem, module);
emit_string_const(key, body, env, mem, module);
compile_expr(&call_expr.args[i].expr, body, env, ctx, module)?;
body.i32_const(0); body.call(env.get(RuntimeFunction::BuilderStepCall));
}
Ok(())
}
pub fn compile_builder_chain(
step: &BuilderStep,
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
let (state_keys, output_type, accumulate) = match step {
BuilderStep::Chain {
state_keys,
output_type,
accumulate,
..
} => (state_keys, output_type.as_str(), *accumulate),
_ => anyhow::bail!("compile_builder_chain called with non-Chain step"),
};
let receiver = call_expr.target.as_ref().ok_or_else(|| {
anyhow::anyhow!(
"Builder chain step '{}' requires a receiver",
call_expr.func_name
)
})?;
if call_expr.args.len() != state_keys.len() {
anyhow::bail!(
"Builder chain step '{}' expects {} argument(s), got {}",
call_expr.func_name,
state_keys.len(),
call_expr.args.len()
);
}
let mem = get_memory_id(module)?;
compile_expr(&receiver.expr, body, env, ctx, module)?;
for (i, key) in state_keys.iter().enumerate() {
emit_string_const(output_type, body, env, mem, module);
emit_string_const(key, body, env, mem, module);
compile_expr(&call_expr.args[i].expr, body, env, ctx, module)?;
body.i32_const(if accumulate { 1 } else { 0 });
body.call(env.get(RuntimeFunction::BuilderStepCall));
}
Ok(())
}
pub fn compile_builder_map_entry(
step: &BuilderStep,
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
let (state_key, output_type) = match step {
BuilderStep::MapEntry {
state_key,
output_type,
..
} => (state_key.as_str(), output_type.as_str()),
_ => anyhow::bail!("compile_builder_map_entry called with non-MapEntry step"),
};
let receiver = call_expr.target.as_ref().ok_or_else(|| {
anyhow::anyhow!(
"Builder MapEntry step '{}' requires a receiver",
call_expr.func_name
)
})?;
if call_expr.args.len() != 2 {
anyhow::bail!(
"Builder MapEntry step '{}' expects 2 arguments, got {}",
call_expr.func_name,
call_expr.args.len()
);
}
let mem = get_memory_id(module)?;
compile_expr(&receiver.expr, body, env, ctx, module)?;
emit_string_const(output_type, body, env, mem, module);
emit_string_const(state_key, body, env, mem, module);
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::BuilderMapEntryCall));
Ok(())
}
pub fn compile_builder_terminal(
step: &BuilderStep,
call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
let (extra_arg_keys, host_namespace, host_function, input_type) = match step {
BuilderStep::Terminal {
extra_arg_keys,
host_namespace,
host_function,
input_type,
..
} => (
extra_arg_keys,
host_namespace.as_str(),
host_function.as_str(),
input_type.as_str(),
),
_ => anyhow::bail!("compile_builder_terminal called with non-Terminal step"),
};
let receiver = call_expr.target.as_ref().ok_or_else(|| {
anyhow::anyhow!(
"Builder terminal '{}' requires a receiver",
call_expr.func_name
)
})?;
if call_expr.args.len() != extra_arg_keys.len() {
anyhow::bail!(
"Builder terminal '{}' expects {} argument(s), got {}",
call_expr.func_name,
extra_arg_keys.len(),
call_expr.args.len()
);
}
let mem = get_memory_id(module)?;
compile_expr(&receiver.expr, body, env, ctx, module)?;
for (i, key) in extra_arg_keys.iter().enumerate() {
emit_string_const(input_type, body, env, mem, module); emit_string_const(key, body, env, mem, module);
compile_expr(&call_expr.args[i].expr, body, env, ctx, module)?;
body.i32_const(0); body.call(env.get(RuntimeFunction::BuilderStepCall));
}
let map_local = module.locals.add(walrus::ValType::I32);
body.local_set(map_local);
emit_string_const(host_namespace, body, env, mem, module);
emit_string_const(host_function, body, env, mem, module);
body.local_get(map_local);
body.call(env.get(RuntimeFunction::ExtCall1));
ctx.record_extension(Some(host_namespace), host_function);
Ok(())
}
fn compile_type_of(expr: &Expr, ctx: &CompilerContext) -> Option<String> {
match expr {
Expr::Call(call) => {
let full_name = build_full_name_from_call(call);
if let Some(entry) = ctx.extensions.builder_entries.get(&full_name) {
return entry.output_type().map(|s| s.to_string());
}
if let Some(target) = &call.target {
let receiver_ty = compile_type_of(&target.expr, ctx);
let short = &call.func_name;
if let Some(steps) = ctx.extensions.builder_steps.get(short.as_str()) {
let matched =
find_matching_step(steps, receiver_ty.as_deref(), call.args.len());
if let Some(step) = matched {
return step.output_type().map(|s| s.to_string());
}
}
}
None
}
_ => None,
}
}
fn build_full_name_from_call(call: &CallExpr) -> String {
let mut segments: Vec<&str> = Vec::new();
collect_segments(call.target.as_deref().map(|e| &e.expr), &mut segments);
segments.push(&call.func_name);
segments.join(".")
}
fn collect_segments<'a>(expr: Option<&'a Expr>, out: &mut Vec<&'a str>) {
match expr {
Some(Expr::Ident(name)) => out.push(name.as_str()),
Some(Expr::Select(sel)) => {
collect_segments(Some(&sel.operand.expr), out);
out.push(&sel.field);
}
_ => {}
}
}
fn find_matching_step<'a>(
steps: &'a [BuilderStep],
receiver_ty: Option<&str>,
arg_count: usize,
) -> Option<&'a BuilderStep> {
if let Some(ty) = receiver_ty {
let candidates: Vec<&BuilderStep> = steps
.iter()
.filter(|s| s.input_type() == Some(ty) && s.expected_args() == arg_count)
.collect();
if candidates.len() == 1 {
return Some(candidates[0]);
}
}
let by_arity: Vec<&BuilderStep> = steps
.iter()
.filter(|s| s.expected_args() == arg_count)
.collect();
if by_arity.len() == 1 {
return Some(by_arity[0]);
}
None
}
pub fn try_compile_builder_call(
full_name: &str, call_expr: &CallExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<bool, anyhow::Error> {
if let Some(step) = ctx.extensions.builder_entries.get(full_name) {
compile_builder_entry(step, call_expr, body, env, ctx, module)?;
return Ok(true);
}
if let Some(target) = &call_expr.target {
let short = &call_expr.func_name;
if let Some(steps) = ctx.extensions.builder_steps.get(short.as_str()) {
let receiver_ty = compile_type_of(&target.expr, ctx);
let step = find_matching_step(steps, receiver_ty.as_deref(), call_expr.args.len())
.ok_or_else(|| {
anyhow::anyhow!(
"No matching builder step '{}' for receiver type {:?} with {} arg(s)",
short,
receiver_ty,
call_expr.args.len()
)
})?;
match step {
BuilderStep::Chain { .. } => {
compile_builder_chain(step, call_expr, body, env, ctx, module)?;
}
BuilderStep::MapEntry { .. } => {
compile_builder_map_entry(step, call_expr, body, env, ctx, module)?;
}
BuilderStep::Terminal { .. } => {
compile_builder_terminal(step, call_expr, body, env, ctx, module)?;
}
BuilderStep::Entry { .. } => unreachable!("Entry steps are not in builder_steps"),
}
return Ok(true);
}
}
Ok(false)
}