use cel::common::ast::Expr;
use ferricel_types::functions::RuntimeFunction;
use walrus::{InstrSeqBuilder, ValType};
use crate::compiler::{
context::{CompilerContext, CompilerEnv},
helpers::{emit_get_variable, emit_string_const, get_memory_id},
};
pub fn resolve_type_name(
name: &str,
container: &Option<String>,
schema: &Option<crate::schema::ProtoSchema>,
) -> Option<String> {
let schema = schema.as_ref()?;
if let Some(stripped) = name.strip_prefix('.') {
return if schema.has_message_type(stripped) {
Some(stripped.to_string())
} else {
None
};
}
if schema.has_message_type(name) {
return Some(name.to_string());
}
if let Some(container_str) = container {
let parts: Vec<&str> = container_str.split('.').collect();
for i in (1..=parts.len()).rev() {
let prefix = parts[0..i].join(".");
let qualified = format!("{}.{}", prefix, name);
if schema.has_message_type(&qualified) {
return Some(qualified);
}
}
}
if schema.has_message_type(name) {
return Some(name.to_string());
}
None
}
pub fn variable_candidates(name: &str, container: &Option<String>) -> Vec<String> {
if let Some(stripped) = name.strip_prefix('.') {
return vec![stripped.to_string()];
}
let mut candidates = Vec::new();
if let Some(container_str) = container {
let parts: Vec<&str> = container_str.split('.').collect();
for i in (1..=parts.len()).rev() {
let prefix = parts[0..i].join(".");
candidates.push(format!("{}.{}", prefix, name));
}
}
candidates.push(name.to_string());
candidates
}
fn emit_variable_lookup_chain_raw(
candidates: &[String],
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
module: &mut walrus::Module,
) -> Result<walrus::LocalId, anyhow::Error> {
let result_local = module.locals.add(ValType::I32);
let emit_get = |name: &str,
body: &mut InstrSeqBuilder,
module: &mut walrus::Module|
-> Result<(), anyhow::Error> {
emit_get_variable(name, body, env, module)?;
body.local_set(result_local);
Ok(())
};
if candidates.is_empty() {
body.i32_const(0).local_set(result_local);
body.local_get(result_local);
return Ok(result_local);
}
if candidates.len() == 1 {
emit_get(&candidates[0], body, module)?;
body.local_get(result_local);
return Ok(result_local);
}
emit_get(&candidates[0], body, module)?;
build_fallback_chain(&candidates[1..], result_local, body, env, module)?;
body.local_get(result_local);
Ok(result_local)
}
fn emit_unbound_error(
name: &str,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
module: &mut walrus::Module,
memory_id: walrus::MemoryId,
) -> Result<walrus::LocalId, anyhow::Error> {
let result_local = module.locals.add(ValType::I32);
let name_bytes = name.as_bytes();
let name_len = name_bytes.len() as i32;
let ptr_local = module.locals.add(ValType::I32);
body.i32_const(name_len)
.call(env.get(RuntimeFunction::Malloc))
.local_set(ptr_local);
for (offset, &byte) in name_bytes.iter().enumerate() {
body.local_get(ptr_local);
body.i32_const(byte as i32);
body.store(
memory_id,
walrus::ir::StoreKind::I32_8 { atomic: false },
walrus::ir::MemArg {
align: 1,
offset: offset as u64,
},
);
}
body.local_get(ptr_local)
.i32_const(name_len)
.call(env.get(RuntimeFunction::UnboundVariableError))
.local_set(result_local);
Ok(result_local)
}
fn emit_null_to_unbound_error(
name: &str,
result_local: walrus::LocalId,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
module: &mut walrus::Module,
memory_id: walrus::MemoryId,
) -> Result<(), anyhow::Error> {
body.local_get(result_local)
.unop(walrus::ir::UnaryOp::I32Eqz);
let then_seq = body.dangling_instr_seq(None);
let then_id = then_seq.id();
let else_seq = body.dangling_instr_seq(None);
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);
let name_bytes = name.as_bytes();
let name_len = name_bytes.len() as i32;
let ptr_local = module.locals.add(ValType::I32);
then_body
.i32_const(name_len)
.call(env.get(RuntimeFunction::Malloc))
.local_set(ptr_local);
for (offset, &byte) in name_bytes.iter().enumerate() {
then_body.local_get(ptr_local);
then_body.i32_const(byte as i32);
then_body.store(
memory_id,
walrus::ir::StoreKind::I32_8 { atomic: false },
walrus::ir::MemArg {
align: 1,
offset: offset as u64,
},
);
}
then_body
.local_get(ptr_local)
.i32_const(name_len)
.call(env.get(RuntimeFunction::UnboundVariableError))
.local_set(result_local);
}
Ok(())
}
fn emit_variable_lookup_chain(
candidates: &[String],
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
module: &mut walrus::Module,
) -> Result<walrus::LocalId, anyhow::Error> {
let memory_id = get_memory_id(module)?;
if candidates.is_empty() {
let result_local = emit_unbound_error("", body, env, module, memory_id)?;
body.local_get(result_local);
return Ok(result_local);
}
let result_local = emit_variable_lookup_chain_raw(candidates, body, env, module)?;
body.local_set(result_local);
let bare_name = candidates.last().unwrap().as_str();
emit_null_to_unbound_error(bare_name, result_local, body, env, module, memory_id)?;
body.local_get(result_local);
Ok(result_local)
}
fn build_fallback_chain(
remaining: &[String],
result_local: walrus::LocalId,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
if remaining.is_empty() {
return Ok(());
}
body.local_get(result_local)
.unop(walrus::ir::UnaryOp::I32Eqz);
let then_seq = body.dangling_instr_seq(None);
let then_id = then_seq.id();
let else_seq = body.dangling_instr_seq(None);
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);
let name = &remaining[0];
emit_get_variable(name, &mut then_body, env, module)?;
then_body.local_set(result_local);
build_fallback_chain(&remaining[1..], result_local, &mut then_body, env, module)?;
}
Ok(())
}
pub fn try_collect_qualified_ident(expr: &Expr) -> Option<String> {
match expr {
Expr::Ident(name) => Some(name.clone()),
Expr::Select(s) if !s.test => {
let base = try_collect_qualified_ident(&s.operand.expr)?;
Some(format!("{}.{}", base, s.field))
}
_ => None,
}
}
fn root_ident(expr: &Expr) -> Option<&str> {
match expr {
Expr::Ident(name) => Some(name.as_str()),
Expr::Select(s) if !s.test => root_ident(&s.operand.expr),
_ => None,
}
}
pub fn compile_ident(
name: &str,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
if let Some(&local_id) = ctx.local_vars.get(name) {
body.local_get(local_id);
return Ok(());
}
match name {
"bool" | "int" | "uint" | "double" | "string" | "bytes" | "list" | "map" | "null_type"
| "type" | "timestamp" | "duration" | "optional_type" => {
let memory_id = get_memory_id(module)?;
emit_string_const(name, body, env, memory_id, module);
body.call(env.get(RuntimeFunction::CreateType));
}
_ => {
let candidates = variable_candidates(name, &ctx.container);
emit_variable_lookup_chain(&candidates, body, env, module)?;
}
}
Ok(())
}
pub fn compile_select(
select_expr: &cel::common::ast::SelectExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
const K8S_TYPE_LITERALS: &[&str] = &["net.IP", "net.CIDR"];
let qualified_name = if !select_expr.test {
try_collect_qualified_ident(&Expr::Select(select_expr.clone())).filter(|name| {
K8S_TYPE_LITERALS.contains(&name.as_str())
|| ctx
.schema
.as_ref()
.map(|s| s.has_message_type(name))
.unwrap_or(false)
})
} else {
None
};
if let Some(type_name) = qualified_name {
let memory_id = get_memory_id(module)?;
emit_string_const(&type_name, body, env, memory_id, module);
body.call(env.get(RuntimeFunction::CreateType));
return Ok(());
}
let root_is_local = root_ident(&Expr::Select(select_expr.clone()))
.map(|r| ctx.local_vars.contains_key(r))
.unwrap_or(false);
if !select_expr.test && !root_is_local {
if let Some(full_name) = try_collect_qualified_ident(&Expr::Select(select_expr.clone())) {
let candidates = variable_candidates(&full_name, &ctx.container);
let memory_id = get_memory_id(module)?;
let result_local = module.locals.add(ValType::I32);
emit_variable_lookup_chain_raw(&candidates, body, env, module)?;
body.local_set(result_local);
body.local_get(result_local)
.unop(walrus::ir::UnaryOp::I32Eqz);
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);
compile_field_access(select_expr, &mut then_body, env, ctx, module, memory_id)?;
}
body.instr_seq(else_id).local_get(result_local);
return Ok(());
}
}
let memory_id = get_memory_id(module)?;
compile_field_access(select_expr, body, env, ctx, module, memory_id)?;
Ok(())
}
fn compile_field_access(
select_expr: &cel::common::ast::SelectExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
memory_id: walrus::MemoryId,
) -> Result<(), anyhow::Error> {
super::expr::compile_expr(&select_expr.operand.expr, body, env, ctx, module)?;
let field_name = &select_expr.field;
let field_bytes = field_name.as_bytes();
let field_len = field_bytes.len() as i32;
let field_ptr_local = module.locals.add(ValType::I32);
body.i32_const(field_len)
.call(env.get(RuntimeFunction::Malloc))
.local_tee(field_ptr_local);
for (offset, &byte) in field_bytes.iter().enumerate() {
body.local_get(field_ptr_local);
body.i32_const(byte as i32);
body.store(
memory_id,
walrus::ir::StoreKind::I32_8 { atomic: false },
walrus::ir::MemArg {
align: 1,
offset: offset as u64,
},
);
}
body.i32_const(field_len);
if select_expr.test {
body.call(env.get(RuntimeFunction::HasField));
} else {
body.call(env.get(RuntimeFunction::GetField));
}
Ok(())
}