use alef_core::ir::{FunctionDef, ParamDef, TypeRef};
use super::errors::resolve_zig_error_type;
use super::helpers::emit_cleaned_zig_doc;
use super::types::zig_field_type;
fn is_struct_named(ty: &TypeRef, struct_names: &std::collections::HashSet<String>) -> bool {
match ty {
TypeRef::Named(name) => struct_names.contains(name),
TypeRef::Optional(inner) => is_struct_named(inner, struct_names),
_ => false,
}
}
fn struct_named_inner(ty: &TypeRef) -> Option<&str> {
match ty {
TypeRef::Named(name) => Some(name.as_str()),
TypeRef::Optional(inner) => struct_named_inner(inner),
_ => None,
}
}
fn snake_case(name: &str) -> String {
heck::AsSnakeCase(name).to_string()
}
pub(crate) fn emit_function(
f: &FunctionDef,
prefix: &str,
declared_errors: &[String],
top_level_names: &std::collections::HashSet<String>,
struct_names: &std::collections::HashSet<String>,
out: &mut String,
) {
emit_cleaned_zig_doc(out, &f.doc, "");
let renamed_params: Vec<ParamDef> = f
.params
.iter()
.map(|p| {
let mut p2 = p.clone();
if top_level_names.contains(&p2.name) {
p2.name = format!("{}_arg", p2.name);
}
p2
})
.collect();
let f_local = FunctionDef {
params: renamed_params,
..f.clone()
};
let f = &f_local;
let params: Vec<String> = f.params.iter().map(|p| format_param_wrapper(p, struct_names)).collect();
let zig_error_type = f
.error_type
.as_ref()
.map(|e| resolve_zig_error_type(e, declared_errors));
let return_ty = if let Some(error_type) = &zig_error_type {
format!(
"({}||error{{OutOfMemory}})!{}",
error_type,
zig_return_type(&f.return_type, struct_names)
)
} else {
zig_return_type(&f.return_type, struct_names)
};
out.push_str(&crate::template_env::render(
"function_signature.jinja",
minijinja::context! {
func_name => &f.name,
params => params.join(", "),
return_ty => &return_ty,
},
));
for p in &f.params {
emit_param_conversion(p, prefix, struct_names, out);
}
let c_args: Vec<String> = f.params.iter().flat_map(|p| c_arg_names(p, struct_names)).collect();
let c_call = format!("c.{prefix}_{}({})", f.name, c_args.join(", "));
if let Some(error_type) = &zig_error_type {
if matches!(f.return_type, TypeRef::Unit) {
out.push_str(&crate::template_env::render(
"function_call_unit.jinja",
minijinja::context! {
c_call => &c_call,
},
));
} else {
out.push_str(&crate::template_env::render(
"function_call_result.jinja",
minijinja::context! {
c_call => &c_call,
},
));
}
out.push_str(&crate::template_env::render(
"function_error_check.jinja",
minijinja::context! {
prefix => prefix,
},
));
out.push_str(&crate::template_env::render(
"function_error_return.jinja",
minijinja::context! {
error_type => error_type,
},
));
out.push_str(" }\n");
for p in &f.params {
emit_param_free(p, prefix, struct_names, out);
}
if matches!(f.return_type, TypeRef::Unit) {
out.push_str(" return;\n");
} else {
let ret_expr = unwrap_return_expr("_result", &f.return_type, prefix, struct_names);
out.push_str(&crate::template_env::render(
"function_return.jinja",
minijinja::context! {
ret_expr => ret_expr,
},
));
}
} else {
for p in &f.params {
emit_param_free(p, prefix, struct_names, out);
}
if matches!(f.return_type, TypeRef::Unit) {
out.push_str(&crate::template_env::render(
"function_call_unit.jinja",
minijinja::context! {
c_call => &c_call,
},
));
} else {
out.push_str(&crate::template_env::render(
"function_call_result.jinja",
minijinja::context! {
c_call => &c_call,
},
));
let ret_expr = unwrap_return_expr("_result", &f.return_type, prefix, struct_names);
out.push_str(&crate::template_env::render(
"function_return.jinja",
minijinja::context! {
ret_expr => ret_expr,
},
));
}
}
out.push_str("}\n");
}
fn format_param_wrapper(p: &ParamDef, struct_names: &std::collections::HashSet<String>) -> String {
let ty_str = zig_param_type(&p.ty, p.optional, struct_names);
format!("{}: {}", p.name, ty_str)
}
fn zig_param_type(ty: &TypeRef, optional: bool, struct_names: &std::collections::HashSet<String>) -> String {
let inner = match ty {
TypeRef::String | TypeRef::Path | TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
"[]const u8".to_string()
}
TypeRef::Named(name) if struct_names.contains(name) => "[]const u8".to_string(),
TypeRef::Optional(inner) => {
let inner_str = zig_param_type(inner, false, struct_names);
return format!("?{inner_str}");
}
other => zig_field_type(other, false),
};
if optional { format!("?{inner}") } else { inner }
}
fn emit_param_conversion(
p: &ParamDef,
prefix: &str,
struct_names: &std::collections::HashSet<String>,
out: &mut String,
) {
let name = &p.name;
if let Some(inner_name) = struct_named_inner(&p.ty) {
if struct_names.contains(inner_name) {
let snake = snake_case(inner_name);
let is_optional = p.optional || matches!(p.ty, TypeRef::Optional(_));
if is_optional {
out.push_str(&format!(
" const {name}_z: ?[:0]u8 = if ({name}) |v| try std.fmt.allocPrintSentinel(\n"
));
out.push_str(" std.heap.c_allocator, \"{s}\", .{v}, 0) else null;\n");
out.push_str(&format!(
" const {name}_handle = if ({name}_z) |z| c.{prefix}_{snake}_from_json(z) else null;\n",
));
} else {
out.push_str(&crate::template_env::render(
"param_string_line1.jinja",
minijinja::context! { name => name },
));
out.push_str(&crate::template_env::render(
"param_string_line2.jinja",
minijinja::context! { name => name },
));
out.push_str(&format!(
" const {name}_handle = c.{prefix}_{snake}_from_json({name}_z);\n",
));
}
return;
}
}
let is_optional_string = p.optional
|| matches!(
&p.ty,
TypeRef::Optional(inner)
if matches!(inner.as_ref(), TypeRef::String | TypeRef::Path)
);
if is_optional_string && matches!(unwrap_optional(&p.ty), TypeRef::String | TypeRef::Path) {
out.push_str(&format!(
" const {name}_z: ?[:0]u8 = if ({name}) |v| try std.fmt.allocPrintSentinel(\n"
));
out.push_str(" std.heap.c_allocator, \"{s}\", .{v}, 0) else null;\n");
return;
}
match &p.ty {
TypeRef::String | TypeRef::Path => {
out.push_str(&crate::template_env::render(
"param_string_line1.jinja",
minijinja::context! {
name => name,
},
));
out.push_str(&crate::template_env::render(
"param_string_line2.jinja",
minijinja::context! {
name => name,
},
));
}
TypeRef::Vec(_) | TypeRef::Map(_, _) => {
out.push_str(" // Vec/Map parameters are passed as JSON strings across the FFI boundary.\n");
out.push_str(&crate::template_env::render(
"param_string_line1.jinja",
minijinja::context! {
name => name,
},
));
out.push_str(&crate::template_env::render(
"param_string_line2.jinja",
minijinja::context! {
name => name,
},
));
}
_ => {
}
}
}
fn unwrap_optional(ty: &TypeRef) -> &TypeRef {
match ty {
TypeRef::Optional(inner) => inner,
other => other,
}
}
fn emit_param_free(p: &ParamDef, prefix: &str, struct_names: &std::collections::HashSet<String>, out: &mut String) {
let name = &p.name;
if let Some(inner_name) = struct_named_inner(&p.ty) {
if struct_names.contains(inner_name) {
let snake = snake_case(inner_name);
let is_optional = p.optional || matches!(p.ty, TypeRef::Optional(_));
if is_optional {
out.push_str(&format!(" if ({name}_z) |z| std.heap.c_allocator.free(z);\n"));
out.push_str(&format!(" if ({name}_handle) |h| c.{prefix}_{snake}_free(h);\n"));
} else {
out.push_str(&crate::template_env::render(
"param_free.jinja",
minijinja::context! { name => name },
));
out.push_str(&format!(" if ({name}_handle) |h| c.{prefix}_{snake}_free(h);\n"));
}
return;
}
}
let is_optional_string = p.optional
|| matches!(
&p.ty,
TypeRef::Optional(inner)
if matches!(inner.as_ref(), TypeRef::String | TypeRef::Path)
);
if is_optional_string && matches!(unwrap_optional(&p.ty), TypeRef::String | TypeRef::Path) {
out.push_str(&format!(" if ({name}_z) |z| std.heap.c_allocator.free(z);\n"));
return;
}
match &p.ty {
TypeRef::String | TypeRef::Path | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
out.push_str(&crate::template_env::render(
"param_free.jinja",
minijinja::context! {
name => name,
},
));
}
_ => {}
}
}
fn c_arg_names(p: &ParamDef, struct_names: &std::collections::HashSet<String>) -> Vec<String> {
if is_struct_named(&p.ty, struct_names) {
return vec![format!("{}_handle", p.name)];
}
let is_optional_string = p.optional
|| matches!(
&p.ty,
TypeRef::Optional(inner)
if matches!(inner.as_ref(), TypeRef::String | TypeRef::Path)
);
if is_optional_string && matches!(unwrap_optional(&p.ty), TypeRef::String | TypeRef::Path) {
return vec![format!("if ({0}_z) |z| z.ptr else null", p.name)];
}
match &p.ty {
TypeRef::String | TypeRef::Path | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
vec![format!("{}_z", p.name)]
}
TypeRef::Bytes => {
vec![format!("{}.ptr", p.name), format!("{}.len", p.name)]
}
_ => vec![p.name.clone()],
}
}
fn unwrap_return_expr(
raw: &str,
ty: &TypeRef,
prefix: &str,
struct_names: &std::collections::HashSet<String>,
) -> String {
match ty {
TypeRef::String | TypeRef::Path | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
let mut s = String::new();
s.push_str("blk: {\n");
s.push_str(&crate::template_env::render(
"return_unwrap_slice.jinja",
minijinja::context! {
raw => raw,
},
));
s.push_str(" const owned = try std.heap.c_allocator.dupe(u8, slice);\n");
s.push_str(&crate::template_env::render(
"return_unwrap_free.jinja",
minijinja::context! {
raw => raw,
},
));
s.push_str(" break :blk owned;\n");
s.push_str(" }");
s
}
TypeRef::Named(name) if struct_names.contains(name) => {
let snake = snake_case(name);
let mut s = String::new();
s.push_str("blk: {\n");
s.push_str(&format!(
" const _json_ptr = c.{prefix}_{snake}_to_json({raw}.?);\n"
));
s.push_str(" defer _free_string(_json_ptr);\n");
s.push_str(&format!(" c.{prefix}_{snake}_free({raw}.?);\n"));
s.push_str(" const slice = std.mem.sliceTo(_json_ptr, 0);\n");
s.push_str(" const owned = try std.heap.c_allocator.dupe(u8, slice);\n");
s.push_str(" break :blk owned;\n");
s.push_str(" }");
s
}
_ => raw.to_string(),
}
}
pub(crate) fn zig_return_type(ty: &TypeRef, struct_names: &std::collections::HashSet<String>) -> String {
match ty {
TypeRef::String | TypeRef::Path | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => "[]u8".to_string(),
TypeRef::Named(name) if struct_names.contains(name) => "[]u8".to_string(),
other => zig_field_type(other, false),
}
}