rusteal_codegen/
naming.rs1pub fn to_snake_case(name: &str) -> String {
2 let mut result = String::with_capacity(name.len() + 8);
3 let chars: Vec<char> = name.chars().collect();
4
5 for (i, &ch) in chars.iter().enumerate() {
6 if ch.is_ascii_uppercase() {
7 if i > 0 {
8 let prev = chars[i - 1];
9
10 let new_word = prev.is_ascii_lowercase()
11 || prev.is_ascii_digit()
12 || (prev.is_ascii_uppercase()
13 && i + 1 < chars.len()
14 && chars[i + 1].is_ascii_lowercase());
15
16 if new_word {
17 result.push('_');
18 }
19 }
20
21 result.push(ch.to_ascii_lowercase());
22 } else {
23 result.push(ch);
24 }
25 }
26
27 result
28}
29
30pub fn strip_bool_prefix(name: &str) -> String {
31 if name.starts_with('b') && name.len() > 1 && name.as_bytes()[1].is_ascii_uppercase() {
32 to_snake_case(&name[1..])
33 } else {
34 to_snake_case(name)
35 }
36}
37
38const RESERVED_WORDS: &[&str] = &[
39 "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for",
40 "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return",
41 "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where",
42 "while", "async", "await", "dyn", "abstract", "become", "box", "do", "final", "macro",
43 "override", "priv", "typeof", "unsized", "virtual", "yield", "try",
44];
45
46pub fn is_reserved(name: &str) -> bool {
47 RESERVED_WORDS.contains(&name)
48}
49
50pub fn escape_reserved(name: &str) -> String {
51 match name {
52 "Self" | "self" | "super" | "crate" => format!("{name}_"),
53 _ if is_reserved(name) => format!("r#{name}"),
54 _ => name.to_string(),
55 }
56}
57
58#[allow(dead_code)]
59pub fn strip_ue_prefix(cpp_name: &str) -> &str {
60 if cpp_name.len() > 1 {
61 let first = cpp_name.as_bytes()[0];
62 let second = cpp_name.as_bytes()[1];
63
64 if (first == b'A' || first == b'U') && second.is_ascii_uppercase() {
65 return &cpp_name[1..];
66 }
67 }
68
69 cpp_name
70}
71
72#[allow(dead_code)]
73pub fn strip_struct_prefix(cpp_name: &str) -> &str {
74 if cpp_name.len() > 1
75 && cpp_name.as_bytes()[0] == b'F'
76 && cpp_name.as_bytes()[1].is_ascii_uppercase()
77 {
78 &cpp_name[1..]
79 } else {
80 cpp_name
81 }
82}
83
84#[allow(dead_code)]
85pub fn to_module_name(name: &str) -> String {
86 let snake = to_snake_case(name);
87 escape_reserved(&snake)
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_to_snake_case() {
96 assert_eq!(to_snake_case("AActor"), "a_actor");
97 assert_eq!(to_snake_case("GetObjectCount"), "get_object_count");
98 assert_eq!(to_snake_case("bNetTemporary"), "b_net_temporary");
99 assert_eq!(to_snake_case("HTTPServer"), "http_server");
100 assert_eq!(to_snake_case("FVector"), "f_vector");
101 assert_eq!(to_snake_case("ECollisionChannel"), "e_collision_channel");
102 assert_eq!(to_snake_case("URL"), "url");
103
104 assert_eq!(
105 to_snake_case("K2_GetActorLocation"),
106 "k2_get_actor_location"
107 );
108 }
109
110 #[test]
111 fn test_strip_bool_prefix() {
112 assert_eq!(strip_bool_prefix("bNetTemporary"), "net_temporary");
113 assert_eq!(strip_bool_prefix("bHidden"), "hidden");
114 assert_eq!(strip_bool_prefix("boolean"), "boolean");
115 }
116
117 #[test]
118 fn test_escape_reserved() {
119 assert_eq!(escape_reserved("Self"), "Self_");
120 assert_eq!(escape_reserved("crate"), "crate_");
121 assert_eq!(escape_reserved("type"), "r#type");
122 assert_eq!(escape_reserved("r#move"), "r#move");
123 assert_eq!(escape_reserved("move"), "r#move");
124 assert_eq!(escape_reserved("actor"), "actor");
125 }
126
127 #[test]
128 fn test_strip_ue_prefix() {
129 assert_eq!(strip_ue_prefix("AActor"), "Actor");
130 assert_eq!(strip_ue_prefix("UObject"), "Object");
131 assert_eq!(strip_ue_prefix("FVector"), "FVector");
132 }
133}