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 {
124 name.split_once('.').map_or(name, |(_, local)| local)
125}
126
127pub fn c_abi_struct_name(name: &str, current_module: &str, prefix: &str) -> String {
132 if let Some((mod_name, type_name)) = name.split_once('.') {
133 format!("{prefix}_{mod_name}_{type_name}")
134 } else {
135 format!("{prefix}_{current_module}_{name}")
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn local_type_name_unqualified() {
145 assert_eq!(local_type_name("Contact"), "Contact");
146 }
147
148 #[test]
149 fn local_type_name_qualified() {
150 assert_eq!(local_type_name("other.Contact"), "Contact");
151 }
152
153 #[test]
154 fn c_abi_struct_name_unqualified() {
155 assert_eq!(
156 c_abi_struct_name("Contact", "math", "weaveffi"),
157 "weaveffi_math_Contact"
158 );
159 }
160
161 #[test]
162 fn c_abi_struct_name_qualified() {
163 assert_eq!(
164 c_abi_struct_name("types.Name", "ops", "weaveffi"),
165 "weaveffi_types_Name"
166 );
167 }
168
169 #[test]
170 fn abi_prefix_aliases_default_is_empty() {
171 assert!(render_abi_prefix_aliases("weaveffi").is_empty());
172 }
173
174 #[test]
175 fn abi_prefix_aliases_custom_lists_every_symbol() {
176 let out = render_abi_prefix_aliases("myffi");
177 for sym in ABI_RUNTIME_SYMBOLS {
178 let line = format!("#define myffi_{sym} weaveffi_{sym}");
179 assert!(out.contains(&line), "missing alias `{line}` in:\n{out}");
180 }
181 }
182
183 #[test]
184 fn prelude_double_slash_carries_required_phrases() {
185 let p = render_prelude(CommentStyle::DoubleSlash, "calc.yml");
186 assert!(p.starts_with("// Generated by WeaveFFI "));
187 assert!(p.contains(" from calc.yml\n"));
188 assert!(p.contains("// DO NOT EDIT"));
189 assert!(p.contains("// To regenerate: weaveffi generate calc.yml -o <out>"));
190 assert!(p.ends_with("\n\n"));
191 }
192
193 #[test]
194 fn prelude_hash_uses_hash_marker() {
195 let p = render_prelude(CommentStyle::Hash, "calc.yml");
196 assert!(p.starts_with("# Generated by WeaveFFI "));
197 assert!(p.contains("# DO NOT EDIT"));
198 assert!(p.contains("# To regenerate: weaveffi generate calc.yml -o <out>"));
199 }
200
201 #[test]
202 fn prelude_xml_wraps_lines_in_brackets() {
203 let p = render_prelude(CommentStyle::Xml, "calc.yml");
204 assert!(p.starts_with("<!-- Generated by WeaveFFI "));
205 assert!(p.contains("from calc.yml -->"));
206 assert!(p.contains("<!-- DO NOT EDIT"));
207 assert!(p.contains("your changes will be overwritten. -->"));
208 }
209
210 #[test]
211 fn trailer_has_correct_marker_per_style() {
212 assert_eq!(
213 render_trailer(CommentStyle::DoubleSlash, "lib.rs"),
214 "// END lib.rs\n"
215 );
216 assert_eq!(
217 render_trailer(CommentStyle::Hash, "build.toml"),
218 "# END build.toml\n"
219 );
220 assert_eq!(
221 render_trailer(CommentStyle::Xml, "package.csproj"),
222 "<!-- END package.csproj -->\n"
223 );
224 }
225
226 #[test]
227 fn json_prelude_contains_required_phrases_in_first_lines() {
228 let p = render_json_prelude("calc.yml");
229 let lines: Vec<&str> = p.lines().collect();
230 assert!(lines[0].contains("Generated by WeaveFFI"));
231 assert!(lines[0].contains("from calc.yml"));
232 assert!(lines[1].contains("DO NOT EDIT"));
233 assert!(lines[2].contains("To regenerate"));
234 for line in &lines {
235 assert!(line.starts_with(" "));
236 assert!(line.ends_with(','));
237 }
238 }
239}