Skip to main content

microcad_lang/syntax/identifier/
mod.rs

1// Copyright © 2024-2026 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! µcad identifier syntax elements
5
6mod 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/// µcad identifier
17#[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
29/// Check if the element only includes one identifier
30pub trait SingleIdentifier {
31    /// If the element only includes one identifier, return it
32    fn single_identifier(&self) -> Option<&Identifier>;
33
34    /// Returns true if the element only includes a single identifier.
35    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/// A case for an identifier.
43#[derive(Debug, PartialEq, Eq)]
44pub enum Case {
45    /// PascalCase
46    Pascal,
47    /// lower_snake_case
48    LowerSnake,
49    /// UPPER_SNAKE_CASE
50    UpperSnake,
51    /// A
52    UpperSingleChar,
53    /// Invalid.
54    Invalid,
55}
56
57/// Shortened identifier
58#[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    /// Make empty (invalid) id
69    pub fn none() -> Self {
70        Self(Refer::none("".into()))
71    }
72
73    /// Create new identifier with a new unique name.
74    ///
75    /// Every call will return a new identifier (which is a `$` followed by an counter)
76    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    /// Check if id shall be ignored when warn about unused symbols
86    pub fn ignore(&self) -> bool {
87        self.0.starts_with("_")
88    }
89
90    /// Check if id is the `super` id
91    pub fn is_super(&self) -> bool {
92        *self.0 == "super"
93    }
94
95    /// Check if this was created with none()
96    pub fn is_none(&self) -> bool {
97        self.0.src_ref().is_empty() && self.src_ref().is_empty()
98    }
99
100    /// Make empty (invalid) id
101    pub fn no_ref(id: &str) -> Self {
102        Self(Refer::none(id.into()))
103    }
104
105    /// Get the value of the identifier
106    pub fn id(&self) -> &Id {
107        &self.0.value
108    }
109
110    /// Return first character of the identifier.
111    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    /// Return number of identifiers in name
129    pub fn len(&self) -> usize {
130        self.0.len()
131    }
132
133    /// Return if name is empty
134    pub fn is_empty(&self) -> bool {
135        self.0.is_empty()
136    }
137
138    /// check if this is a valid identifier (contains only `A`-`Z`, `a`-`z` or `_`)
139    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    /// Add given `prefix` to identifier to get `qualified name`.
155    pub fn with_prefix(&self, prefix: &QualifiedName) -> QualifiedName {
156        QualifiedName::from(self).with_prefix(prefix)
157    }
158
159    /// Detect if the identifier matches a certain case.
160    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            // Must be PascalCase: starts with uppercase and contains no underscores
188            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    // same id but different src refs
308    let id1 = Identifier::no_ref("x");
309    let id2 = Identifier(Refer::new("x".into(), SrcRef::new(0..5, 0, 1, 1)));
310
311    // shall be equal
312    assert!(id1 == id2);
313}
314
315#[test]
316fn identifier_hash() {
317    use crate::syntax::*;
318    use std::hash::{Hash, Hasher};
319
320    // same id but different src refs
321    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    // shall be equal
333    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); // New case
346    assert_eq!(detect_case("z"), Case::Invalid); // lowercase single letter
347    assert_eq!(detect_case("_"), Case::Invalid); // only underscore
348    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}