alef_backend_zig/gen_bindings/
mod.rs1use alef_core::backend::{Backend, BuildConfig, BuildDependency, Capabilities, GeneratedFile};
2use alef_core::config::{Language, ResolvedCrateConfig, resolve_output_dir};
3use alef_core::ir::ApiSurface;
4use std::path::PathBuf;
5
6use crate::trait_bridge::emit_trait_bridge;
7
8mod errors;
9mod functions;
10mod helpers;
11mod types;
12
13use errors::emit_error_set;
14use functions::emit_function;
15use helpers::emit_helpers;
16use types::{emit_enum, emit_type};
17
18fn zig_module_name(crate_name: &str) -> String {
19 crate_name.replace('-', "_")
20}
21
22pub struct ZigBackend;
23
24impl Backend for ZigBackend {
25 fn name(&self) -> &str {
26 "zig"
27 }
28
29 fn language(&self) -> Language {
30 Language::Zig
31 }
32
33 fn capabilities(&self) -> Capabilities {
34 Capabilities {
35 supports_async: false,
36 supports_classes: false,
37 supports_enums: true,
38 supports_option: true,
39 supports_result: true,
40 supports_callbacks: false,
41 supports_streaming: false,
42 }
43 }
44
45 fn generate_bindings(&self, api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
46 let module_name = zig_module_name(&config.name);
47 let header = config.ffi_header_name();
48 let prefix = config.ffi_prefix();
49
50 let exclude_functions: std::collections::HashSet<&str> = config
51 .zig
52 .as_ref()
53 .map(|c| c.exclude_functions.iter().map(String::as_str).collect())
54 .unwrap_or_default();
55 let exclude_types: std::collections::HashSet<&str> = config
56 .zig
57 .as_ref()
58 .map(|c| c.exclude_types.iter().map(String::as_str).collect())
59 .unwrap_or_default();
60
61 let has_async = api
62 .functions
63 .iter()
64 .filter(|f| !exclude_functions.contains(f.name.as_str()))
65 .any(|f| f.is_async);
66
67 let mut content = String::new();
68 content.push_str("// Generated by alef. Do not edit by hand.\n");
69 if has_async {
70 content.push_str("// Async functions are not supported in this backend.\n");
71 }
72 content.push('\n');
73 content.push_str("const std = @import(\"std\");\n");
74 content.push_str(&crate::template_env::render(
75 "c_import.jinja",
76 minijinja::context! {
77 header => header,
78 },
79 ));
80 content.push('\n');
81
82 emit_helpers(&prefix, &mut content);
84 content.push('\n');
85
86 for error in &api.errors {
87 emit_error_set(error, &mut content);
88 content.push('\n');
89 }
90
91 for ty in api.types.iter().filter(|t| !exclude_types.contains(t.name.as_str())) {
92 emit_type(ty, &mut content);
93 content.push('\n');
94 }
95
96 for en in api.enums.iter().filter(|e| !exclude_types.contains(e.name.as_str())) {
97 emit_enum(en, &mut content);
98 content.push('\n');
99 }
100
101 let declared_errors: Vec<String> = api.errors.iter().map(|e| e.name.clone()).collect();
102 let mut top_level_names: std::collections::HashSet<String> = std::collections::HashSet::new();
105 for f in &api.functions {
106 top_level_names.insert(f.name.clone());
107 }
108 for ty in &api.types {
109 top_level_names.insert(ty.name.clone());
110 }
111 for en in &api.enums {
112 top_level_names.insert(en.name.clone());
113 }
114 let struct_names: std::collections::HashSet<String> = api
119 .types
120 .iter()
121 .filter(|t| !t.is_trait)
122 .map(|t| t.name.clone())
123 .collect();
124 let trait_bridge_fn_names: std::collections::HashSet<String> = config
129 .trait_bridges
130 .iter()
131 .filter(|b| !b.exclude_languages.iter().any(|lang| lang == "zig"))
132 .filter_map(|b| {
133 api.types
134 .iter()
135 .find(|t| t.name == b.trait_name && t.is_trait)
136 .map(|t| heck::AsSnakeCase(&t.name).to_string())
137 })
138 .flat_map(|snake| [format!("register_{snake}"), format!("unregister_{snake}")])
139 .collect();
140 for f in api
141 .functions
142 .iter()
143 .filter(|f| !exclude_functions.contains(f.name.as_str()))
144 {
145 if f.is_async {
147 continue;
148 }
149 if trait_bridge_fn_names.contains(&f.name) {
150 continue;
151 }
152 emit_function(
153 f,
154 &prefix,
155 &declared_errors,
156 &top_level_names,
157 &struct_names,
158 &mut content,
159 );
160 content.push('\n');
161 }
162
163 for bridge_cfg in &config.trait_bridges {
166 if bridge_cfg.exclude_languages.iter().any(|lang| lang == "zig") {
168 continue;
169 }
170 if let Some(trait_def) = api.types.iter().find(|t| t.name == bridge_cfg.trait_name && t.is_trait) {
171 emit_trait_bridge(&prefix, bridge_cfg, trait_def, &mut content);
172 content.push('\n');
173 }
174 }
175
176 let dir = resolve_output_dir(None, &config.name, "packages/zig/src");
177 let path = PathBuf::from(dir).join(format!("{module_name}.zig"));
178
179 Ok(vec![GeneratedFile {
180 path,
181 content,
182 generated_header: false,
183 }])
184 }
185
186 fn build_config(&self) -> Option<BuildConfig> {
187 Some(BuildConfig {
188 tool: "zig",
189 crate_suffix: "",
190 build_dep: BuildDependency::Ffi,
191 post_build: vec![],
192 })
193 }
194}