pub fn c_symbol_name(module: &str, func: &str) -> String {
format!("weaveffi_{}_{}", module, func)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommentStyle {
DoubleSlash,
Hash,
Xml,
}
impl CommentStyle {
fn open(self) -> &'static str {
match self {
Self::DoubleSlash => "// ",
Self::Hash => "# ",
Self::Xml => "<!-- ",
}
}
fn close(self) -> &'static str {
match self {
Self::Xml => " -->",
_ => "",
}
}
}
pub fn render_prelude(style: CommentStyle, input_basename: &str) -> String {
let version = env!("CARGO_PKG_VERSION");
let o = style.open();
let c = style.close();
format!(
"{o}Generated by WeaveFFI {version} from {input_basename}{c}\n\
{o}DO NOT EDIT — your changes will be overwritten.{c}\n\
{o}To regenerate: weaveffi generate {input_basename} -o <out>{c}\n\n"
)
}
pub fn render_trailer(style: CommentStyle, filename: &str) -> String {
let o = style.open();
let c = style.close();
format!("{o}END {filename}{c}\n")
}
pub fn render_json_prelude(input_basename: &str) -> String {
let version = env!("CARGO_PKG_VERSION");
format!(
" \"//\": \"Generated by WeaveFFI {version} from {input_basename}\",\n \
\"//warning\": \"DO NOT EDIT — your changes will be overwritten.\",\n \
\"//regenerate\": \"To regenerate: weaveffi generate {input_basename} -o <out>\",\n"
)
}
pub const ABI_RUNTIME_SYMBOLS: &[&str] = &[
"error",
"handle_t",
"error_set",
"error_clear",
"free_string",
"free_bytes",
"arena_create",
"arena_destroy",
"arena_register",
"cancel_token",
"cancel_token_create",
"cancel_token_cancel",
"cancel_token_is_cancelled",
"cancel_token_destroy",
];
pub fn render_abi_prefix_aliases(prefix: &str) -> String {
if prefix == "weaveffi" {
return String::new();
}
let mut out = String::new();
out.push_str("/* Aliases for weaveffi-abi runtime symbols */\n");
for sym in ABI_RUNTIME_SYMBOLS {
out.push_str(&format!("#define {prefix}_{sym} weaveffi_{sym}\n"));
}
out.push('\n');
out
}
pub fn wrapper_name(module: &str, func: &str, strip_module_prefix: bool) -> String {
if strip_module_prefix {
func.to_string()
} else {
format!("{module}_{func}")
}
}
pub fn local_type_name(name: &str) -> &str {
name.split_once('.').map_or(name, |(_, local)| local)
}
pub fn c_abi_struct_name(name: &str, current_module: &str, prefix: &str) -> String {
if let Some((mod_name, type_name)) = name.split_once('.') {
format!("{prefix}_{mod_name}_{type_name}")
} else {
format!("{prefix}_{current_module}_{name}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn local_type_name_unqualified() {
assert_eq!(local_type_name("Contact"), "Contact");
}
#[test]
fn local_type_name_qualified() {
assert_eq!(local_type_name("other.Contact"), "Contact");
}
#[test]
fn c_abi_struct_name_unqualified() {
assert_eq!(
c_abi_struct_name("Contact", "math", "weaveffi"),
"weaveffi_math_Contact"
);
}
#[test]
fn c_abi_struct_name_qualified() {
assert_eq!(
c_abi_struct_name("types.Name", "ops", "weaveffi"),
"weaveffi_types_Name"
);
}
#[test]
fn abi_prefix_aliases_default_is_empty() {
assert!(render_abi_prefix_aliases("weaveffi").is_empty());
}
#[test]
fn abi_prefix_aliases_custom_lists_every_symbol() {
let out = render_abi_prefix_aliases("myffi");
for sym in ABI_RUNTIME_SYMBOLS {
let line = format!("#define myffi_{sym} weaveffi_{sym}");
assert!(out.contains(&line), "missing alias `{line}` in:\n{out}");
}
}
#[test]
fn prelude_double_slash_carries_required_phrases() {
let p = render_prelude(CommentStyle::DoubleSlash, "calc.yml");
assert!(p.starts_with("// Generated by WeaveFFI "));
assert!(p.contains(" from calc.yml\n"));
assert!(p.contains("// DO NOT EDIT"));
assert!(p.contains("// To regenerate: weaveffi generate calc.yml -o <out>"));
assert!(p.ends_with("\n\n"));
}
#[test]
fn prelude_hash_uses_hash_marker() {
let p = render_prelude(CommentStyle::Hash, "calc.yml");
assert!(p.starts_with("# Generated by WeaveFFI "));
assert!(p.contains("# DO NOT EDIT"));
assert!(p.contains("# To regenerate: weaveffi generate calc.yml -o <out>"));
}
#[test]
fn prelude_xml_wraps_lines_in_brackets() {
let p = render_prelude(CommentStyle::Xml, "calc.yml");
assert!(p.starts_with("<!-- Generated by WeaveFFI "));
assert!(p.contains("from calc.yml -->"));
assert!(p.contains("<!-- DO NOT EDIT"));
assert!(p.contains("your changes will be overwritten. -->"));
}
#[test]
fn trailer_has_correct_marker_per_style() {
assert_eq!(
render_trailer(CommentStyle::DoubleSlash, "lib.rs"),
"// END lib.rs\n"
);
assert_eq!(
render_trailer(CommentStyle::Hash, "build.toml"),
"# END build.toml\n"
);
assert_eq!(
render_trailer(CommentStyle::Xml, "package.csproj"),
"<!-- END package.csproj -->\n"
);
}
#[test]
fn json_prelude_contains_required_phrases_in_first_lines() {
let p = render_json_prelude("calc.yml");
let lines: Vec<&str> = p.lines().collect();
assert!(lines[0].contains("Generated by WeaveFFI"));
assert!(lines[0].contains("from calc.yml"));
assert!(lines[1].contains("DO NOT EDIT"));
assert!(lines[2].contains("To regenerate"));
for line in &lines {
assert!(line.starts_with(" "));
assert!(line.ends_with(','));
}
}
}