microcad_lang/syntax/identifier/
mod.rs

1// Copyright © 2024-2025 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};
10use miette::SourceSpan;
11pub use identifier_list::*;
12pub use qualified_name::*;
13
14use crate::{parse::*, parser::Parser, 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
57impl Identifier {
58    /// Make empty (invalid) id
59    pub fn none() -> Self {
60        Self(Refer::none("".into()))
61    }
62
63    /// Create new identifier with a new unique name.
64    ///
65    /// Every call will return a new identifier (which is a `$` followed by an counter)
66    pub fn unique() -> Self {
67        let mut num = UNIQUE_ID_NEXT
68            .lock()
69            .expect("lock on UNIQUE_ID_NEXT failed");
70        let id = format!("${num}");
71        *num += 1;
72        Identifier::no_ref(&id)
73    }
74
75    /// Check if id shall be ignored when warn about unused symbols
76    pub fn ignore(&self) -> bool {
77        self.0.starts_with("_")
78    }
79
80    /// Check if id is the `super` id
81    pub fn is_super(&self) -> bool {
82        *self.0 == "super"
83    }
84
85    /// Check if this was created with none()
86    pub fn is_none(&self) -> bool {
87        self.0.src_ref().is_empty() && self.src_ref().is_empty()
88    }
89
90    /// Make empty (invalid) id
91    pub fn no_ref(id: &str) -> Self {
92        Self(Refer::none(id.into()))
93    }
94
95    /// Get the value of the identifier
96    pub fn id(&self) -> &Id {
97        &self.0.value
98    }
99
100    /// Return number of identifiers in name
101    pub fn len(&self) -> usize {
102        self.0.len()
103    }
104
105    /// Return if name is empty
106    pub fn is_empty(&self) -> bool {
107        self.0.is_empty()
108    }
109
110    /// check if this is a valid identifier (contains only `A`-`Z`, `a`-`z` or `_`)
111    pub fn validate(self) -> ParseResult<Self> {
112        Parser::parse_rule(crate::parser::Rule::identifier, self.id().as_str(), 0)
113    }
114
115    /// Add given `prefix` to identifier to get `qualified name`.
116    pub fn with_prefix(&self, prefix: &QualifiedName) -> QualifiedName {
117        QualifiedName::from(self).with_prefix(prefix)
118    }
119
120    /// Detect if the identifier matches a certain case.
121    pub fn detect_case(&self) -> Case {
122        let s = &self.0.value;
123
124        if s.is_empty() {
125            return Case::Invalid;
126        }
127
128        if s.len() == 1 {
129            let c = s.chars().next().expect("At least one char");
130            if c.is_ascii_uppercase() {
131                return Case::UpperSingleChar;
132            } else {
133                return Case::Invalid;
134            }
135        }
136
137        let has_underscore = s.contains('_');
138
139        if has_underscore {
140            if s.chars().all(|c| c.is_ascii_uppercase() || c == '_') {
141                return Case::UpperSnake;
142            } else if s.chars().all(|c| c.is_ascii_lowercase() || c == '_') {
143                return Case::LowerSnake;
144            } else {
145                return Case::Invalid;
146            }
147        } else {
148            // Must be PascalCase: starts with uppercase and contains no underscores
149            let mut chars = s.chars();
150            if let Some(first) = chars.next() {
151                if first.is_ascii_uppercase() && chars.all(|c| c.is_ascii_alphanumeric()) {
152                    return Case::Pascal;
153                }
154            }
155        }
156
157        Case::Invalid
158    }
159}
160
161impl SrcReferrer for Identifier {
162    fn src_ref(&self) -> SrcRef {
163        self.0.src_ref.clone()
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 std::str::FromStr for Identifier {
180    type Err = crate::eval::EvalError;
181
182    fn from_str(id: &str) -> Result<Self, Self::Err> {
183        Ok(Identifier::no_ref(id).validate()?)
184    }
185}
186
187impl From<&std::ffi::OsStr> for Identifier {
188    fn from(value: &std::ffi::OsStr) -> Self {
189        Identifier::no_ref(value.to_string_lossy().to_string().as_str())
190    }
191}
192
193impl From<&str> for Identifier {
194    fn from(value: &str) -> Self {
195        Parser::parse_rule(crate::parser::Rule::identifier, value, 0).expect("A valid identifier")
196    }
197}
198
199impl<'a> From<&'a Identifier> for &'a str {
200    fn from(value: &'a Identifier) -> Self {
201        &value.0
202    }
203}
204
205impl std::fmt::Display for Identifier {
206    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
207        if self.is_empty() {
208            write!(f, crate::invalid_no_ansi!(ID))
209        } else {
210            write!(f, "{}", self.0)
211        }
212    }
213}
214
215impl std::fmt::Debug for Identifier {
216    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
217        if self.is_empty() {
218            write!(f, "{}", crate::invalid!(ID))
219        } else {
220            write!(f, "{}", self.0)
221        }
222    }
223}
224
225impl PartialEq<str> for Identifier {
226    fn eq(&self, other: &str) -> bool {
227        *self.0 == other
228    }
229}
230
231impl TreeDisplay for Identifier {
232    fn tree_print(&self, f: &mut std::fmt::Formatter, depth: TreeState) -> std::fmt::Result {
233        writeln!(f, "{:depth$}Identifier: {}", "", self.id())
234    }
235}
236
237impl std::fmt::Display for IdentifierSet {
238    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239        write!(
240            f,
241            "{}",
242            self.iter()
243                .map(|id| id.to_string())
244                .collect::<Vec<_>>()
245                .join(", ")
246        )
247    }
248}
249
250impl std::fmt::Debug for IdentifierSet {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        write!(
253            f,
254            "{}",
255            self.iter()
256                .map(|id| format!("{id:?}"))
257                .collect::<Vec<_>>()
258                .join(", ")
259        )
260    }
261}
262
263#[test]
264fn identifier_comparison() {
265    use crate::syntax::*;
266
267    // same id but different src refs
268    let id1 = Identifier::no_ref("x");
269    let id2 = Identifier(Refer::new("x".into(), SrcRef::new(0..5, 0, 1, 1)));
270
271    // shall be equal
272    assert!(id1 == id2);
273}
274
275#[test]
276fn identifier_hash() {
277    use crate::syntax::*;
278    use std::hash::{Hash, Hasher};
279
280    // same id but different src refs
281    let id1 = Identifier(Refer::none("x".into()));
282    let id2 = Identifier(Refer::new("x".into(), SrcRef::new(0..5, 0, 1, 1)));
283
284    let mut hasher = std::hash::DefaultHasher::new();
285    id1.hash(&mut hasher);
286    let hash1 = hasher.finish();
287    let mut hasher = std::hash::DefaultHasher::new();
288    id2.hash(&mut hasher);
289
290    let hash2 = hasher.finish();
291
292    // shall be equal
293    assert_eq!(hash1, hash2);
294}
295
296#[test]
297fn identifier_case() {
298    let detect_case = |s| -> Case { Identifier::no_ref(s).detect_case() };
299
300    assert_eq!(detect_case("PascalCase"), Case::Pascal);
301    assert_eq!(detect_case("lower_snake_case"), Case::LowerSnake);
302    assert_eq!(detect_case("UPPER_SNAKE_CASE"), Case::UpperSnake);
303    assert_eq!(detect_case("notValid123_"), Case::Invalid);
304    assert_eq!(detect_case(""), Case::Invalid);
305    assert_eq!(detect_case("A"), Case::UpperSingleChar); // New case
306    assert_eq!(detect_case("z"), Case::Invalid); // lowercase single letter
307    assert_eq!(detect_case("_"), Case::Invalid); // only underscore
308    assert_eq!(detect_case("a_b"), Case::LowerSnake);
309    assert_eq!(detect_case("A_B"), Case::UpperSnake);
310
311    println!("All tests passed.");
312}