microcad_lang/syntax/identifier/
mod.rs1mod identifier_list;
7mod qualified_name;
8
9pub use identifier_list::*;
10pub use qualified_name::*;
11
12use crate::{parse::*, parser::Parser, src_ref::*, syntax::*, Id};
13
14#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
16pub struct Identifier(pub Refer<Id>);
17
18static UNIQUE_ID_NEXT: std::sync::Mutex<usize> = std::sync::Mutex::new(0);
19
20#[derive(Debug, PartialEq, Eq)]
22pub enum Case {
23 Pascal,
25 LowerSnake,
27 UpperSnake,
29 UpperSingleChar,
31 Invalid,
33}
34
35impl Identifier {
36 pub fn none() -> Self {
38 Self(Refer::none("".into()))
39 }
40
41 pub fn unique() -> Self {
45 let mut num = UNIQUE_ID_NEXT
46 .lock()
47 .expect("lock on UNIQUE_ID_NEXT failed");
48 let id = format!("${num}");
49 *num += 1;
50 Identifier::no_ref(&id)
51 }
52
53 pub fn is_super(&self) -> bool {
55 *self.0 == "super"
56 }
57
58 pub fn is_none(&self) -> bool {
60 self.0.src_ref().is_empty() && self.0.is_empty()
61 }
62
63 pub fn no_ref(id: &str) -> Self {
65 Self(Refer::none(id.into()))
66 }
67
68 pub fn id(&self) -> &Id {
70 &self.0.value
71 }
72
73 pub fn len(&self) -> usize {
75 self.0.len()
76 }
77
78 pub fn is_empty(&self) -> bool {
80 self.0.is_empty()
81 }
82
83 pub fn validate(self) -> ParseResult<Self> {
85 Parser::parse_rule(crate::parser::Rule::identifier, self.id().as_str(), 0)
86 }
87
88 pub fn with_prefix(&self, prefix: &QualifiedName) -> QualifiedName {
90 QualifiedName::from(self).with_prefix(prefix)
91 }
92
93 pub fn detect_case(&self) -> Case {
95 let s = &self.0.value;
96
97 if s.is_empty() {
98 return Case::Invalid;
99 }
100
101 if s.len() == 1 {
102 let c = s.chars().next().expect("At least one char");
103 if c.is_ascii_uppercase() {
104 return Case::UpperSingleChar;
105 } else {
106 return Case::Invalid;
107 }
108 }
109
110 let has_underscore = s.contains('_');
111
112 if has_underscore {
113 if s.chars().all(|c| c.is_ascii_uppercase() || c == '_') {
114 return Case::UpperSnake;
115 } else if s.chars().all(|c| c.is_ascii_lowercase() || c == '_') {
116 return Case::LowerSnake;
117 } else {
118 return Case::Invalid;
119 }
120 } else {
121 let mut chars = s.chars();
123 if let Some(first) = chars.next() {
124 if first.is_ascii_uppercase() && chars.all(|c| c.is_ascii_alphanumeric()) {
125 return Case::Pascal;
126 }
127 }
128 }
129
130 Case::Invalid
131 }
132}
133
134impl SrcReferrer for Identifier {
135 fn src_ref(&self) -> SrcRef {
136 self.0.src_ref.clone()
137 }
138}
139
140impl std::hash::Hash for Identifier {
141 fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
142 self.0.hash(hasher)
143 }
144}
145
146impl std::str::FromStr for Identifier {
147 type Err = crate::eval::EvalError;
148
149 fn from_str(id: &str) -> Result<Self, Self::Err> {
150 Ok(Identifier::no_ref(id).validate()?)
151 }
152}
153
154#[cfg(test)]
155impl From<&str> for Identifier {
156 fn from(value: &str) -> Self {
157 Self(Refer::none(value.into()))
158 }
159}
160
161#[cfg(not(test))]
162impl TryFrom<&str> for Identifier {
163 type Error = ParseError;
164
165 fn try_from(value: &str) -> Result<Self, Self::Error> {
166 Parser::parse_rule(crate::parser::Rule::identifier, value, 0)
167 }
168}
169
170impl<'a> From<&'a Identifier> for &'a str {
171 fn from(value: &'a Identifier) -> Self {
172 &value.0
173 }
174}
175
176impl std::fmt::Display for Identifier {
177 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
178 if self.is_empty() {
179 write!(f, crate::invalid_no_ansi!(ID))
180 } else {
181 write!(f, "{}", self.0)
182 }
183 }
184}
185
186impl std::fmt::Debug for Identifier {
187 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
188 if self.is_empty() {
189 write!(f, "{}", crate::invalid!(ID))
190 } else {
191 write!(f, "{}", self.0)
192 }
193 }
194}
195
196impl PartialEq<str> for Identifier {
197 fn eq(&self, other: &str) -> bool {
198 *self.0 == other
199 }
200}
201
202impl TreeDisplay for Identifier {
203 fn tree_print(&self, f: &mut std::fmt::Formatter, depth: TreeState) -> std::fmt::Result {
204 writeln!(f, "{:depth$}Identifier: {}", "", self.id())
205 }
206}
207
208pub fn join_identifiers(identifiers: &[Identifier], separator: &str) -> String {
210 identifiers
211 .iter()
212 .map(|ident| format!("{ident}"))
213 .collect::<Vec<_>>()
214 .join(separator)
215}
216
217pub fn join_identifiers_debug(identifiers: &[Identifier], separator: &str) -> String {
219 identifiers
220 .iter()
221 .map(|ident| format!("{ident:?}"))
222 .collect::<Vec<_>>()
223 .join(separator)
224}
225
226#[test]
227fn identifier_comparison() {
228 use crate::syntax::*;
229
230 let id1 = Identifier::no_ref("x");
232 let id2 = Identifier(Refer::new("x".into(), SrcRef::new(0..5, 0, 1, 1)));
233
234 assert!(id1 == id2);
236}
237
238#[test]
239fn identifier_hash() {
240 use crate::syntax::*;
241 use std::hash::{Hash, Hasher};
242
243 let id1 = Identifier(Refer::none("x".into()));
245 let id2 = Identifier(Refer::new("x".into(), SrcRef::new(0..5, 0, 1, 1)));
246
247 let mut hasher = std::hash::DefaultHasher::new();
248 id1.hash(&mut hasher);
249 let hash1 = hasher.finish();
250 let mut hasher = std::hash::DefaultHasher::new();
251 id2.hash(&mut hasher);
252
253 let hash2 = hasher.finish();
254
255 assert_eq!(hash1, hash2);
257}
258
259#[test]
260fn identifier_case() {
261 let detect_case = |s| -> Case { Identifier::no_ref(s).detect_case() };
262
263 assert_eq!(detect_case("PascalCase"), Case::Pascal);
264 assert_eq!(detect_case("lower_snake_case"), Case::LowerSnake);
265 assert_eq!(detect_case("UPPER_SNAKE_CASE"), Case::UpperSnake);
266 assert_eq!(detect_case("notValid123_"), Case::Invalid);
267 assert_eq!(detect_case(""), Case::Invalid);
268 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);
272 assert_eq!(detect_case("A_B"), Case::UpperSnake);
273
274 println!("All tests passed.");
275}