Skip to main content

weaveffi_core/
cabi.rs

1//! Shared rendering of the **C ABI declarations** from a
2//! [`BindingModel`](crate::model::BindingModel).
3//!
4//! Both the C generator (which emits the canonical `{prefix}.h`) and the C++
5//! generator (whose idiomatic wrapper opens an `extern "C"` block re-declaring
6//! the same symbols) render their C declarations through this module. Before it
7//! existed the two re-derived the ABI independently and drifted — most visibly,
8//! the C++ `extern "C"` block lowered `iter<T>` as a list and omitted callbacks
9//! and listeners entirely. Routing both through one model-driven renderer makes
10//! that class of drift impossible.
11
12use std::fmt::Write;
13
14use crate::abi::AbiParam;
15use crate::codegen::common::{emit_doc as common_emit_doc, DocCommentStyle};
16use crate::model::{AbiFn, CallShape, EnumBinding, ModuleBinding, StructBinding};
17
18/// Emit a `/** ... */` doc comment at `indent`.
19pub fn emit_doc(out: &mut String, doc: &Option<String>, indent: &str) {
20    common_emit_doc(out, doc, indent, DocCommentStyle::Javadoc);
21}
22
23/// Join lowered ABI slots into a `"<c-type> <name>, ..."` declaration string.
24pub fn params_str(params: &[AbiParam], prefix: &str) -> String {
25    params
26        .iter()
27        .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name))
28        .collect::<Vec<_>>()
29        .join(", ")
30}
31
32/// Render a full `{ret} {symbol}({params});` declaration for a lowered symbol.
33pub fn fn_decl(out: &mut String, f: &AbiFn, prefix: &str) {
34    let _ = writeln!(
35        out,
36        "{} {}({});",
37        f.ret.render_c(prefix),
38        f.symbol,
39        params_str(&f.params, prefix)
40    );
41}
42
43/// Render the runtime typedefs and helper prototypes (`handle_t`, `error`,
44/// `free_*`, `cancel_token`) that every WeaveFFI C surface depends on.
45pub fn render_runtime_decls(out: &mut String, prefix: &str) {
46    let _ = write!(
47        out,
48        "typedef uint64_t {prefix}_handle_t;\n\n\
49         typedef struct {prefix}_error {{ int32_t code; const char* message; }} {prefix}_error;\n\n\
50         void {prefix}_error_clear({prefix}_error* err);\n\
51         void {prefix}_free_string(const char* ptr);\n\
52         void {prefix}_free_bytes(uint8_t* ptr, size_t len);\n\n\
53         typedef struct {prefix}_cancel_token {prefix}_cancel_token;\n\
54         {prefix}_cancel_token* {prefix}_cancel_token_create(void);\n\
55         void {prefix}_cancel_token_cancel({prefix}_cancel_token* token);\n\
56         bool {prefix}_cancel_token_is_cancelled(const {prefix}_cancel_token* token);\n\
57         void {prefix}_cancel_token_destroy({prefix}_cancel_token* token);\n\n",
58    );
59}
60
61/// Render an enum typedef. Multi-line when any variant is documented.
62pub fn render_enum_decl(out: &mut String, e: &EnumBinding) {
63    emit_doc(out, &e.doc, "");
64    if e.variants.iter().any(|v| v.doc.is_some()) {
65        out.push_str("typedef enum {\n");
66        for (i, v) in e.variants.iter().enumerate() {
67            emit_doc(out, &v.doc, "    ");
68            let comma = if i + 1 == e.variants.len() { "" } else { "," };
69            let _ = writeln!(out, "    {} = {}{comma}", v.c_const, v.value);
70        }
71        let _ = writeln!(out, "}} {};", e.c_tag);
72    } else {
73        let variants: Vec<String> = e
74            .variants
75            .iter()
76            .map(|v| format!("{} = {}", v.c_const, v.value))
77            .collect();
78        let _ = writeln!(
79            out,
80            "typedef enum {{ {} }} {};",
81            variants.join(", "),
82            e.c_tag
83        );
84    }
85}
86
87/// Render the opaque struct/builder *tags* (forward typedefs) for one struct.
88///
89/// These reference no other types, so emitting every struct's tags before any
90/// function declaration lets a function in one module accept or return a struct
91/// declared in *another* module (a parent module referencing a child's type).
92fn render_struct_tags(out: &mut String, s: &StructBinding) {
93    let tag = &s.c_tag;
94    let _ = writeln!(out, "typedef struct {tag} {tag};");
95    if let Some(b) = &s.builder {
96        let bt = &b.builder_tag;
97        let _ = writeln!(out, "typedef struct {bt} {bt};");
98    }
99}
100
101/// Render the function declarations for one struct: create/destroy/getters and,
102/// if present, the fluent builder's new/setters/build/destroy. Assumes the
103/// struct (and every other struct it may reference) already has a forward
104/// typedef emitted via [`render_struct_tags`].
105fn render_struct_fn_decls(out: &mut String, s: &StructBinding, prefix: &str) {
106    let tag = &s.c_tag;
107    emit_doc(out, &s.doc, "");
108    fn_decl(out, &s.create, prefix);
109    let _ = writeln!(out, "void {}({tag}* ptr);", s.destroy_symbol);
110    for field in &s.fields {
111        emit_doc(out, &field.doc, "");
112        let mut parts = vec![format!("const {tag}* ptr")];
113        parts.extend(
114            field
115                .getter_out_params
116                .iter()
117                .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name)),
118        );
119        let _ = writeln!(
120            out,
121            "{} {}({});",
122            field.getter_ret.render_c(prefix),
123            field.getter_symbol,
124            parts.join(", ")
125        );
126    }
127    out.push('\n');
128
129    if let Some(b) = &s.builder {
130        let bt = &b.builder_tag;
131        let _ = writeln!(out, "{bt}* {}(void);", b.new_symbol);
132        for (field, (_, setter)) in s.fields.iter().zip(&b.setters) {
133            emit_doc(out, &field.doc, "");
134            let _ = writeln!(
135                out,
136                "void {setter}({bt}* builder, {});",
137                params_str(&field.value_params, prefix)
138            );
139        }
140        let _ = writeln!(
141            out,
142            "{tag}* {}({bt}* builder, {prefix}_error* out_err);",
143            b.build_symbol
144        );
145        let _ = writeln!(out, "void {}({bt}* builder);", b.destroy_symbol);
146        out.push('\n');
147    }
148}
149
150/// Phase 1a — enum definitions for one module. Enums reference no other types,
151/// so they are emitted first across all modules.
152pub fn render_module_enum_defs(out: &mut String, module: &ModuleBinding) {
153    for e in &module.enums {
154        render_enum_decl(out, e);
155    }
156}
157
158/// Phase 1b — opaque struct/builder/iterator forward typedefs for one module.
159/// Pointers to these are all the C ABI ever uses, so a forward typedef is
160/// sufficient and lets declarations in any module reference any struct.
161pub fn render_module_type_tags(out: &mut String, module: &ModuleBinding) {
162    for s in &module.structs {
163        render_struct_tags(out, s);
164    }
165    for f in &module.functions {
166        if let CallShape::Iterator(it) = &f.shape {
167            let t = &it.iter_tag;
168            let _ = writeln!(out, "typedef struct {t} {t};");
169        }
170    }
171}
172
173/// Phase 1c — callback / async-callback function-pointer typedefs for one
174/// module. These may reference enums (by value) and structs (by pointer), so
175/// they are emitted after every module's enums and type tags.
176pub fn render_module_callback_types(out: &mut String, module: &ModuleBinding, prefix: &str) {
177    for cb in &module.callbacks {
178        emit_doc(out, &cb.doc, "");
179        let _ = writeln!(
180            out,
181            "typedef void (*{})({});",
182            cb.c_fn_type,
183            params_str(&cb.abi_params, prefix)
184        );
185    }
186    for f in &module.functions {
187        if let CallShape::Async(a) = &f.shape {
188            let _ = writeln!(
189                out,
190                "typedef void (*{})({});",
191                a.callback_type,
192                params_str(&a.callback_params, prefix)
193            );
194        }
195    }
196}
197
198/// Phase 2 — every function prototype for one module: struct create/destroy/
199/// getters and builders, listeners, then sync/async/iterator functions. All
200/// type tags and callback typedefs are assumed already emitted (phases 1a–1c).
201/// Caller controls the leading `// Module:` comment and any framing.
202pub fn render_module_fn_decls(out: &mut String, module: &ModuleBinding, prefix: &str) {
203    for s in &module.structs {
204        render_struct_fn_decls(out, s, prefix);
205    }
206    for l in &module.listeners {
207        emit_doc(out, &l.doc, "");
208        let _ = writeln!(
209            out,
210            "uint64_t {}({} callback, void* context);",
211            l.register_symbol, l.callback_c_fn_type
212        );
213        emit_doc(out, &l.doc, "");
214        let _ = writeln!(out, "void {}(uint64_t id);", l.unregister_symbol);
215    }
216    for f in &module.functions {
217        emit_doc(out, &f.doc, "");
218        if let Some(msg) = &f.deprecated {
219            let _ = writeln!(
220                out,
221                "__attribute__((deprecated(\"{}\")))",
222                msg.replace('"', "\\\"")
223            );
224        }
225        match &f.shape {
226            CallShape::Iterator(it) => {
227                let t = &it.iter_tag;
228                fn_decl(out, &it.launch, prefix);
229                fn_decl(out, &it.next, prefix);
230                let _ = writeln!(out, "void {}({t}* iter);", it.destroy_symbol);
231            }
232            CallShape::Async(a) => {
233                fn_decl(out, &a.launch, prefix);
234            }
235            CallShape::Sync(abi) => {
236                fn_decl(out, abi, prefix);
237            }
238        }
239    }
240}
241
242/// Render the complete C ABI declaration surface for `modules` in
243/// dependency-safe order: all enum definitions, then all opaque type tags, then
244/// all callback typedefs, then per-module function prototypes. Emitting every
245/// type tag before any function lets a parent module's function reference a
246/// child module's struct — cross-module forward references the previous
247/// per-module interleaving could not express.
248///
249/// The runtime decls (`handle_t`, `error`, `free_*`, cancel token) are *not*
250/// emitted here; callers render those first (the C generator inserts its map
251/// convention comment in between).
252pub fn render_decls(
253    out: &mut String,
254    modules: &[ModuleBinding],
255    prefix: &str,
256    module_comments: bool,
257) {
258    for m in modules {
259        render_module_enum_defs(out, m);
260    }
261    for m in modules {
262        render_module_type_tags(out, m);
263    }
264    for m in modules {
265        render_module_callback_types(out, m, prefix);
266    }
267    out.push('\n');
268    for m in modules {
269        if module_comments {
270            let _ = writeln!(out, "// Module: {}", m.path);
271        }
272        render_module_fn_decls(out, m, prefix);
273        out.push('\n');
274    }
275}