use alef_core::backend::{Backend, BuildConfig, BuildDependency, Capabilities, GeneratedFile};
use alef_core::config::{Language, ResolvedCrateConfig, resolve_output_dir};
use alef_core::ir::ApiSurface;
use std::path::PathBuf;
use crate::trait_bridge::emit_trait_bridge;
mod errors;
mod functions;
mod helpers;
mod types;
use errors::emit_error_set;
use functions::emit_function;
use helpers::emit_helpers;
use types::{emit_enum, emit_type};
fn zig_module_name(crate_name: &str) -> String {
crate_name.replace('-', "_")
}
pub struct ZigBackend;
impl Backend for ZigBackend {
fn name(&self) -> &str {
"zig"
}
fn language(&self) -> Language {
Language::Zig
}
fn capabilities(&self) -> Capabilities {
Capabilities {
supports_async: false,
supports_classes: false,
supports_enums: true,
supports_option: true,
supports_result: true,
supports_callbacks: false,
supports_streaming: false,
}
}
fn generate_bindings(&self, api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let module_name = zig_module_name(&config.name);
let header = config.ffi_header_name();
let prefix = config.ffi_prefix();
let exclude_functions: std::collections::HashSet<&str> = config
.zig
.as_ref()
.map(|c| c.exclude_functions.iter().map(String::as_str).collect())
.unwrap_or_default();
let exclude_types: std::collections::HashSet<&str> = config
.zig
.as_ref()
.map(|c| c.exclude_types.iter().map(String::as_str).collect())
.unwrap_or_default();
let has_async = api
.functions
.iter()
.filter(|f| !exclude_functions.contains(f.name.as_str()))
.any(|f| f.is_async);
let mut content = String::new();
content.push_str("// Generated by alef. Do not edit by hand.\n");
if has_async {
content.push_str("// Async functions are not supported in this backend.\n");
}
content.push('\n');
content.push_str("const std = @import(\"std\");\n");
content.push_str(&format!("pub const c = @cImport(@cInclude(\"{header}\"));\n\n"));
emit_helpers(&prefix, &mut content);
content.push('\n');
for error in &api.errors {
emit_error_set(error, &mut content);
content.push('\n');
}
for ty in api.types.iter().filter(|t| !exclude_types.contains(t.name.as_str())) {
emit_type(ty, &mut content);
content.push('\n');
}
for en in api.enums.iter().filter(|e| !exclude_types.contains(e.name.as_str())) {
emit_enum(en, &mut content);
content.push('\n');
}
let declared_errors: Vec<String> = api.errors.iter().map(|e| e.name.clone()).collect();
let mut top_level_names: std::collections::HashSet<String> = std::collections::HashSet::new();
for f in &api.functions {
top_level_names.insert(f.name.clone());
}
for ty in &api.types {
top_level_names.insert(ty.name.clone());
}
for en in &api.enums {
top_level_names.insert(en.name.clone());
}
let trait_bridge_fn_names: std::collections::HashSet<String> = config
.trait_bridges
.iter()
.filter(|b| !b.exclude_languages.iter().any(|lang| lang == "zig"))
.filter_map(|b| {
api.types
.iter()
.find(|t| t.name == b.trait_name && t.is_trait)
.map(|t| heck::AsSnakeCase(&t.name).to_string())
})
.flat_map(|snake| [format!("register_{snake}"), format!("unregister_{snake}")])
.collect();
for f in api
.functions
.iter()
.filter(|f| !exclude_functions.contains(f.name.as_str()))
{
if f.is_async {
continue;
}
if trait_bridge_fn_names.contains(&f.name) {
continue;
}
emit_function(f, &prefix, &declared_errors, &top_level_names, &mut content);
content.push('\n');
}
for bridge_cfg in &config.trait_bridges {
if bridge_cfg.exclude_languages.iter().any(|lang| lang == "zig") {
continue;
}
if let Some(trait_def) = api.types.iter().find(|t| t.name == bridge_cfg.trait_name && t.is_trait) {
emit_trait_bridge(&prefix, bridge_cfg, trait_def, &mut content);
content.push('\n');
}
}
let dir = resolve_output_dir(None, &config.name, "packages/zig/src");
let path = PathBuf::from(dir).join(format!("{module_name}.zig"));
Ok(vec![GeneratedFile {
path,
content,
generated_header: false,
}])
}
fn build_config(&self) -> Option<BuildConfig> {
Some(BuildConfig {
tool: "zig",
crate_suffix: "",
build_dep: BuildDependency::Ffi,
post_build: vec![],
})
}
}