1use std::{
2 fmt::Display,
3 ops::Deref,
4 sync::{Arc, OnceLock},
5};
6
7use faststr::FastStr;
8use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
9use phf::phf_set;
10
11crate::newtype_index! {
12 pub struct FileId { .. }
13}
14
15crate::newtype_index! {
16 pub struct DefId { .. }
17}
18
19pub static SPECIAL_NAMINGS: OnceLock<Vec<FastStr>> = OnceLock::new();
20
21static KEYWORDS_SET: phf::Set<&'static str> = phf_set![
22 "as", "use", "break", "const", "continue", "crate", "else", "if", "enum", "extern", "false",
23 "fn", "for", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
24 "return", "Self", "self", "static", "struct", "super", "trait", "true", "type", "unsafe",
25 "where", "while", "abstract", "alignof", "become", "box", "do", "final", "macro", "offsetof",
26 "override", "priv", "proc", "pure", "sizeof", "typeof", "unsized", "virtual", "yield", "dyn",
27 "async", "await", "try", "gen"
28];
29
30#[derive(Hash, PartialEq, Eq, Clone, Debug, PartialOrd, Ord)]
31pub struct Symbol(pub FastStr);
32
33impl std::borrow::Borrow<str> for Symbol {
34 fn borrow(&self) -> &str {
35 self
36 }
37}
38
39impl Deref for Symbol {
40 type Target = str;
41
42 fn deref(&self) -> &Self::Target {
43 &self.0
44 }
45}
46
47impl<T> From<T> for Symbol
48where
49 T: Into<FastStr>,
50{
51 fn from(t: T) -> Self {
52 Symbol(t.into())
53 }
54}
55
56impl std::fmt::Display for Symbol {
57 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
58 if self.is_path_segment_keyword() {
59 return write!(f, "{}_", &**self);
60 }
61 if KEYWORDS_SET.contains(self) {
62 write!(f, "r#{}", &**self)
63 } else {
64 write!(f, "{}", &**self)
65 }
66 }
67}
68
69impl Symbol {
70 fn is_path_segment_keyword(&self) -> bool {
72 ["super", "self", "Self", "crate"].contains(&&**self)
73 }
74}
75
76#[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)]
77pub enum EnumRepr {
78 I32,
79}
80
81#[derive(Hash, PartialEq, Eq, Clone, Debug)]
82pub struct Ident {
83 pub sym: Symbol,
84}
85
86impl Ident {
87 pub fn new(sym: Symbol) -> Self {
88 Ident { sym }
89 }
90
91 pub fn raw_str(&self) -> FastStr {
92 self.sym.0.clone()
93 }
94}
95
96impl Deref for Ident {
97 type Target = Symbol;
98
99 fn deref(&self) -> &Self::Target {
100 &self.sym
101 }
102}
103
104impl Display for Ident {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 std::fmt::Display::fmt(&self.sym, f)
107 }
108}
109
110impl<T> From<T> for Ident
111where
112 T: Into<FastStr>,
113{
114 fn from(t: T) -> Self {
115 Ident {
116 sym: Symbol(t.into()),
117 }
118 }
119}
120
121#[derive(Hash, PartialEq, Eq, Clone, Debug, Ord, PartialOrd)]
122pub struct ModPath(Arc<[FastStr]>);
123
124impl Deref for ModPath {
125 type Target = [FastStr];
126
127 fn deref(&self) -> &Self::Target {
128 &self.0
129 }
130}
131
132impl<T> From<T> for ModPath
133where
134 T: Into<Arc<[FastStr]>>,
135{
136 fn from(t: T) -> Self {
137 ModPath(t.into())
138 }
139}
140pub trait IdentName {
141 fn struct_ident(&self) -> FastStr {
142 self.upper_camel_ident()
143 }
144
145 fn enum_ident(&self) -> FastStr {
146 self.upper_camel_ident()
147 }
148
149 fn mod_ident(&self) -> FastStr {
150 self.snake_ident()
151 }
152
153 fn variant_ident(&self) -> FastStr {
154 self.upper_camel_ident()
155 }
156 fn fn_ident(&self) -> FastStr {
157 self.snake_ident()
158 }
159 fn field_ident(&self) -> FastStr {
160 self.snake_ident()
161 }
162 fn const_ident(&self) -> FastStr {
163 self.shouty_snake_case()
164 }
165
166 fn trait_ident(&self) -> FastStr {
167 self.upper_camel_ident()
168 }
169
170 fn newtype_ident(&self) -> FastStr {
171 self.upper_camel_ident()
172 }
173
174 fn upper_camel_ident(&self) -> FastStr;
175 fn snake_ident(&self) -> FastStr;
176 fn shouty_snake_case(&self) -> FastStr;
177}
178
179impl IdentName for &str {
180 fn upper_camel_ident(&self) -> FastStr {
181 if let Some(index) = self.find(|c: char| c != '_') {
182 let s = self[index..].to_upper_camel_case();
183 return format!("{}{}", &self[0..index], s).into();
184 }
185 self.to_string().into()
186 }
187
188 fn snake_ident(&self) -> FastStr {
189 if let Some(index) = self.find(|c: char| c != '_') {
190 let s = &self[index..];
191 let s = if is_common_initialism(s) {
192 to_snake_case(s)
193 } else {
194 s.to_snake_case()
195 };
196 return format!("{}{}", &self[0..index], s).into();
197 }
198 self.to_string().into()
199 }
200
201 fn shouty_snake_case(&self) -> FastStr {
202 if let Some(index) = self.find(|c: char| c != '_') {
203 let s = &self[index..];
204 let s = if is_common_initialism(s) {
205 to_snake_case(s).to_uppercase()
206 } else {
207 s.to_shouty_snake_case()
208 };
209 return format!("{}{}", &self[0..index], s).into();
210 }
211 self.to_string().into()
212 }
213}
214
215impl IdentName for FastStr {
216 fn upper_camel_ident(&self) -> FastStr {
217 (&**self).upper_camel_ident()
218 }
219
220 fn snake_ident(&self) -> FastStr {
221 (&**self).snake_ident()
222 }
223
224 fn shouty_snake_case(&self) -> FastStr {
225 (&**self).shouty_snake_case()
226 }
227}
228
229fn to_snake_case(mut str: &str) -> String {
231 let mut words = vec![];
232 str = str.trim_start_matches(|c: char| {
234 if c == '_' {
235 words.push(String::new());
236 true
237 } else {
238 false
239 }
240 });
241 for s in str.split('_') {
242 let mut last_upper = false;
243 let mut buf = String::new();
244 if s.is_empty() {
245 continue;
246 }
247 for ch in s.chars() {
248 if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
249 words.push(buf);
250 buf = String::new();
251 }
252 last_upper = ch.is_uppercase();
253 buf.extend(ch.to_lowercase());
254 }
255 words.push(buf);
256 }
257 words.join("_")
258}
259
260fn is_common_initialism(s: &str) -> bool {
261 for name in SPECIAL_NAMINGS.get().unwrap_or(&Default::default()).iter() {
262 if s.contains(name.as_str()) {
263 return true;
264 }
265 }
266 false
267}
268
269#[cfg(test)]
270mod tests {
271 use heck::ToSnakeCase;
272
273 use crate::symbol::to_snake_case;
274
275 #[test]
276 fn snake_case() {
277 assert_eq!("IDs".to_snake_case(), "i_ds");
278 assert_eq!(to_snake_case("IDs"), "ids");
280
281 assert_eq!("UIDSecure".to_snake_case(), "uid_secure");
282 assert_eq!(to_snake_case("UIDSecure"), "uidsecure");
284 }
285}