alef_backend_zig/gen_bindings/
mod.rs1use alef_core::backend::{Backend, BuildConfig, BuildDependency, Capabilities, GeneratedFile};
2use alef_core::config::{AdapterPattern, 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 opaque_handles;
12mod types;
13
14use errors::emit_error_set;
15use functions::emit_function;
16use helpers::emit_helpers;
17use opaque_handles::emit_opaque_handle;
18use types::{emit_enum, emit_type};
19
20fn zig_module_name(crate_name: &str) -> String {
21 crate_name.replace('-', "_")
22}
23
24pub struct ZigBackend;
25
26impl Backend for ZigBackend {
27 fn name(&self) -> &str {
28 "zig"
29 }
30
31 fn language(&self) -> Language {
32 Language::Zig
33 }
34
35 fn capabilities(&self) -> Capabilities {
36 Capabilities {
37 supports_async: false,
38 supports_classes: true,
39 supports_enums: true,
40 supports_option: true,
41 supports_result: true,
42 supports_callbacks: false,
43 supports_streaming: false,
44 }
45 }
46
47 fn generate_bindings(&self, api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
48 let module_name = zig_module_name(&config.name);
49 let header = config.ffi_header_name();
50 let prefix = config.ffi_prefix();
51
52 let exclude_functions: std::collections::HashSet<&str> = config
53 .zig
54 .as_ref()
55 .map(|c| c.exclude_functions.iter().map(String::as_str).collect())
56 .unwrap_or_default();
57 let exclude_types: std::collections::HashSet<&str> = config
58 .zig
59 .as_ref()
60 .map(|c| c.exclude_types.iter().map(String::as_str).collect())
61 .unwrap_or_default();
62
63 let mut content = String::new();
64 content.push_str("// Generated by alef. Do not edit by hand.\n");
65 content.push('\n');
66 content.push_str("const std = @import(\"std\");\n");
67 content.push_str(&crate::template_env::render(
68 "c_import.jinja",
69 minijinja::context! {
70 header => header,
71 },
72 ));
73 content.push('\n');
74
75 emit_helpers(&prefix, &mut content);
77 content.push('\n');
78
79 for error in &api.errors {
80 emit_error_set(error, &mut content);
81 content.push('\n');
82 }
83
84 for ty in api
85 .types
86 .iter()
87 .filter(|t| !exclude_types.contains(t.name.as_str()) && !t.is_opaque && t.has_serde)
88 {
89 emit_type(ty, &mut content);
90 content.push('\n');
91 }
92
93 for en in api.enums.iter().filter(|e| !exclude_types.contains(e.name.as_str())) {
94 emit_enum(en, &mut content);
95 content.push('\n');
96 }
97
98 let declared_errors: Vec<String> = api.errors.iter().map(|e| e.name.clone()).collect();
99 let mut top_level_names: std::collections::HashSet<String> = std::collections::HashSet::new();
102 for f in &api.functions {
103 top_level_names.insert(f.name.clone());
104 }
105 for ty in &api.types {
106 top_level_names.insert(ty.name.clone());
107 }
108 for en in &api.enums {
109 top_level_names.insert(en.name.clone());
110 }
111 let struct_names: std::collections::HashSet<String> = api
116 .types
117 .iter()
118 .filter(|t| !t.is_trait && !t.is_opaque && t.has_serde)
119 .map(|t| t.name.clone())
120 .collect();
121 let opaque_creator_map: std::collections::HashMap<String, (String, String)> = {
126 let mut map = std::collections::HashMap::new();
127 for opaque_ty in api
128 .types
129 .iter()
130 .filter(|t| !t.is_trait && (t.is_opaque || !t.has_serde))
131 {
132 if let Some(creator) = api
133 .functions
134 .iter()
135 .find(|f| matches!(&f.return_type, alef_core::ir::TypeRef::Named(n) if n == &opaque_ty.name))
136 {
137 if let Some(config_param) = creator.params.first() {
138 if let Some(config_name) = functions::opaque_type_name_inner(&config_param.ty) {
139 map.insert(
140 opaque_ty.name.clone(),
141 (creator.name.clone(), heck::AsSnakeCase(config_name).to_string()),
142 );
143 }
144 }
145 }
146 }
147 map
148 };
149
150 let trait_bridge_fn_names: std::collections::HashSet<String> = config
155 .trait_bridges
156 .iter()
157 .filter(|b| !b.exclude_languages.iter().any(|lang| lang == "zig"))
158 .filter_map(|b| {
159 api.types
160 .iter()
161 .find(|t| t.name == b.trait_name && t.is_trait)
162 .map(|t| heck::AsSnakeCase(&t.name).to_string())
163 })
164 .flat_map(|snake| [format!("register_{snake}"), format!("unregister_{snake}")])
165 .collect();
166 for f in api
167 .functions
168 .iter()
169 .filter(|f| !exclude_functions.contains(f.name.as_str()))
170 {
171 if trait_bridge_fn_names.contains(&f.name) {
172 continue;
173 }
174 emit_function(
175 f,
176 &prefix,
177 &declared_errors,
178 &top_level_names,
179 &struct_names,
180 &opaque_creator_map,
181 &mut content,
182 );
183 content.push('\n');
184 }
185
186 for bridge_cfg in &config.trait_bridges {
189 if bridge_cfg.exclude_languages.iter().any(|lang| lang == "zig") {
191 continue;
192 }
193 if let Some(trait_def) = api.types.iter().find(|t| t.name == bridge_cfg.trait_name && t.is_trait) {
194 emit_trait_bridge(&prefix, bridge_cfg, trait_def, &mut content);
195 content.push('\n');
196 }
197 }
198
199 let streaming_item_types: std::collections::HashMap<String, String> = config
204 .adapters
205 .iter()
206 .filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
207 .filter_map(|a| a.item_type.as_ref().map(|item| (a.name.clone(), item.clone())))
208 .collect();
209
210 for ty in api
215 .types
216 .iter()
217 .filter(|t| !t.is_trait && (t.is_opaque || !t.has_serde) && !t.methods.is_empty())
218 .filter(|t| !exclude_types.contains(t.name.as_str()))
219 {
220 emit_opaque_handle(
221 ty,
222 &prefix,
223 &declared_errors,
224 &struct_names,
225 &streaming_item_types,
226 &mut content,
227 );
228 content.push('\n');
229 }
230
231 let dir = resolve_output_dir(None, &config.name, "packages/zig/src");
232 let path = PathBuf::from(dir).join(format!("{module_name}.zig"));
233
234 Ok(vec![GeneratedFile {
235 path,
236 content,
237 generated_header: false,
238 }])
239 }
240
241 fn build_config(&self) -> Option<BuildConfig> {
242 Some(BuildConfig {
243 tool: "zig",
244 crate_suffix: "",
245 build_dep: BuildDependency::Ffi,
246 post_build: vec![],
247 })
248 }
249}