use cel::common::ast::{ComprehensionExpr, EntryExpr, Expr, ListExpr, MapExpr, StructExpr};
use ferricel_types::functions::RuntimeFunction;
use slog::error;
use walrus::{InstrSeqBuilder, ValType};
use crate::compiler::{
access::resolve_type_name,
context::{CompilerContext, CompilerEnv},
expr::compile_expr,
helpers::compile_string_to_local,
};
pub fn compile_list(
list_expr: &ListExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
body.call(env.get(RuntimeFunction::CreateArray));
let array_ptr_local = module.locals.add(ValType::I32);
body.local_set(array_ptr_local);
for (idx, element) in list_expr.elements.iter().enumerate() {
let is_optional_elem = list_expr.optional_indices.contains(&idx);
if is_optional_elem {
compile_expr(&element.expr, body, env, ctx, module)?;
let elem_local = module.locals.add(ValType::I32);
body.local_tee(elem_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(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);
then_body.local_get(elem_local);
then_body.call(env.get(RuntimeFunction::OptionalValue));
let inner_local = module.locals.add(ValType::I32);
then_body.local_set(inner_local);
then_body.local_get(array_ptr_local);
then_body.local_get(inner_local);
then_body.call(env.get(RuntimeFunction::ArrayPush));
}
} else {
compile_expr(&element.expr, body, env, ctx, module)?;
let element_ptr_local = module.locals.add(ValType::I32);
body.local_set(element_ptr_local);
body.local_get(array_ptr_local);
body.local_get(element_ptr_local);
body.call(env.get(RuntimeFunction::ArrayPush));
}
}
body.local_get(array_ptr_local);
Ok(())
}
pub fn compile_map(
map_expr: &MapExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
body.call(env.get(RuntimeFunction::CreateMap));
let map_ptr_local = module.locals.add(ValType::I32);
body.local_set(map_ptr_local);
for entry in &map_expr.entries {
match &entry.expr {
EntryExpr::MapEntry(map_entry) => {
if map_entry.optional {
compile_expr(&map_entry.key.expr, body, env, ctx, module)?;
let key_ptr_local = module.locals.add(ValType::I32);
body.local_set(key_ptr_local);
compile_expr(&map_entry.value.expr, body, env, ctx, module)?;
let value_opt_local = module.locals.add(ValType::I32);
body.local_tee(value_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(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);
then_body.local_get(value_opt_local);
then_body.call(env.get(RuntimeFunction::OptionalValue));
let inner_local = module.locals.add(ValType::I32);
then_body.local_set(inner_local);
then_body.local_get(map_ptr_local);
then_body.local_get(key_ptr_local);
then_body.local_get(inner_local);
then_body.call(env.get(RuntimeFunction::MapInsert));
}
} else {
compile_expr(&map_entry.key.expr, body, env, ctx, module)?;
let key_ptr_local = module.locals.add(ValType::I32);
body.local_set(key_ptr_local);
compile_expr(&map_entry.value.expr, body, env, ctx, module)?;
let value_ptr_local = module.locals.add(ValType::I32);
body.local_set(value_ptr_local);
body.local_get(map_ptr_local);
body.local_get(key_ptr_local);
body.local_get(value_ptr_local);
body.call(env.get(RuntimeFunction::MapInsert));
}
}
_ => anyhow::bail!("Unsupported map entry type: {:?}", entry.expr),
}
}
body.local_get(map_ptr_local);
Ok(())
}
pub fn compile_struct(
struct_expr: &StructExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
let resolved_type_name = resolve_type_name(&struct_expr.type_name, &ctx.container, &ctx.schema)
.unwrap_or_else(|| struct_expr.type_name.clone());
body.call(env.get(RuntimeFunction::CreateMap));
let map_ptr_local = module.locals.add(ValType::I32);
body.local_set(map_ptr_local);
let type_key_local = compile_string_to_local("__type__", body, env, module)?;
let type_value_local = compile_string_to_local(&resolved_type_name, body, env, module)?;
body.local_get(map_ptr_local);
body.local_get(type_key_local);
body.local_get(type_value_local);
body.call(env.get(RuntimeFunction::MapInsert));
if let Some(schema) = &ctx.schema {
if resolved_type_name.contains('.') && !schema.has_message_type(&resolved_type_name) {
error!(
ctx.logger,
"Struct '{}' looks like a protobuf message, but is not defined in the provided schema",
struct_expr.type_name;
"struct_type" => &struct_expr.type_name,
"resolved_type" => &resolved_type_name
);
error!(ctx.logger, "Wrapper type semantics will not be available");
error!(
ctx.logger,
"Ensure the correct proto descriptor files are provided with --proto-descriptor"
);
}
let wrapper_fields = schema.get_wrapper_fields(&resolved_type_name);
if !wrapper_fields.is_empty() {
body.call(env.get(RuntimeFunction::CreateArray));
let array_ptr_local = module.locals.add(ValType::I32);
body.local_set(array_ptr_local);
for field_name in &wrapper_fields {
let field_name_local = compile_string_to_local(field_name, body, env, module)?;
body.local_get(array_ptr_local);
body.local_get(field_name_local);
body.call(env.get(RuntimeFunction::ArrayPush));
}
let wrapper_key_local =
compile_string_to_local("__wrapper_fields__", body, env, module)?;
body.local_get(map_ptr_local);
body.local_get(wrapper_key_local);
body.local_get(array_ptr_local);
body.call(env.get(RuntimeFunction::MapInsert));
}
let field_defaults = schema.get_field_default_kinds(&resolved_type_name);
if !field_defaults.is_empty() {
body.call(env.get(RuntimeFunction::CreateMap));
let defaults_map_local = module.locals.add(ValType::I32);
body.local_set(defaults_map_local);
for (fname, fkind) in &field_defaults {
let fname_local = compile_string_to_local(fname, body, env, module)?;
let fkind_local = compile_string_to_local(fkind, body, env, module)?;
body.local_get(defaults_map_local);
body.local_get(fname_local);
body.local_get(fkind_local);
body.call(env.get(RuntimeFunction::MapInsert));
}
let defaults_key_local =
compile_string_to_local("__field_defaults__", body, env, module)?;
body.local_get(map_ptr_local);
body.local_get(defaults_key_local);
body.local_get(defaults_map_local);
body.call(env.get(RuntimeFunction::MapInsert));
}
if resolved_type_name == "google.protobuf.Any" {
use cel::common::ast::{EntryExpr as EE, LiteralValue};
let maybe_type_url: Option<String> = struct_expr.entries.iter().find_map(|e| {
if let EE::StructField(sf) = &e.expr
&& sf.field == "type_url"
&& let Expr::Literal(LiteralValue::String(s)) = &sf.value.expr
{
return Some(s.inner().to_owned());
}
None
});
if let Some(type_url) = maybe_type_url {
let msg_type = if let Some(stripped) = type_url.strip_prefix("type.googleapis.com/")
{
stripped.to_string()
} else {
type_url.clone()
};
let field_schema = schema.get_any_field_schema(&msg_type);
if !field_schema.is_empty() {
body.call(env.get(RuntimeFunction::CreateMap));
let schema_map_local = module.locals.add(ValType::I32);
body.local_set(schema_map_local);
for (field_num, kind_str) in &field_schema {
let num_key_local =
compile_string_to_local(&field_num.to_string(), body, env, module)?;
let kind_val_local = compile_string_to_local(kind_str, body, env, module)?;
body.local_get(schema_map_local);
body.local_get(num_key_local);
body.local_get(kind_val_local);
body.call(env.get(RuntimeFunction::MapInsert));
}
let any_type_key_local =
compile_string_to_local("__any_type__", body, env, module)?;
let any_type_val_local = compile_string_to_local(&msg_type, body, env, module)?;
body.local_get(schema_map_local);
body.local_get(any_type_key_local);
body.local_get(any_type_val_local);
body.call(env.get(RuntimeFunction::MapInsert));
let schema_key_local =
compile_string_to_local("__any_schema__", body, env, module)?;
body.local_get(map_ptr_local);
body.local_get(schema_key_local);
body.local_get(schema_map_local);
body.call(env.get(RuntimeFunction::MapInsert));
}
}
}
} else if resolved_type_name.contains('.') {
error!(
ctx.logger,
"Struct '{}' looks like a protobuf message, but no schema provided",
struct_expr.type_name;
"struct_type" => &struct_expr.type_name,
"resolved_type" => &resolved_type_name
);
error!(ctx.logger, "Wrapper type semantics will not be available");
error!(ctx.logger, "Use --proto-descriptor to provide schema");
}
for entry in &struct_expr.entries {
match &entry.expr {
EntryExpr::StructField(struct_field) => {
if struct_field.optional {
let field_key_local =
compile_string_to_local(&struct_field.field, body, env, module)?;
compile_expr(&struct_field.value.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(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);
then_body.local_get(opt_local);
then_body.call(env.get(RuntimeFunction::OptionalValue));
let field_value_local = module.locals.add(ValType::I32);
then_body.local_set(field_value_local);
then_body.local_get(map_ptr_local);
then_body.local_get(field_key_local);
then_body.local_get(field_value_local);
then_body.call(env.get(RuntimeFunction::MapInsert));
}
{
body.instr_seq(else_id);
}
} else {
let field_key_local =
compile_string_to_local(&struct_field.field, body, env, module)?;
compile_expr(&struct_field.value.expr, body, env, ctx, module)?;
let field_value_local = module.locals.add(ValType::I32);
body.local_set(field_value_local);
body.local_get(map_ptr_local);
body.local_get(field_key_local);
body.local_get(field_value_local);
body.call(env.get(RuntimeFunction::MapInsert));
}
}
_ => anyhow::bail!("Unsupported struct entry type: {:?}", entry.expr),
}
}
body.local_get(map_ptr_local);
Ok(())
}
pub fn compile_comprehension(
comp_expr: &ComprehensionExpr,
body: &mut InstrSeqBuilder,
env: &CompilerEnv,
ctx: &CompilerContext,
module: &mut walrus::Module,
) -> Result<(), anyhow::Error> {
compile_expr(&comp_expr.iter_range.expr, body, env, ctx, module)?;
let range_local = module.locals.add(ValType::I32);
body.local_set(range_local);
body.local_get(range_local);
body.call(env.get(RuntimeFunction::IterPrepare));
let prepared_local = module.locals.add(ValType::I32);
body.local_set(prepared_local);
body.local_get(prepared_local);
body.call(env.get(RuntimeFunction::ArrayLen)); let length_local = module.locals.add(ValType::I32);
body.local_set(length_local);
compile_expr(&comp_expr.accu_init.expr, body, env, ctx, module)?;
let accu_local = module.locals.add(ValType::I32);
body.local_set(accu_local);
let index_local = module.locals.add(ValType::I32);
body.i32_const(0);
body.local_set(index_local);
let exit_block = body.dangling_instr_seq(None);
let exit_block_id = exit_block.id();
let continue_loop = body.dangling_instr_seq(None);
let continue_loop_id = continue_loop.id();
body.instr(walrus::ir::Block { seq: exit_block_id });
body.instr_seq(exit_block_id).instr(walrus::ir::Loop {
seq: continue_loop_id,
});
let mut loop_body = body.instr_seq(continue_loop_id);
loop_body.local_get(index_local);
loop_body.local_get(length_local);
loop_body.binop(walrus::ir::BinaryOp::I32GeU); loop_body.instr(walrus::ir::BrIf {
block: exit_block_id,
});
loop_body.local_get(prepared_local);
loop_body.local_get(index_local);
loop_body.call(env.get(RuntimeFunction::ArrayGet));
let element_local = module.locals.add(ValType::I32);
loop_body.local_set(element_local);
let inner_ctx = ctx.with_local(comp_expr.iter_var.clone(), element_local);
let inner_ctx = inner_ctx.with_local(comp_expr.accu_var.clone(), accu_local);
let is_list_accumulator =
matches!(&comp_expr.accu_init.expr, Expr::List(l) if l.elements.is_empty());
let map_inner = if is_list_accumulator {
extract_map_concat_inner(&comp_expr.loop_step.expr, &comp_expr.accu_var)
} else {
None
};
if let Some(inner_expr) = map_inner {
let result_local = module.locals.add(ValType::I32);
compile_expr(inner_expr, &mut loop_body, env, &inner_ctx, module)?;
loop_body.local_set(result_local);
loop_body.local_get(result_local);
loop_body.call(env.get(RuntimeFunction::IsError));
let err_then = loop_body.dangling_instr_seq(None);
let err_then_id = err_then.id();
let err_else = loop_body.dangling_instr_seq(None);
let err_else_id = err_else.id();
{
let mut t = loop_body.instr_seq(err_then_id);
t.local_get(result_local);
t.local_set(accu_local);
t.instr(walrus::ir::Br {
block: exit_block_id,
});
}
{
let mut e = loop_body.instr_seq(err_else_id);
e.local_get(accu_local);
e.local_get(result_local);
e.call(env.get(RuntimeFunction::ArrayPush));
}
loop_body.instr(walrus::ir::IfElse {
consequent: err_then_id,
alternative: err_else_id,
});
} else {
compile_expr(
&comp_expr.loop_step.expr,
&mut loop_body,
env,
&inner_ctx,
module,
)?;
loop_body.local_set(accu_local);
}
let inner_ctx = inner_ctx.with_local(comp_expr.accu_var.clone(), accu_local);
compile_expr(
&comp_expr.loop_cond.expr,
&mut loop_body,
env,
&inner_ctx,
module,
)?;
loop_body.call(env.get(RuntimeFunction::ValueToBool));
loop_body.unop(walrus::ir::UnaryOp::I64Eqz); loop_body.instr(walrus::ir::BrIf {
block: exit_block_id,
});
loop_body.local_get(index_local);
loop_body.i32_const(1);
loop_body.binop(walrus::ir::BinaryOp::I32Add);
loop_body.local_set(index_local);
loop_body.instr(walrus::ir::Br {
block: continue_loop_id,
});
let result_ctx = ctx.with_local(comp_expr.accu_var.clone(), accu_local);
compile_expr(&comp_expr.result.expr, body, env, &result_ctx, module)?;
Ok(())
}
fn extract_map_concat_inner<'a>(step: &'a Expr, accu_var: &str) -> Option<&'a Expr> {
match step {
Expr::Call(call) if call.func_name == "_+_" && call.args.len() == 2 => {
let rhs = &call.args[1].expr;
if let Expr::List(list) = rhs
&& list.elements.len() == 1
&& !list.optional_indices.contains(&0)
{
return Some(&list.elements[0].expr);
}
None
}
Expr::Call(call) if call.func_name == "_?_(_:_)" && call.args.len() == 3 => {
let consequent = &call.args[1].expr;
let alternative = &call.args[2].expr;
let alt_is_accu = matches!(alternative, Expr::Ident(name) if name == accu_var);
if alt_is_accu {
extract_map_concat_inner(consequent, accu_var)
} else {
None
}
}
_ => None,
}
}