microcad_lang/syntax/identifier/
mod.rs1mod identifier_list;
7mod qualified_name;
8
9use derive_more::Deref;
10pub use identifier_list::*;
11use microcad_lang_base::{Refer, SrcRef, SrcReferrer, TreeDisplay, TreeState};
12use miette::SourceSpan;
13pub use qualified_name::*;
14
15use crate::{Id, parse::*};
16
17#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
19pub struct Identifier(pub Refer<Id>);
20
21pub trait SingleIdentifier {
23 fn single_identifier(&self) -> Option<&Identifier>;
25
26 fn is_single_identifier(&self) -> bool {
28 self.single_identifier().is_some()
29 }
30}
31
32pub trait Identifiable {
34 fn id(&self) -> Identifier {
36 self.id_ref().clone()
37 }
38
39 fn id_ref(&self) -> &Identifier;
41
42 fn id_as_str(&self) -> &str {
44 self.id_ref().0.as_str()
45 }
46}
47
48static UNIQUE_ID_NEXT: std::sync::Mutex<usize> = std::sync::Mutex::new(0);
49
50#[derive(Debug, PartialEq, Eq)]
52pub enum Case {
53 Pascal,
55 LowerSnake,
57 UpperSnake,
59 UpperSingleChar,
61 Invalid,
63}
64
65#[derive(Deref)]
67pub(crate) struct ShortId(String);
68
69impl PartialEq<Identifier> for ShortId {
70 fn eq(&self, other: &Identifier) -> bool {
71 self.0 == other.to_string()
72 }
73}
74
75impl Identifier {
76 pub fn none() -> Self {
78 Self(Refer::none("".into()))
79 }
80
81 pub fn unique() -> Self {
85 let mut num = UNIQUE_ID_NEXT
86 .lock()
87 .expect("lock on UNIQUE_ID_NEXT failed");
88 let id = format!("${num}");
89 *num += 1;
90 Identifier::no_ref(&id)
91 }
92
93 pub fn ignore(&self) -> bool {
95 self.0.starts_with("_")
96 }
97
98 pub fn is_super(&self) -> bool {
100 *self.0 == "super"
101 }
102
103 pub fn is_none(&self) -> bool {
105 self.0.src_ref().is_empty() && self.src_ref().is_empty()
106 }
107
108 pub fn no_ref(id: &str) -> Self {
110 Self(Refer::none(id.into()))
111 }
112
113 pub fn id(&self) -> &Id {
115 &self.0.value
116 }
117
118 pub(crate) fn short_id(&self) -> ShortId {
120 let parts = self
121 .0
122 .value
123 .split("_")
124 .map(|part| {
125 part.chars()
126 .next()
127 .expect("cannot shorten empty Identifier")
128 })
129 .map(|p| p.to_string())
130 .collect::<Vec<_>>()
131 .join("_");
132
133 ShortId(parts)
134 }
135
136 pub fn len(&self) -> usize {
138 self.0.len()
139 }
140
141 pub fn is_empty(&self) -> bool {
143 self.0.is_empty()
144 }
145
146 pub fn validate(self) -> ParseResult<Self> {
148 let str = self.0.as_str();
149
150 let Some(start) = str.chars().next() else {
151 return Err(ParseError::InvalidIdentifier(Refer::new(
152 self.0.as_str().into(),
153 self.src_ref(),
154 )));
155 };
156 if start != '_' && !start.is_ascii_alphabetic() {
157 return Err(ParseError::InvalidIdentifier(Refer::new(
158 self.0.as_str().into(),
159 self.src_ref(),
160 )));
161 }
162 if !str.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
163 return Err(ParseError::InvalidIdentifier(Refer::new(
164 self.0.as_str().into(),
165 self.src_ref(),
166 )));
167 }
168 Ok(self)
169 }
170
171 pub fn with_prefix(&self, prefix: &QualifiedName) -> QualifiedName {
173 QualifiedName::from(self).with_prefix(prefix)
174 }
175
176 pub fn detect_case(&self) -> Case {
178 let s = &self.0.value;
179
180 if s.is_empty() {
181 return Case::Invalid;
182 }
183
184 if s.len() == 1 {
185 let c = s.chars().next().expect("At least one char");
186 if c.is_ascii_uppercase() {
187 return Case::UpperSingleChar;
188 } else {
189 return Case::Invalid;
190 }
191 }
192
193 let has_underscore = s.contains('_');
194
195 if has_underscore {
196 if s.chars().all(|c| c.is_ascii_uppercase() || c == '_') {
197 return Case::UpperSnake;
198 } else if s.chars().all(|c| c.is_ascii_lowercase() || c == '_') {
199 return Case::LowerSnake;
200 } else {
201 return Case::Invalid;
202 }
203 } else {
204 let mut chars = s.chars();
206 if let Some(first) = chars.next() {
207 if first.is_ascii_uppercase() && chars.all(|c| c.is_ascii_alphanumeric()) {
208 return Case::Pascal;
209 }
210 }
211 }
212
213 Case::Invalid
214 }
215}
216
217impl SrcReferrer for Identifier {
218 fn src_ref(&self) -> SrcRef {
219 self.0.src_ref.clone()
220 }
221}
222
223impl From<Identifier> for SourceSpan {
224 fn from(value: Identifier) -> Self {
225 value.src_ref().into()
226 }
227}
228
229impl std::hash::Hash for Identifier {
230 fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
231 self.0.hash(hasher)
232 }
233}
234
235impl std::str::FromStr for Identifier {
236 type Err = crate::eval::EvalError;
237
238 fn from_str(id: &str) -> Result<Self, Self::Err> {
239 Ok(Identifier::no_ref(id).validate()?)
240 }
241}
242
243impl From<&std::ffi::OsStr> for Identifier {
244 fn from(value: &std::ffi::OsStr) -> Self {
245 Identifier::no_ref(value.to_string_lossy().to_string().as_str())
246 }
247}
248
249impl From<&str> for Identifier {
250 fn from(value: &str) -> Self {
251 let identifier = Identifier::no_ref(value);
252 identifier.validate().expect("A valid identifier")
253 }
254}
255
256impl<'a> From<&'a Identifier> for &'a str {
257 fn from(value: &'a Identifier) -> Self {
258 &value.0
259 }
260}
261
262impl std::fmt::Display for Identifier {
263 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
264 if self.is_empty() {
265 write!(f, microcad_lang_base::invalid_no_ansi!(ID))
266 } else {
267 write!(f, "{}", self.0)
268 }
269 }
270}
271
272impl std::fmt::Debug for Identifier {
273 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
274 if self.is_empty() {
275 write!(f, "{}", microcad_lang_base::invalid!(ID))
276 } else {
277 write!(f, "{}", self.0)
278 }
279 }
280}
281
282impl PartialEq<str> for Identifier {
283 fn eq(&self, other: &str) -> bool {
284 *self.0 == other
285 }
286}
287
288impl TreeDisplay for Identifier {
289 fn tree_print(&self, f: &mut std::fmt::Formatter, depth: TreeState) -> std::fmt::Result {
290 writeln!(f, "{:depth$}Identifier: {}", "", self.id())
291 }
292}
293
294#[test]
295fn identifier_comparison() {
296 use crate::syntax::*;
297
298 let id1 = Identifier::no_ref("x");
300 let id2 = Identifier(Refer::new("x".into(), SrcRef::new(0..5, 0, 1, 1)));
301
302 assert!(id1 == id2);
304}
305
306#[test]
307fn identifier_hash() {
308 use crate::syntax::*;
309 use std::hash::{Hash, Hasher};
310
311 let id1 = Identifier(Refer::none("x".into()));
313 let id2 = Identifier(Refer::new("x".into(), SrcRef::new(0..5, 0, 1, 1)));
314
315 let mut hasher = std::hash::DefaultHasher::new();
316 id1.hash(&mut hasher);
317 let hash1 = hasher.finish();
318 let mut hasher = std::hash::DefaultHasher::new();
319 id2.hash(&mut hasher);
320
321 let hash2 = hasher.finish();
322
323 assert_eq!(hash1, hash2);
325}
326
327#[test]
328fn identifier_case() {
329 let detect_case = |s| -> Case { Identifier::no_ref(s).detect_case() };
330
331 assert_eq!(detect_case("PascalCase"), Case::Pascal);
332 assert_eq!(detect_case("lower_snake_case"), Case::LowerSnake);
333 assert_eq!(detect_case("UPPER_SNAKE_CASE"), Case::UpperSnake);
334 assert_eq!(detect_case("notValid123_"), Case::Invalid);
335 assert_eq!(detect_case(""), Case::Invalid);
336 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);
340 assert_eq!(detect_case("A_B"), Case::UpperSnake);
341
342 println!("All tests passed.");
343}
344
345#[test]
346fn test_short_identifiers() {
347 fn test(id: &str) -> String {
348 Identifier::from(id).short_id().to_string()
349 }
350
351 assert_eq!(test("weather_thermal_function"), "w_t_f");
352 assert_eq!(test("width"), "w");
353 assert_eq!(test("WeatherThermal_Function"), "W_F");
354}