microcad_lang_base/
identifier.rs1use compact_str::{CompactString, ToCompactString};
5use derive_more::Deref;
6use miette::SourceSpan;
7
8use crate::{Id, Refer, SrcRef, SrcReferrer, TreeDisplay, TreeState};
9
10#[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
12pub struct Identifier(pub Refer<Id>);
13
14impl SrcReferrer for Identifier {
15 fn src_ref(&self) -> SrcRef {
16 self.0.src_ref.clone()
17 }
18}
19
20impl Identifier {
21 pub fn none() -> Self {
23 Self(Refer::none("".into()))
24 }
25
26 pub fn unique() -> Self {
30 let mut num = UNIQUE_ID_NEXT
31 .lock()
32 .expect("lock on UNIQUE_ID_NEXT failed");
33 let id = format!("${num}");
34 *num += 1;
35 Identifier::no_ref(&id)
36 }
37
38 pub fn ignore(&self) -> bool {
40 self.0.starts_with("_")
41 }
42
43 pub fn is_super(&self) -> bool {
45 *self.0 == "super"
46 }
47
48 pub fn no_ref(id: &str) -> Self {
50 Self(Refer::none(id.into()))
51 }
52
53 pub fn id(&self) -> &Id {
55 &self.0.value
56 }
57
58 pub fn short_id(&self) -> ShortId {
60 let parts = self
61 .0
62 .value
63 .split("_")
64 .map(|part| {
65 part.chars()
66 .next()
67 .expect("cannot shorten empty Identifier")
68 })
69 .map(|p| p.to_compact_string())
70 .collect::<Vec<_>>()
71 .join("_")
72 .to_compact_string();
73
74 ShortId(parts)
75 }
76
77 pub fn len(&self) -> usize {
79 self.0.len()
80 }
81
82 pub fn is_empty(&self) -> bool {
84 self.0.is_empty()
85 }
86
87 pub fn is_valid(&self) -> bool {
89 let str = self.0.as_str();
90
91 let Some(start) = str.chars().next() else {
93 return false;
94 };
95
96 (start == '_' || start.is_ascii_alphabetic())
97 && str.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
98 }
99
100 pub fn detect_case(&self) -> Case {
102 let s = &self.0.value;
103
104 if s.is_empty() {
105 return Case::Invalid;
106 }
107
108 if s.len() == 1 {
109 let c = s.chars().next().expect("At least one char");
110 if c.is_ascii_uppercase() {
111 return Case::UpperSingleChar;
112 } else {
113 return Case::Invalid;
114 }
115 }
116
117 let has_underscore = s.contains('_');
118
119 if has_underscore {
120 if s.chars().all(|c| c.is_ascii_uppercase() || c == '_') {
121 return Case::UpperSnake;
122 } else if s.chars().all(|c| c.is_ascii_lowercase() || c == '_') {
123 return Case::LowerSnake;
124 } else {
125 return Case::Invalid;
126 }
127 } else {
128 let mut chars = s.chars();
130 if let Some(first) = chars.next() {
131 if first.is_ascii_uppercase() && chars.all(|c| c.is_ascii_alphanumeric()) {
132 return Case::Pascal;
133 }
134 }
135 }
136
137 Case::Invalid
138 }
139}
140
141#[derive(Debug, PartialEq, Eq)]
143pub enum Case {
144 Pascal,
146 LowerSnake,
148 UpperSnake,
150 UpperSingleChar,
152 Invalid,
154}
155
156#[derive(Deref)]
158pub struct ShortId(CompactString);
159
160impl PartialEq<Identifier> for ShortId {
161 fn eq(&self, other: &Identifier) -> bool {
162 self.0 == other.to_string()
163 }
164}
165
166impl From<Identifier> for SourceSpan {
167 fn from(value: Identifier) -> Self {
168 value.src_ref().into()
169 }
170}
171
172impl std::hash::Hash for Identifier {
173 fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
174 self.0.hash(hasher)
175 }
176}
177
178impl From<&std::ffi::OsStr> for Identifier {
179 fn from(value: &std::ffi::OsStr) -> Self {
180 Identifier::no_ref(value.to_string_lossy().to_string().as_str())
181 }
182}
183
184impl From<&str> for Identifier {
185 fn from(value: &str) -> Self {
186 let identifier = Identifier::no_ref(value);
187 assert!(identifier.is_valid());
188 identifier
189 }
190}
191
192impl<'a> From<&'a Identifier> for &'a str {
193 fn from(value: &'a Identifier) -> Self {
194 &value.0
195 }
196}
197
198impl std::fmt::Display for Identifier {
199 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
200 if self.is_empty() {
201 write!(f, "<NO ID>")
202 } else {
203 write!(f, "{}", self.0)
204 }
205 }
206}
207
208impl PartialEq<str> for Identifier {
209 fn eq(&self, other: &str) -> bool {
210 *self.0 == other
211 }
212}
213
214impl TreeDisplay for Identifier {
215 fn tree_print(&self, f: &mut std::fmt::Formatter, depth: TreeState) -> std::fmt::Result {
216 writeln!(f, "{:depth$}Identifier: {}", "", self.id())
217 }
218}
219
220static UNIQUE_ID_NEXT: std::sync::Mutex<usize> = std::sync::Mutex::new(0);
221
222#[test]
223fn identifier_comparison() {
224 use crate::{LineCol, SrcRef};
225
226 let id1 = Identifier::no_ref("x");
228 let id2 = Identifier(Refer::new(
229 "x".into(),
230 SrcRef::new(0..5, LineCol { line: 0, col: 1 }, 1),
231 ));
232
233 assert!(id1 == id2);
235}
236
237#[test]
238fn identifier_hash() {
239 use crate::{LineCol, SrcRef};
240 use std::hash::{Hash, Hasher};
241
242 let id1 = Identifier(Refer::none("x".into()));
244 let id2 = Identifier(Refer::new(
245 "x".into(),
246 SrcRef::new(0..5, LineCol { line: 0, col: 1 }, 1),
247 ));
248
249 let mut hasher = std::hash::DefaultHasher::new();
250 id1.hash(&mut hasher);
251 let hash1 = hasher.finish();
252 let mut hasher = std::hash::DefaultHasher::new();
253 id2.hash(&mut hasher);
254
255 let hash2 = hasher.finish();
256
257 assert_eq!(hash1, hash2);
259}
260
261#[test]
262fn identifier_case() {
263 let detect_case = |s| -> Case { Identifier::no_ref(s).detect_case() };
264
265 assert_eq!(detect_case("PascalCase"), Case::Pascal);
266 assert_eq!(detect_case("lower_snake_case"), Case::LowerSnake);
267 assert_eq!(detect_case("UPPER_SNAKE_CASE"), Case::UpperSnake);
268 assert_eq!(detect_case("notValid123_"), Case::Invalid);
269 assert_eq!(detect_case(""), Case::Invalid);
270 assert_eq!(detect_case("A"), Case::UpperSingleChar); assert_eq!(detect_case("z"), Case::Invalid); assert_eq!(detect_case("_"), Case::Invalid); assert_eq!(detect_case("a_b"), Case::LowerSnake);
274 assert_eq!(detect_case("A_B"), Case::UpperSnake);
275
276 println!("All tests passed.");
277}
278
279#[test]
280fn test_short_identifiers() {
281 fn test(id: &str) -> String {
282 Identifier::from(id).short_id().to_string()
283 }
284
285 assert_eq!(test("weather_thermal_function"), "w_t_f");
286 assert_eq!(test("width"), "w");
287 assert_eq!(test("WeatherThermal_Function"), "W_F");
288}