1pub fn c_symbol_name(module: &str, func: &str) -> String {
3 format!("weaveffi_{}_{}", module, func)
4}
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum CommentStyle {
9 DoubleSlash,
11 Hash,
13 Xml,
15}
16
17impl CommentStyle {
18 fn open(self) -> &'static str {
19 match self {
20 Self::DoubleSlash => "// ",
21 Self::Hash => "# ",
22 Self::Xml => "<!-- ",
23 }
24 }
25
26 fn close(self) -> &'static str {
27 match self {
28 Self::Xml => " -->",
29 _ => "",
30 }
31 }
32}
33
34pub fn render_prelude(style: CommentStyle, input_basename: &str) -> String {
38 let version = env!("CARGO_PKG_VERSION");
39 let o = style.open();
40 let c = style.close();
41 format!(
42 "{o}Generated by WeaveFFI {version} from {input_basename}{c}\n\
43 {o}DO NOT EDIT — your changes will be overwritten.{c}\n\
44 {o}To regenerate: weaveffi generate {input_basename} -o <out>{c}\n\n"
45 )
46}
47
48pub fn render_trailer(style: CommentStyle, filename: &str) -> String {
51 let o = style.open();
52 let c = style.close();
53 format!("{o}END {filename}{c}\n")
54}
55
56pub fn render_json_prelude(input_basename: &str) -> String {
61 let version = env!("CARGO_PKG_VERSION");
62 format!(
63 " \"//\": \"Generated by WeaveFFI {version} from {input_basename}\",\n \
64 \"//warning\": \"DO NOT EDIT — your changes will be overwritten.\",\n \
65 \"//regenerate\": \"To regenerate: weaveffi generate {input_basename} -o <out>\",\n"
66 )
67}
68
69pub const ABI_RUNTIME_SYMBOLS: &[&str] = &[
77 "error",
78 "handle_t",
79 "error_set",
80 "error_clear",
81 "free_string",
82 "free_bytes",
83 "arena_create",
84 "arena_destroy",
85 "arena_register",
86 "cancel_token",
87 "cancel_token_create",
88 "cancel_token_cancel",
89 "cancel_token_is_cancelled",
90 "cancel_token_destroy",
91];
92
93pub fn render_abi_prefix_aliases(prefix: &str) -> String {
96 if prefix == "weaveffi" {
97 return String::new();
98 }
99 let mut out = String::new();
100 out.push_str("/* Aliases for weaveffi-abi runtime symbols */\n");
101 for sym in ABI_RUNTIME_SYMBOLS {
102 out.push_str(&format!("#define {prefix}_{sym} weaveffi_{sym}\n"));
103 }
104 out.push('\n');
105 out
106}
107
108pub fn wrapper_name(module: &str, func: &str, strip_module_prefix: bool) -> String {
113 if strip_module_prefix {
114 func.to_string()
115 } else {
116 format!("{module}_{func}")
117 }
118}
119
120pub fn local_type_name(name: &str) -> &str {
128 name.rsplit_once('.').map_or(name, |(_, local)| local)
129}
130
131pub fn c_abi_struct_name(name: &str, current_module: &str, prefix: &str) -> String {
141 if let Some((module_path, type_name)) = name.rsplit_once('.') {
142 let module_path = module_path.replace('.', "_");
143 format!("{prefix}_{module_path}_{type_name}")
144 } else {
145 format!("{prefix}_{current_module}_{name}")
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn local_type_name_unqualified() {
155 assert_eq!(local_type_name("Contact"), "Contact");
156 }
157
158 #[test]
159 fn local_type_name_qualified() {
160 assert_eq!(local_type_name("other.Contact"), "Contact");
161 }
162
163 #[test]
164 fn local_type_name_multi_level() {
165 assert_eq!(local_type_name("a.b.Widget"), "Widget");
166 }
167
168 #[test]
169 fn c_abi_struct_name_unqualified() {
170 assert_eq!(
171 c_abi_struct_name("Contact", "math", "weaveffi"),
172 "weaveffi_math_Contact"
173 );
174 }
175
176 #[test]
177 fn c_abi_struct_name_qualified() {
178 assert_eq!(
179 c_abi_struct_name("types.Name", "ops", "weaveffi"),
180 "weaveffi_types_Name"
181 );
182 }
183
184 #[test]
185 fn c_abi_struct_name_multi_level_flattens_path() {
186 assert_eq!(
187 c_abi_struct_name("a.b.Widget", "ops", "weaveffi"),
188 "weaveffi_a_b_Widget"
189 );
190 }
191
192 #[test]
193 fn abi_prefix_aliases_default_is_empty() {
194 assert!(render_abi_prefix_aliases("weaveffi").is_empty());
195 }
196
197 #[test]
198 fn abi_prefix_aliases_custom_lists_every_symbol() {
199 let out = render_abi_prefix_aliases("myffi");
200 for sym in ABI_RUNTIME_SYMBOLS {
201 let line = format!("#define myffi_{sym} weaveffi_{sym}");
202 assert!(out.contains(&line), "missing alias `{line}` in:\n{out}");
203 }
204 }
205
206 #[test]
207 fn prelude_double_slash_carries_required_phrases() {
208 let p = render_prelude(CommentStyle::DoubleSlash, "calc.yml");
209 assert!(p.starts_with("// Generated by WeaveFFI "));
210 assert!(p.contains(" from calc.yml\n"));
211 assert!(p.contains("// DO NOT EDIT"));
212 assert!(p.contains("// To regenerate: weaveffi generate calc.yml -o <out>"));
213 assert!(p.ends_with("\n\n"));
214 }
215
216 #[test]
217 fn prelude_hash_uses_hash_marker() {
218 let p = render_prelude(CommentStyle::Hash, "calc.yml");
219 assert!(p.starts_with("# Generated by WeaveFFI "));
220 assert!(p.contains("# DO NOT EDIT"));
221 assert!(p.contains("# To regenerate: weaveffi generate calc.yml -o <out>"));
222 }
223
224 #[test]
225 fn prelude_xml_wraps_lines_in_brackets() {
226 let p = render_prelude(CommentStyle::Xml, "calc.yml");
227 assert!(p.starts_with("<!-- Generated by WeaveFFI "));
228 assert!(p.contains("from calc.yml -->"));
229 assert!(p.contains("<!-- DO NOT EDIT"));
230 assert!(p.contains("your changes will be overwritten. -->"));
231 }
232
233 #[test]
234 fn trailer_has_correct_marker_per_style() {
235 assert_eq!(
236 render_trailer(CommentStyle::DoubleSlash, "lib.rs"),
237 "// END lib.rs\n"
238 );
239 assert_eq!(
240 render_trailer(CommentStyle::Hash, "build.toml"),
241 "# END build.toml\n"
242 );
243 assert_eq!(
244 render_trailer(CommentStyle::Xml, "package.csproj"),
245 "<!-- END package.csproj -->\n"
246 );
247 }
248
249 #[test]
250 fn json_prelude_contains_required_phrases_in_first_lines() {
251 let p = render_json_prelude("calc.yml");
252 let lines: Vec<&str> = p.lines().collect();
253 assert!(lines[0].contains("Generated by WeaveFFI"));
254 assert!(lines[0].contains("from calc.yml"));
255 assert!(lines[1].contains("DO NOT EDIT"));
256 assert!(lines[2].contains("To regenerate"));
257 for line in &lines {
258 assert!(line.starts_with(" "));
259 assert!(line.ends_with(','));
260 }
261 }
262}