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