pub(crate) fn module_name(name: &str) -> String {
split_identifier_words(name).join("_")
}
pub(crate) fn rust_type_name(name: &str) -> String {
split_identifier_words(name)
.into_iter()
.map(|part| {
let mut chars = part.chars();
chars.next().map_or_else(String::new, |first| {
let mut out = String::with_capacity(part.len());
out.push(first.to_ascii_uppercase());
out.push_str(chars.as_str());
out
})
})
.collect()
}
pub(crate) fn rust_function_name(name: &str) -> String {
let words = split_identifier_words(name);
let ident = if words.is_empty() {
"rule".to_owned()
} else {
words.join("_")
};
let ident = sanitize_identifier(&ident);
if is_rust_keyword(&ident) {
format!("r#{ident}")
} else {
ident
}
}
pub(crate) fn rust_string(value: &str) -> String {
value.escape_default().to_string()
}
pub(crate) fn split_identifier_words(name: &str) -> Vec<String> {
let mut words = Vec::new();
let mut current = String::new();
let chars: Vec<char> = name.chars().collect();
for (index, ch) in chars.iter().copied().enumerate() {
if !ch.is_ascii_alphanumeric() {
if !current.is_empty() {
words.push(ascii_lowercase(¤t));
current.clear();
}
continue;
}
let previous = index.checked_sub(1).and_then(|i| chars.get(i)).copied();
let next = chars.get(index + 1).copied();
let starts_new_word = !current.is_empty()
&& ch.is_ascii_uppercase()
&& (previous.is_some_and(|prev| prev.is_ascii_lowercase() || prev.is_ascii_digit())
|| (previous.is_some_and(|prev| prev.is_ascii_uppercase())
&& next.is_some_and(|next| next.is_ascii_lowercase())));
if starts_new_word {
words.push(ascii_lowercase(¤t));
current.clear();
}
current.push(ch);
}
if !current.is_empty() {
words.push(ascii_lowercase(¤t));
}
words
}
pub(crate) fn sanitize_identifier(value: &str) -> String {
let mut out = String::new();
for (index, ch) in value.chars().enumerate() {
if ch == '_' || ch.is_ascii_alphanumeric() {
if index == 0 && ch.is_ascii_digit() {
out.push('_');
}
out.push(ch);
} else {
out.push('_');
}
}
if out.is_empty() { "_".to_owned() } else { out }
}
pub(crate) fn is_rust_keyword(value: &str) -> bool {
matches!(
value,
"as" | "async"
| "await"
| "break"
| "const"
| "continue"
| "crate"
| "dyn"
| "else"
| "enum"
| "extern"
| "false"
| "fn"
| "for"
| "gen"
| "if"
| "impl"
| "in"
| "let"
| "loop"
| "match"
| "mod"
| "move"
| "mut"
| "pub"
| "ref"
| "return"
| "Self"
| "self"
| "static"
| "struct"
| "super"
| "trait"
| "true"
| "type"
| "unsafe"
| "use"
| "where"
| "while"
| "abstract"
| "become"
| "box"
| "do"
| "final"
| "macro"
| "override"
| "priv"
| "try"
| "typeof"
| "unsized"
| "virtual"
| "yield"
)
}
fn ascii_lowercase(value: &str) -> String {
value.chars().map(|ch| ch.to_ascii_lowercase()).collect()
}