1pub fn c_symbol_name(prefix: &str, module: &str, func: &str) -> String {
9 format!("{prefix}_{module}_{func}")
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum CommentStyle {
15 DoubleSlash,
17 Hash,
19 Xml,
21}
22
23impl CommentStyle {
24 fn open(self) -> &'static str {
25 match self {
26 Self::DoubleSlash => "// ",
27 Self::Hash => "# ",
28 Self::Xml => "<!-- ",
29 }
30 }
31
32 fn close(self) -> &'static str {
33 match self {
34 Self::Xml => " -->",
35 _ => "",
36 }
37 }
38}
39
40pub fn render_prelude(style: CommentStyle, input_basename: &str) -> String {
44 let version = env!("CARGO_PKG_VERSION");
45 let o = style.open();
46 let c = style.close();
47 format!(
48 "{o}Generated by WeaveFFI {version} from {input_basename}{c}\n\
49 {o}DO NOT EDIT — your changes will be overwritten.{c}\n\
50 {o}To regenerate: weaveffi generate {input_basename} -o <out>{c}\n\n"
51 )
52}
53
54pub fn render_trailer(style: CommentStyle, filename: &str) -> String {
57 let o = style.open();
58 let c = style.close();
59 format!("{o}END {filename}{c}\n")
60}
61
62pub fn render_json_prelude(input_basename: &str) -> String {
67 let version = env!("CARGO_PKG_VERSION");
68 format!(
69 " \"//\": \"Generated by WeaveFFI {version} from {input_basename}\",\n \
70 \"//warning\": \"DO NOT EDIT — your changes will be overwritten.\",\n \
71 \"//regenerate\": \"To regenerate: weaveffi generate {input_basename} -o <out>\",\n"
72 )
73}
74
75pub const ABI_RUNTIME_SYMBOLS: &[&str] = &[
83 "error",
84 "handle_t",
85 "error_set",
86 "error_clear",
87 "free_string",
88 "free_bytes",
89 "arena_create",
90 "arena_destroy",
91 "arena_register",
92 "cancel_token",
93 "cancel_token_create",
94 "cancel_token_cancel",
95 "cancel_token_is_cancelled",
96 "cancel_token_destroy",
97];
98
99pub fn render_abi_prefix_aliases(prefix: &str) -> String {
102 if prefix == "weaveffi" {
103 return String::new();
104 }
105 let mut out = String::new();
106 out.push_str("/* Aliases for weaveffi-abi runtime symbols */\n");
107 for sym in ABI_RUNTIME_SYMBOLS {
108 out.push_str(&format!("#define {prefix}_{sym} weaveffi_{sym}\n"));
109 }
110 out.push('\n');
111 out
112}
113
114pub fn wrapper_name(module: &str, func: &str, strip_module_prefix: bool) -> String {
119 if strip_module_prefix {
120 func.to_string()
121 } else {
122 format!("{module}_{func}")
123 }
124}
125
126pub fn local_type_name(name: &str) -> &str {
134 name.rsplit_once('.').map_or(name, |(_, local)| local)
135}
136
137pub fn c_abi_struct_name(name: &str, current_module: &str, prefix: &str) -> String {
147 if let Some((module_path, type_name)) = name.rsplit_once('.') {
148 let module_path = module_path.replace('.', "_");
149 format!("{prefix}_{module_path}_{type_name}")
150 } else {
151 format!("{prefix}_{current_module}_{name}")
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn c_symbol_name_uses_prefix() {
161 assert_eq!(
162 c_symbol_name("weaveffi", "calc", "add"),
163 "weaveffi_calc_add"
164 );
165 assert_eq!(c_symbol_name("myffi", "calc", "add"), "myffi_calc_add");
166 }
167
168 #[test]
169 fn local_type_name_unqualified() {
170 assert_eq!(local_type_name("Contact"), "Contact");
171 }
172
173 #[test]
174 fn local_type_name_qualified() {
175 assert_eq!(local_type_name("other.Contact"), "Contact");
176 }
177
178 #[test]
179 fn local_type_name_multi_level() {
180 assert_eq!(local_type_name("a.b.Widget"), "Widget");
181 }
182
183 #[test]
184 fn c_abi_struct_name_unqualified() {
185 assert_eq!(
186 c_abi_struct_name("Contact", "math", "weaveffi"),
187 "weaveffi_math_Contact"
188 );
189 }
190
191 #[test]
192 fn c_abi_struct_name_qualified() {
193 assert_eq!(
194 c_abi_struct_name("types.Name", "ops", "weaveffi"),
195 "weaveffi_types_Name"
196 );
197 }
198
199 #[test]
200 fn c_abi_struct_name_multi_level_flattens_path() {
201 assert_eq!(
202 c_abi_struct_name("a.b.Widget", "ops", "weaveffi"),
203 "weaveffi_a_b_Widget"
204 );
205 }
206
207 #[test]
208 fn abi_prefix_aliases_default_is_empty() {
209 assert!(render_abi_prefix_aliases("weaveffi").is_empty());
210 }
211
212 #[test]
213 fn abi_prefix_aliases_custom_lists_every_symbol() {
214 let out = render_abi_prefix_aliases("myffi");
215 for sym in ABI_RUNTIME_SYMBOLS {
216 let line = format!("#define myffi_{sym} weaveffi_{sym}");
217 assert!(out.contains(&line), "missing alias `{line}` in:\n{out}");
218 }
219 }
220
221 #[test]
222 fn prelude_double_slash_carries_required_phrases() {
223 let p = render_prelude(CommentStyle::DoubleSlash, "calc.yml");
224 assert!(p.starts_with("// Generated by WeaveFFI "));
225 assert!(p.contains(" from calc.yml\n"));
226 assert!(p.contains("// DO NOT EDIT"));
227 assert!(p.contains("// To regenerate: weaveffi generate calc.yml -o <out>"));
228 assert!(p.ends_with("\n\n"));
229 }
230
231 #[test]
232 fn prelude_hash_uses_hash_marker() {
233 let p = render_prelude(CommentStyle::Hash, "calc.yml");
234 assert!(p.starts_with("# Generated by WeaveFFI "));
235 assert!(p.contains("# DO NOT EDIT"));
236 assert!(p.contains("# To regenerate: weaveffi generate calc.yml -o <out>"));
237 }
238
239 #[test]
240 fn prelude_xml_wraps_lines_in_brackets() {
241 let p = render_prelude(CommentStyle::Xml, "calc.yml");
242 assert!(p.starts_with("<!-- Generated by WeaveFFI "));
243 assert!(p.contains("from calc.yml -->"));
244 assert!(p.contains("<!-- DO NOT EDIT"));
245 assert!(p.contains("your changes will be overwritten. -->"));
246 }
247
248 #[test]
249 fn trailer_has_correct_marker_per_style() {
250 assert_eq!(
251 render_trailer(CommentStyle::DoubleSlash, "lib.rs"),
252 "// END lib.rs\n"
253 );
254 assert_eq!(
255 render_trailer(CommentStyle::Hash, "build.toml"),
256 "# END build.toml\n"
257 );
258 assert_eq!(
259 render_trailer(CommentStyle::Xml, "package.csproj"),
260 "<!-- END package.csproj -->\n"
261 );
262 }
263
264 #[test]
265 fn json_prelude_contains_required_phrases_in_first_lines() {
266 let p = render_json_prelude("calc.yml");
267 let lines: Vec<&str> = p.lines().collect();
268 assert!(lines[0].contains("Generated by WeaveFFI"));
269 assert!(lines[0].contains("from calc.yml"));
270 assert!(lines[1].contains("DO NOT EDIT"));
271 assert!(lines[2].contains("To regenerate"));
272 for line in &lines {
273 assert!(line.starts_with(" "));
274 assert!(line.ends_with(','));
275 }
276 }
277}