1pub fn c_symbol_name(prefix: &str, module: &str, func: &str) -> String {
18 format!("{prefix}_{module}_{func}")
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum CommentStyle {
24 DoubleSlash,
26 Hash,
28 Xml,
30}
31
32impl CommentStyle {
33 fn open(self) -> &'static str {
34 match self {
35 Self::DoubleSlash => "// ",
36 Self::Hash => "# ",
37 Self::Xml => "<!-- ",
38 }
39 }
40
41 fn close(self) -> &'static str {
42 match self {
43 Self::Xml => " -->",
44 _ => "",
45 }
46 }
47}
48
49pub fn render_prelude(style: CommentStyle, input_basename: &str) -> String {
53 let version = env!("CARGO_PKG_VERSION");
54 let o = style.open();
55 let c = style.close();
56 format!(
57 "{o}Generated by WeaveFFI {version} from {input_basename}{c}\n\
58 {o}DO NOT EDIT. Your changes will be overwritten.{c}\n\
59 {o}To regenerate: weaveffi generate {input_basename} -o <out>{c}\n\n"
60 )
61}
62
63pub fn render_trailer(style: CommentStyle, filename: &str) -> String {
66 let o = style.open();
67 let c = style.close();
68 format!("{o}END {filename}{c}\n")
69}
70
71pub fn render_json_prelude(input_basename: &str) -> String {
76 let version = env!("CARGO_PKG_VERSION");
77 format!(
78 " \"//\": \"Generated by WeaveFFI {version} from {input_basename}\",\n \
79 \"//warning\": \"DO NOT EDIT. Your changes will be overwritten.\",\n \
80 \"//regenerate\": \"To regenerate: weaveffi generate {input_basename} -o <out>\",\n"
81 )
82}
83
84pub const ABI_RUNTIME_SYMBOLS: &[&str] = &[
101 "error",
102 "handle_t",
103 "error_clear",
104 "free_string",
105 "free_bytes",
106 "arena_create",
107 "arena_destroy",
108 "arena_register",
109 "cancel_token",
110 "cancel_token_create",
111 "cancel_token_cancel",
112 "cancel_token_is_cancelled",
113 "cancel_token_destroy",
114];
115
116pub fn render_abi_prefix_aliases(prefix: &str) -> String {
119 if prefix == "weaveffi" {
120 return String::new();
121 }
122 let mut out = String::new();
123 out.push_str("/* Aliases for weaveffi-abi runtime symbols */\n");
124 for sym in ABI_RUNTIME_SYMBOLS {
125 out.push_str(&format!("#define {prefix}_{sym} weaveffi_{sym}\n"));
126 }
127 out.push('\n');
128 out
129}
130
131pub fn wrapper_name(module: &str, func: &str, strip_module_prefix: bool) -> String {
136 if strip_module_prefix {
137 func.to_string()
138 } else {
139 format!("{module}_{func}")
140 }
141}
142
143pub fn local_type_name(name: &str) -> &str {
151 name.rsplit_once('.').map_or(name, |(_, local)| local)
152}
153
154pub fn c_abi_struct_name(name: &str, current_module: &str, prefix: &str) -> String {
164 if let Some((module_path, type_name)) = name.rsplit_once('.') {
165 let module_path = module_path.replace('.', "_");
166 format!("{prefix}_{module_path}_{type_name}")
167 } else {
168 format!("{prefix}_{current_module}_{name}")
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn c_symbol_name_uses_prefix() {
178 assert_eq!(
179 c_symbol_name("weaveffi", "calc", "add"),
180 "weaveffi_calc_add"
181 );
182 assert_eq!(c_symbol_name("myffi", "calc", "add"), "myffi_calc_add");
183 }
184
185 #[test]
186 fn local_type_name_unqualified() {
187 assert_eq!(local_type_name("Contact"), "Contact");
188 }
189
190 #[test]
191 fn local_type_name_qualified() {
192 assert_eq!(local_type_name("other.Contact"), "Contact");
193 }
194
195 #[test]
196 fn local_type_name_multi_level() {
197 assert_eq!(local_type_name("a.b.Widget"), "Widget");
198 }
199
200 #[test]
201 fn c_abi_struct_name_unqualified() {
202 assert_eq!(
203 c_abi_struct_name("Contact", "math", "weaveffi"),
204 "weaveffi_math_Contact"
205 );
206 }
207
208 #[test]
209 fn c_abi_struct_name_qualified() {
210 assert_eq!(
211 c_abi_struct_name("types.Name", "ops", "weaveffi"),
212 "weaveffi_types_Name"
213 );
214 }
215
216 #[test]
217 fn c_abi_struct_name_multi_level_flattens_path() {
218 assert_eq!(
219 c_abi_struct_name("a.b.Widget", "ops", "weaveffi"),
220 "weaveffi_a_b_Widget"
221 );
222 }
223
224 #[test]
225 fn abi_prefix_aliases_default_is_empty() {
226 assert!(render_abi_prefix_aliases("weaveffi").is_empty());
227 }
228
229 #[test]
230 fn abi_prefix_aliases_custom_lists_every_symbol() {
231 let out = render_abi_prefix_aliases("myffi");
232 for sym in ABI_RUNTIME_SYMBOLS {
233 let line = format!("#define myffi_{sym} weaveffi_{sym}");
234 assert!(out.contains(&line), "missing alias `{line}` in:\n{out}");
235 }
236 }
237
238 #[test]
239 fn prelude_double_slash_carries_required_phrases() {
240 let p = render_prelude(CommentStyle::DoubleSlash, "calc.yml");
241 assert!(p.starts_with("// Generated by WeaveFFI "));
242 assert!(p.contains(" from calc.yml\n"));
243 assert!(p.contains("// DO NOT EDIT"));
244 assert!(p.contains("// To regenerate: weaveffi generate calc.yml -o <out>"));
245 assert!(p.ends_with("\n\n"));
246 }
247
248 #[test]
249 fn prelude_hash_uses_hash_marker() {
250 let p = render_prelude(CommentStyle::Hash, "calc.yml");
251 assert!(p.starts_with("# Generated by WeaveFFI "));
252 assert!(p.contains("# DO NOT EDIT"));
253 assert!(p.contains("# To regenerate: weaveffi generate calc.yml -o <out>"));
254 }
255
256 #[test]
257 fn prelude_xml_wraps_lines_in_brackets() {
258 let p = render_prelude(CommentStyle::Xml, "calc.yml");
259 assert!(p.starts_with("<!-- Generated by WeaveFFI "));
260 assert!(p.contains("from calc.yml -->"));
261 assert!(p.contains("<!-- DO NOT EDIT"));
262 assert!(p.contains("Your changes will be overwritten. -->"));
263 }
264
265 #[test]
266 fn trailer_has_correct_marker_per_style() {
267 assert_eq!(
268 render_trailer(CommentStyle::DoubleSlash, "lib.rs"),
269 "// END lib.rs\n"
270 );
271 assert_eq!(
272 render_trailer(CommentStyle::Hash, "build.toml"),
273 "# END build.toml\n"
274 );
275 assert_eq!(
276 render_trailer(CommentStyle::Xml, "package.csproj"),
277 "<!-- END package.csproj -->\n"
278 );
279 }
280
281 #[test]
282 fn json_prelude_contains_required_phrases_in_first_lines() {
283 let p = render_json_prelude("calc.yml");
284 let lines: Vec<&str> = p.lines().collect();
285 assert!(lines[0].contains("Generated by WeaveFFI"));
286 assert!(lines[0].contains("from calc.yml"));
287 assert!(lines[1].contains("DO NOT EDIT"));
288 assert!(lines[2].contains("To regenerate"));
289 for line in &lines {
290 assert!(line.starts_with(" "));
291 assert!(line.ends_with(','));
292 }
293 }
294}