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