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 typedef plus create/destroy/getters, then any
88/// fluent builder.
89pub fn render_struct_decls(out: &mut String, s: &StructBinding, prefix: &str) {
90    let tag = &s.c_tag;
91    emit_doc(out, &s.doc, "");
92    let _ = writeln!(out, "typedef struct {tag} {tag};");
93    fn_decl(out, &s.create, prefix);
94    let _ = writeln!(out, "void {}({tag}* ptr);", s.destroy_symbol);
95    for field in &s.fields {
96        emit_doc(out, &field.doc, "");
97        let mut parts = vec![format!("const {tag}* ptr")];
98        parts.extend(
99            field
100                .getter_out_params
101                .iter()
102                .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name)),
103        );
104        let _ = writeln!(
105            out,
106            "{} {}({});",
107            field.getter_ret.render_c(prefix),
108            field.getter_symbol,
109            parts.join(", ")
110        );
111    }
112    out.push('\n');
113
114    if let Some(b) = &s.builder {
115        let bt = &b.builder_tag;
116        let _ = writeln!(out, "typedef struct {bt} {bt};");
117        let _ = writeln!(out, "{bt}* {}(void);", b.new_symbol);
118        for (field, (_, setter)) in s.fields.iter().zip(&b.setters) {
119            emit_doc(out, &field.doc, "");
120            let _ = writeln!(
121                out,
122                "void {setter}({bt}* builder, {});",
123                params_str(&field.value_params, prefix)
124            );
125        }
126        let _ = writeln!(
127            out,
128            "{tag}* {}({bt}* builder, {prefix}_error* out_err);",
129            b.build_symbol
130        );
131        let _ = writeln!(out, "void {}({bt}* builder);", b.destroy_symbol);
132        out.push('\n');
133    }
134}
135
136/// Render every declaration for one module: enums, structs, callbacks,
137/// listeners, then functions (sync/async/iterator). Caller controls the
138/// leading `// Module:` comment and any framing.
139pub fn render_module_decls(out: &mut String, module: &ModuleBinding, prefix: &str) {
140    for e in &module.enums {
141        render_enum_decl(out, e);
142    }
143    for s in &module.structs {
144        render_struct_decls(out, s, prefix);
145    }
146    for cb in &module.callbacks {
147        emit_doc(out, &cb.doc, "");
148        let _ = writeln!(
149            out,
150            "typedef void (*{})({});",
151            cb.c_fn_type,
152            params_str(&cb.abi_params, prefix)
153        );
154    }
155    for l in &module.listeners {
156        emit_doc(out, &l.doc, "");
157        let _ = writeln!(
158            out,
159            "uint64_t {}({} callback, void* context);",
160            l.register_symbol, l.callback_c_fn_type
161        );
162        emit_doc(out, &l.doc, "");
163        let _ = writeln!(out, "void {}(uint64_t id);", l.unregister_symbol);
164    }
165    for f in &module.functions {
166        emit_doc(out, &f.doc, "");
167        if let Some(msg) = &f.deprecated {
168            let _ = writeln!(
169                out,
170                "__attribute__((deprecated(\"{}\")))",
171                msg.replace('"', "\\\"")
172            );
173        }
174        match &f.shape {
175            CallShape::Iterator(it) => {
176                let t = &it.iter_tag;
177                let _ = writeln!(out, "typedef struct {t} {t};");
178                fn_decl(out, &it.launch, prefix);
179                fn_decl(out, &it.next, prefix);
180                let _ = writeln!(out, "void {}({t}* iter);", it.destroy_symbol);
181            }
182            CallShape::Async(a) => {
183                let _ = writeln!(
184                    out,
185                    "typedef void (*{})({});",
186                    a.callback_type,
187                    params_str(&a.callback_params, prefix)
188                );
189                fn_decl(out, &a.launch, prefix);
190            }
191            CallShape::Sync(abi) => {
192                fn_decl(out, abi, prefix);
193            }
194        }
195    }
196}