use super::case::pascal_to_snake;
use heck::{ToPascalCase, ToSnakeCase};
pub fn internal_rust_identifier(name: &str) -> String {
crate::core::keywords::rust_raw_ident(name)
}
pub fn abi_symbol(prefix: &str, name: &str) -> String {
to_c_name(prefix, name)
}
pub fn to_c_name(prefix: &str, name: &str) -> String {
format!("{}_{}", prefix, name.to_snake_case())
}
pub fn abi_symbol_from_components<I, S>(components: I) -> String
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut parts = components
.into_iter()
.enumerate()
.map(|(idx, component)| {
let sanitized = sanitize_symbol_component(component.as_ref());
if idx == 0 {
sanitized
} else {
sanitized.trim_start_matches('_').to_string()
}
})
.filter(|component| !component.is_empty())
.collect::<Vec<_>>();
let symbol = parts.join("_");
if symbol.chars().next().is_some_and(|ch| ch.is_ascii_digit()) {
parts.insert(0, "_".to_string());
parts.join("_")
} else {
symbol
}
}
pub(super) fn sanitize_symbol_component(name: &str) -> String {
let mut sanitized = String::with_capacity(name.len() + 1);
for ch in name.chars() {
if ch.is_ascii_alphanumeric() || ch == '_' {
sanitized.push(ch.to_ascii_lowercase());
} else {
sanitized.push('_');
}
}
while sanitized.contains("__") {
sanitized = sanitized.replace("__", "_");
}
let sanitized = sanitized.trim_matches('_').to_string();
if sanitized.chars().next().is_some_and(|ch| ch.is_ascii_digit()) {
format!("_{sanitized}")
} else {
sanitized
}
}
fn identifier_words(name: &str) -> Vec<String> {
name.split(|character: char| !character.is_ascii_alphanumeric())
.filter(|chunk| !chunk.is_empty())
.flat_map(|chunk| {
pascal_to_snake(chunk)
.split('_')
.filter(|word| !word.is_empty())
.map(str::to_string)
.collect::<Vec<_>>()
})
.collect()
}
fn collapse_repeated_words(words: Vec<String>) -> Vec<String> {
let mut collapsed: Vec<String> = Vec::with_capacity(words.len());
for word in words {
if collapsed
.last()
.is_some_and(|previous| previous.eq_ignore_ascii_case(&word))
{
continue;
}
collapsed.push(word);
}
collapsed
}
pub fn ffi_builtin_error_code_prefix(abi_prefix: &str) -> String {
format!("{abi_prefix}_alef").to_pascal_case()
}
pub fn ffi_error_code_variant_name(error_type: &str, variant: &str) -> String {
let path_words = collapse_repeated_words(identifier_words(error_type));
let mut variant_words = identifier_words(variant);
if path_words.last().is_some_and(|word| word == "error")
&& variant_words.first().is_some_and(|word| word == "error")
&& variant_words.len() > 1
{
variant_words.remove(0);
}
path_words
.into_iter()
.chain(variant_words)
.collect::<Vec<_>>()
.join("_")
.to_pascal_case()
}