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's discriminant constants as a C `typedef enum` named
62/// `type_name`. Multi-line when any variant is documented.
63fn render_enum_constants(out: &mut String, e: &EnumBinding, type_name: &str) {
64    emit_doc(out, &e.doc, "");
65    if e.variants.iter().any(|v| v.doc.is_some()) {
66        out.push_str("typedef enum {\n");
67        for (i, v) in e.variants.iter().enumerate() {
68            emit_doc(out, &v.doc, "    ");
69            let comma = if i + 1 == e.variants.len() { "" } else { "," };
70            let _ = writeln!(out, "    {} = {}{comma}", v.c_const, v.value);
71        }
72        let _ = writeln!(out, "}} {type_name};");
73    } else {
74        let variants: Vec<String> = e
75            .variants
76            .iter()
77            .map(|v| format!("{} = {}", v.c_const, v.value))
78            .collect();
79        let _ = writeln!(
80            out,
81            "typedef enum {{ {} }} {type_name};",
82            variants.join(", ")
83        );
84    }
85}
86
87/// Render a C-style enum typedef. Multi-line when any variant is documented.
88pub fn render_enum_decl(out: &mut String, e: &EnumBinding) {
89    render_enum_constants(out, e, &e.c_tag);
90}
91
92/// Render the *discriminant* enum of a rich (algebraic) enum, named
93/// `{c_tag}_Tag`. The payload-carrying value itself is an opaque struct
94/// `{c_tag}` (forward-declared via [`render_module_type_tags`]); the tag getter
95/// returns one of these discriminant constants as `int32_t`.
96fn render_rich_enum_tag_decl(out: &mut String, e: &EnumBinding) {
97    let tag_enum = format!("{}_Tag", e.c_tag);
98    render_enum_constants(out, e, &tag_enum);
99}
100
101/// Render the function surface of a rich (algebraic) enum: the tag getter, each
102/// variant's constructor and field getters, then the destructor. Assumes the
103/// opaque object tag and every referenced type tag are already forward-declared.
104fn render_rich_enum_fn_decls(out: &mut String, e: &EnumBinding, prefix: &str) {
105    let Some(rich) = &e.rich else {
106        return;
107    };
108    let tag = &e.c_tag;
109    emit_doc(out, &e.doc, "");
110    let _ = writeln!(out, "int32_t {}(const {tag}* self);", rich.tag_symbol);
111    for v in &rich.variants {
112        emit_doc(out, &v.doc, "");
113        fn_decl(out, &v.create, prefix);
114        for field in &v.fields {
115            emit_doc(out, &field.doc, "");
116            let mut parts = vec![format!("const {tag}* self")];
117            parts.extend(
118                field
119                    .getter_out_params
120                    .iter()
121                    .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name)),
122            );
123            let _ = writeln!(
124                out,
125                "{} {}({});",
126                field.getter_ret.render_c(prefix),
127                field.getter_symbol,
128                parts.join(", ")
129            );
130        }
131    }
132    let _ = writeln!(out, "void {}({tag}* self);", rich.destroy_symbol);
133    out.push('\n');
134}
135
136/// Render the opaque struct/builder *tags* (forward typedefs) for one struct.
137///
138/// These reference no other types, so emitting every struct's tags before any
139/// function declaration lets a function in one module accept or return a struct
140/// declared in *another* module (a parent module referencing a child's type).
141fn render_struct_tags(out: &mut String, s: &StructBinding) {
142    let tag = &s.c_tag;
143    let _ = writeln!(out, "typedef struct {tag} {tag};");
144    if let Some(b) = &s.builder {
145        let bt = &b.builder_tag;
146        let _ = writeln!(out, "typedef struct {bt} {bt};");
147    }
148}
149
150/// Render the function declarations for one struct: create/destroy/getters and,
151/// if present, the fluent builder's new/setters/build/destroy. Assumes the
152/// struct (and every other struct it may reference) already has a forward
153/// typedef emitted via [`render_struct_tags`].
154fn render_struct_fn_decls(out: &mut String, s: &StructBinding, prefix: &str) {
155    let tag = &s.c_tag;
156    emit_doc(out, &s.doc, "");
157    fn_decl(out, &s.create, prefix);
158    let _ = writeln!(out, "void {}({tag}* ptr);", s.destroy_symbol);
159    for field in &s.fields {
160        emit_doc(out, &field.doc, "");
161        let mut parts = vec![format!("const {tag}* ptr")];
162        parts.extend(
163            field
164                .getter_out_params
165                .iter()
166                .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name)),
167        );
168        let _ = writeln!(
169            out,
170            "{} {}({});",
171            field.getter_ret.render_c(prefix),
172            field.getter_symbol,
173            parts.join(", ")
174        );
175    }
176    out.push('\n');
177
178    if let Some(b) = &s.builder {
179        let bt = &b.builder_tag;
180        let _ = writeln!(out, "{bt}* {}(void);", b.new_symbol);
181        for (field, (_, setter)) in s.fields.iter().zip(&b.setters) {
182            emit_doc(out, &field.doc, "");
183            let _ = writeln!(
184                out,
185                "void {setter}({bt}* builder, {});",
186                params_str(&field.value_params, prefix)
187            );
188        }
189        let _ = writeln!(
190            out,
191            "{tag}* {}({bt}* builder, {prefix}_error* out_err);",
192            b.build_symbol
193        );
194        let _ = writeln!(out, "void {}({bt}* builder);", b.destroy_symbol);
195        out.push('\n');
196    }
197}
198
199/// Phase 1a: enum definitions for one module. Enums reference no other types,
200/// so they are emitted first across all modules.
201pub fn render_module_enum_defs(out: &mut String, module: &ModuleBinding) {
202    for e in &module.enums {
203        if e.is_rich() {
204            render_rich_enum_tag_decl(out, e);
205        } else {
206            render_enum_decl(out, e);
207        }
208    }
209}
210
211/// Phase 1b: opaque struct/builder/iterator forward typedefs for one module.
212/// Pointers to these are all the C ABI ever uses, so a forward typedef is
213/// sufficient and lets declarations in any module reference any struct.
214pub fn render_module_type_tags(out: &mut String, module: &ModuleBinding) {
215    // A rich (algebraic) enum is an opaque object, declared like a struct tag.
216    for e in &module.enums {
217        if e.is_rich() {
218            let t = &e.c_tag;
219            let _ = writeln!(out, "typedef struct {t} {t};");
220        }
221    }
222    for s in &module.structs {
223        render_struct_tags(out, s);
224    }
225    for f in &module.functions {
226        if let CallShape::Iterator(it) = &f.shape {
227            let t = &it.iter_tag;
228            let _ = writeln!(out, "typedef struct {t} {t};");
229        }
230    }
231}
232
233/// Phase 1c: callback / async-callback function-pointer typedefs for one
234/// module. These may reference enums (by value) and structs (by pointer), so
235/// they are emitted after every module's enums and type tags.
236pub fn render_module_callback_types(out: &mut String, module: &ModuleBinding, prefix: &str) {
237    for cb in &module.callbacks {
238        emit_doc(out, &cb.doc, "");
239        let _ = writeln!(
240            out,
241            "typedef void (*{})({});",
242            cb.c_fn_type,
243            params_str(&cb.abi_params, prefix)
244        );
245    }
246    for f in &module.functions {
247        if let CallShape::Async(a) = &f.shape {
248            let _ = writeln!(
249                out,
250                "typedef void (*{})({});",
251                a.callback_type,
252                params_str(&a.callback_params, prefix)
253            );
254        }
255    }
256}
257
258/// Phase 2: every function prototype for one module: struct create/destroy/
259/// getters and builders, listeners, then sync/async/iterator functions. All
260/// type tags and callback typedefs are assumed already emitted (phases 1a–1c).
261/// Caller controls the leading `// Module:` comment and any framing.
262pub fn render_module_fn_decls(out: &mut String, module: &ModuleBinding, prefix: &str) {
263    for e in &module.enums {
264        render_rich_enum_fn_decls(out, e, prefix);
265    }
266    for s in &module.structs {
267        render_struct_fn_decls(out, s, prefix);
268    }
269    for l in &module.listeners {
270        emit_doc(out, &l.doc, "");
271        let _ = writeln!(
272            out,
273            "uint64_t {}({} callback, void* context);",
274            l.register_symbol, l.callback_c_fn_type
275        );
276        emit_doc(out, &l.doc, "");
277        let _ = writeln!(out, "void {}(uint64_t id);", l.unregister_symbol);
278    }
279    for f in &module.functions {
280        emit_doc(out, &f.doc, "");
281        if let Some(msg) = &f.deprecated {
282            let _ = writeln!(
283                out,
284                "__attribute__((deprecated(\"{}\")))",
285                msg.replace('"', "\\\"")
286            );
287        }
288        match &f.shape {
289            CallShape::Iterator(it) => {
290                let t = &it.iter_tag;
291                fn_decl(out, &it.launch, prefix);
292                fn_decl(out, &it.next, prefix);
293                let _ = writeln!(out, "void {}({t}* iter);", it.destroy_symbol);
294            }
295            CallShape::Async(a) => {
296                fn_decl(out, &a.launch, prefix);
297            }
298            CallShape::Sync(abi) => {
299                fn_decl(out, abi, prefix);
300            }
301        }
302    }
303}
304
305/// Render the complete C ABI declaration surface for `modules` in
306/// dependency-safe order: all enum definitions, then all opaque type tags, then
307/// all callback typedefs, then per-module function prototypes. Emitting every
308/// type tag before any function lets a parent module's function reference a
309/// child module's struct: cross-module forward references the previous
310/// per-module interleaving could not express.
311///
312/// The runtime decls (`handle_t`, `error`, `free_*`, cancel token) are *not*
313/// emitted here; callers render those first (the C generator inserts its map
314/// convention comment in between).
315pub fn render_decls(
316    out: &mut String,
317    modules: &[ModuleBinding],
318    prefix: &str,
319    module_comments: bool,
320) {
321    for m in modules {
322        render_module_enum_defs(out, m);
323    }
324    for m in modules {
325        render_module_type_tags(out, m);
326    }
327    for m in modules {
328        render_module_callback_types(out, m, prefix);
329    }
330    out.push('\n');
331    for m in modules {
332        if module_comments {
333            let _ = writeln!(out, "// Module: {}", m.path);
334        }
335        render_module_fn_decls(out, m, prefix);
336        out.push('\n');
337    }
338}