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;
pub(crate) fn emit_function(
f: &FunctionDef,
prefix: &str,
declared_errors: &[String],
top_level_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(format_param_wrapper).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)
)
} else {
zig_return_type(&f.return_type)
};
out.push_str(&format!("pub fn {}({}) {} {{\n", f.name, params.join(", "), return_ty));
for p in &f.params {
emit_param_conversion(p, out);
}
let c_args: Vec<String> = f.params.iter().flat_map(c_arg_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(&format!(" _ = {c_call};\n"));
} else {
out.push_str(&format!(" const _result = {c_call};\n"));
}
out.push_str(&format!(" if (c.{prefix}_last_error_code() != 0) {{\n"));
out.push_str(&format!(" return _first_error({error_type});\n"));
out.push_str(" }\n");
for p in &f.params {
emit_param_free(p, out);
}
if matches!(f.return_type, TypeRef::Unit) {
out.push_str(" return;\n");
} else {
let ret_expr = unwrap_return_expr("_result", &f.return_type);
out.push_str(&format!(" return {ret_expr};\n"));
}
} else {
for p in &f.params {
emit_param_free(p, out);
}
if matches!(f.return_type, TypeRef::Unit) {
out.push_str(&format!(" {c_call};\n"));
} else {
out.push_str(&format!(" const _result = {c_call};\n"));
let ret_expr = unwrap_return_expr("_result", &f.return_type);
out.push_str(&format!(" return {ret_expr};\n"));
}
}
out.push_str("}\n");
}
fn format_param_wrapper(p: &ParamDef) -> String {
let ty_str = zig_param_type(&p.ty, p.optional);
format!("{}: {}", p.name, ty_str)
}
fn zig_param_type(ty: &TypeRef, optional: bool) -> String {
let inner = match ty {
TypeRef::String | TypeRef::Path | TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
"[]const u8".to_string()
}
TypeRef::Optional(inner) => {
let inner_str = zig_param_type(inner, false);
return format!("?{inner_str}");
}
other => zig_field_type(other, false),
};
if optional { format!("?{inner}") } else { inner }
}
fn emit_param_conversion(p: &ParamDef, out: &mut String) {
let name = &p.name;
match &p.ty {
TypeRef::String | TypeRef::Path => {
out.push_str(&format!(
" const {name}_z: [:0]u8 = try std.fmt.allocPrintSentinel(\n"
));
out.push_str(&format!(" std.heap.c_allocator, \"{{s}}\", .{{{name}}}, 0,\n"));
out.push_str(" );\n");
}
TypeRef::Vec(_) | TypeRef::Map(_, _) => {
out.push_str(" // Vec/Map parameters are passed as JSON strings across the FFI boundary.\n");
out.push_str(&format!(
" const {name}_z: [:0]u8 = try std.fmt.allocPrintSentinel(\n"
));
out.push_str(&format!(" std.heap.c_allocator, \"{{s}}\", .{{{name}}}, 0,\n"));
out.push_str(" );\n");
}
_ => {
}
}
}
fn emit_param_free(p: &ParamDef, out: &mut String) {
let name = &p.name;
match &p.ty {
TypeRef::String | TypeRef::Path | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
out.push_str(&format!(" std.heap.c_allocator.free({name}_z);\n"));
}
_ => {}
}
}
fn c_arg_names(p: &ParamDef) -> Vec<String> {
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) -> 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(&format!(" const slice = std.mem.sliceTo({raw}, 0);\n"));
s.push_str(" const owned = try std.heap.c_allocator.dupe(u8, slice);\n");
s.push_str(&format!(" _free_string({raw});\n"));
s.push_str(" break :blk owned;\n");
s.push_str(" }");
s
}
_ => raw.to_string(),
}
}
pub(crate) fn zig_return_type(ty: &TypeRef) -> String {
match ty {
TypeRef::String | TypeRef::Path | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => "[]u8".to_string(),
other => zig_field_type(other, false),
}
}