const GLEAM_RESERVED: &[&str] = &[
"as",
"assert",
"auto",
"case",
"const",
"delegate",
"derive",
"echo",
"else",
"fn",
"if",
"implement",
"import",
"let",
"macro",
"opaque",
"panic",
"pub",
"test",
"todo",
"type",
"use",
];
pub(crate) fn is_snake_identifier(text: &str) -> bool {
let mut chars = text.chars();
let Some(first) = chars.next() else {
return false;
};
first.is_ascii_lowercase()
&& chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
}
pub(crate) fn is_reserved_word(text: &str) -> bool {
GLEAM_RESERVED.contains(&text)
}
pub(crate) fn pascal_to_snake(name: &str) -> String {
let mut out = String::with_capacity(name.len() + 4);
for (index, ch) in name.char_indices() {
if ch.is_ascii_uppercase() {
if index != 0 {
out.push('_');
}
out.push(ch.to_ascii_lowercase());
} else {
out.push(ch);
}
}
out
}
#[cfg(test)]
mod tests {
use super::{is_reserved_word, is_snake_identifier, pascal_to_snake};
#[test]
fn snake_identifiers_are_classified() {
for valid in ["a", "repo_root", "round_backoff_ms", "a1", "x_2_y"] {
assert!(is_snake_identifier(valid), "{valid} should be valid");
}
for invalid in ["", "Repo", "1a", "a-b", "_a", "a b", "naïve"] {
assert!(!is_snake_identifier(invalid), "{invalid} should be invalid");
}
}
#[test]
fn reserved_words_are_recognised() {
assert!(is_reserved_word("type"));
assert!(is_reserved_word("use"));
assert!(!is_reserved_word("kind"));
}
#[test]
fn pascal_to_snake_is_deterministic_and_shape_true() {
assert_eq!(pascal_to_snake("OrderInput"), "order_input");
assert_eq!(pascal_to_snake("Shipment"), "shipment");
assert_eq!(
pascal_to_snake("GateInputWorkspace"),
"gate_input_workspace"
);
assert_eq!(pascal_to_snake("ClosedOut"), "closed_out");
assert_eq!(pascal_to_snake("Vm"), "vm");
assert_eq!(pascal_to_snake("A1B"), "a1_b");
}
}