1use std::{
2 fmt::{Debug, Display},
3 num::NonZeroU32,
4 sync::Arc,
5};
6
7use convert_case::{Boundary, Case, Pattern};
8
9#[repr(C)]
10#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
11pub enum RuntimeNamespace {
12 Global,
15 Local { site: Option<NonZeroU32> },
18 Operation,
21 Type,
24}
25
26impl RuntimeNamespace {
27 pub fn shares_namespace_with(&self, other: RuntimeNamespace) -> bool {
28 for self_namespace in self.concrete_namespaces() {
29 for other_namespace in other.concrete_namespaces() {
30 if self_namespace == other_namespace {
31 return true;
32 }
33 }
34 }
35
36 false
37 }
38
39 pub fn concrete_namespaces(&self) -> Vec<RuntimeNamespace> {
40 match self {
41 RuntimeNamespace::Global => {
42 vec![RuntimeNamespace::Operation, RuntimeNamespace::Type]
43 }
44 RuntimeNamespace::Local { .. } => vec![*self],
45 RuntimeNamespace::Operation => vec![*self],
46 RuntimeNamespace::Type => vec![*self],
47 }
48 }
49}
50
51impl Display for RuntimeNamespace {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 RuntimeNamespace::Global => write!(f, "Global"),
55 RuntimeNamespace::Local { site: Some(site) } => write!(f, "Local({site})"),
56 RuntimeNamespace::Local { site: None } => write!(f, "Local"),
57 RuntimeNamespace::Operation => write!(f, "Operation"),
58 RuntimeNamespace::Type => write!(f, "Type"),
59 }
60 }
61}
62
63#[repr(transparent)]
64#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
65pub struct Type(RuntimeNamespace);
66impl Default for Type {
67 fn default() -> Self {
68 Self(RuntimeNamespace::Type)
69 }
70}
71#[repr(transparent)]
72#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
73pub struct Operation(RuntimeNamespace);
74impl Default for Operation {
75 fn default() -> Self {
76 Self(RuntimeNamespace::Operation)
77 }
78}
79#[repr(transparent)]
80#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
81pub struct Global(RuntimeNamespace);
82impl Default for Global {
83 fn default() -> Self {
84 Self(RuntimeNamespace::Global)
85 }
86}
87
88#[repr(transparent)]
89#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
90pub struct Local(RuntimeNamespace);
91impl Default for Local {
92 fn default() -> Self {
93 Self(RuntimeNamespace::Local { site: None })
94 }
95}
96
97pub unsafe trait Namespace: Debug {
100 fn runtime_value(&self) -> RuntimeNamespace;
101}
102
103unsafe impl Namespace for Type {
104 fn runtime_value(&self) -> RuntimeNamespace {
105 RuntimeNamespace::Type
106 }
107}
108unsafe impl Namespace for Operation {
109 fn runtime_value(&self) -> RuntimeNamespace {
110 RuntimeNamespace::Operation
111 }
112}
113unsafe impl Namespace for Global {
114 fn runtime_value(&self) -> RuntimeNamespace {
115 RuntimeNamespace::Global
116 }
117}
118unsafe impl Namespace for Local {
119 fn runtime_value(&self) -> RuntimeNamespace {
120 self.0.runtime_value()
121 }
122}
123unsafe impl Namespace for RuntimeNamespace {
124 fn runtime_value(&self) -> RuntimeNamespace {
125 *self
126 }
127}
128
129impl From<Global> for Type {
130 fn from(_: Global) -> Self {
131 Type::default()
132 }
133}
134impl From<Global> for Operation {
135 fn from(_: Global) -> Self {
136 Operation::default()
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
142#[repr(C)]
143pub struct Identifier<T: Namespace> {
144 boundaries_applied: bool,
145 original: Arc<String>,
147 words: Arc<[String]>,
148 duplicate_id: Option<NonZeroU32>,
149 namespace: T,
151}
152
153impl<T: Namespace> Identifier<T> {
154 pub fn try_parse(value: &str) -> Result<Self, Error>
157 where
158 T: Default,
159 {
160 Self::try_parse_with_type(value, T::default())
161 }
162
163 pub fn try_parse_with_type(value: &str, id_type: T) -> Result<Self, Error> {
166 if value.is_empty() {
167 return Err(Error::Empty);
168 }
169
170 Ok(Self {
171 boundaries_applied: false,
172 original: Arc::new(value.into()),
173 words: [value.into()].into(),
174 duplicate_id: None,
175 namespace: id_type,
176 })
177 }
178
179 pub fn apply_boundaries(&mut self, boundaries: &[Boundary]) -> &mut Self {
181 assert!(!self.boundaries_applied);
182
183 let mut words = Vec::new();
184
185 for word in self.words.iter() {
186 let mut local_words = convert_case::split(word, boundaries);
187 local_words.retain(|word| !word.is_empty());
188 words.append(&mut local_words);
189 }
190
191 let words = Pattern::Lowercase.mutate(&words);
192
193 self.boundaries_applied = true;
194 self.words = words.into_iter().collect();
195 self
196 }
197
198 pub fn check_validity(&self) -> Result<(), Error> {
199 assert!(self.boundaries_applied);
200
201 for (word_index, word) in self.words.iter().enumerate() {
202 for (char_offset, char) in word.char_indices() {
203 let tfn = match (word_index, char_offset) {
204 (0, 0) => |c| unicode_ident::is_xid_start(c),
205 _ => |c| unicode_ident::is_xid_continue(c),
206 };
207
208 if !tfn(char) {
209 let offset = self
210 .original()
211 .to_lowercase()
212 .find(word)
213 .map(|word_offset| word_offset + char_offset)
214 .expect("Word should be present in identifier words");
215 return Err(Error::InvalidCharacter {
216 byte_offset: offset,
217 invalid_char: char,
218 });
219 }
220 }
221 }
222
223 if self.words.iter().all(String::is_empty) {
224 return Err(Error::EmptyAfterSplits);
225 }
226
227 let converted = self.to_case(Case::Pascal);
228 if converted.contains(['-', '_', ' ']) {
229 return Err(Error::CannotConvert {
230 case_name: "Pascal",
231 example: converted,
232 });
233 }
234
235 Ok(())
236 }
237
238 pub fn to_case(&self, case: Case) -> String {
240 assert!(
241 self.boundaries_applied,
242 "Boundaries not applied for `{}`",
243 self.original()
244 );
245
246 let mut words = self.words.to_vec();
247
248 if let Some(dup_id) = self.duplicate_id {
249 words.push("dup".to_string());
250 words.push(format!("{dup_id:X}"));
251 }
252
253 let words = case.mutate(&words.iter().map(String::as_str).collect::<Vec<_>>());
254 case.join(&words)
255 }
256
257 pub fn original(&self) -> &str {
260 &self.original
261 }
262
263 pub fn words(&self) -> Arc<[String]> {
265 self.words.clone()
266 }
267
268 pub fn words_display(&self) -> String {
270 self.words.join("ยท")
271 }
272
273 pub fn words_display_prepended(&self, word: String) -> String {
275 let mut words = self.words.to_vec();
276 words.insert(0, word);
277 words.join("ยท")
278 }
279
280 pub fn is_empty(&self) -> bool {
281 self.words.iter().all(String::is_empty)
282 }
283
284 pub fn take_ref(&self) -> IdentifierRef<T>
286 where
287 T: Clone,
288 {
289 IdentifierRef {
290 original: self.original.clone(),
291 id_type: self.namespace.clone(),
292 }
293 }
294
295 pub fn set_duplicate_id(&mut self, val: NonZeroU32) {
296 self.duplicate_id = Some(val);
297 }
298
299 pub fn duplicate_id(&self) -> Option<NonZeroU32> {
300 self.duplicate_id
301 }
302
303 pub fn to_runtime_namespace(self) -> Identifier<RuntimeNamespace> {
304 Identifier {
305 boundaries_applied: self.boundaries_applied,
306 original: self.original,
307 words: self.words,
308 duplicate_id: self.duplicate_id,
309 namespace: self.namespace.runtime_value(),
310 }
311 }
312
313 pub fn as_runtime_namespace_mut(&mut self) -> &mut Identifier<RuntimeNamespace> {
314 assert_eq!(size_of::<T>(), size_of::<RuntimeNamespace>());
315 unsafe { std::mem::transmute::<&mut Self, &mut Identifier<RuntimeNamespace>>(self) }
318 }
319
320 pub fn as_runtime_namespace(&self) -> &Identifier<RuntimeNamespace> {
321 assert_eq!(size_of::<T>(), size_of::<RuntimeNamespace>());
322 unsafe { std::mem::transmute::<&Self, &Identifier<RuntimeNamespace>>(self) }
325 }
326
327 pub fn namespace(&self) -> &T {
329 &self.namespace
330 }
331
332 pub fn cast<U>(self) -> Identifier<U>
334 where
335 U: Namespace + Default,
336 U: From<T>,
337 {
338 self.cast_unchecked()
340 }
341
342 pub fn cast_unchecked<U: Namespace + Default>(self) -> Identifier<U> {
346 Identifier {
347 boundaries_applied: self.boundaries_applied,
348 original: self.original,
349 words: self.words,
350 duplicate_id: self.duplicate_id,
351 namespace: U::default(),
352 }
353 }
354
355 #[track_caller]
358 pub fn cast_assert<U: Namespace + Default>(self) -> Identifier<U> {
359 assert_eq!(self.namespace.runtime_value(), U::default().runtime_value());
360
361 Identifier {
362 boundaries_applied: self.boundaries_applied,
363 original: self.original,
364 words: self.words,
365 duplicate_id: self.duplicate_id,
366 namespace: U::default(),
367 }
368 }
369
370 pub fn is_valid(&self) -> bool {
372 match self.namespace().runtime_value() {
373 RuntimeNamespace::Global => true,
374 RuntimeNamespace::Local { site } => site.is_some(),
375 RuntimeNamespace::Operation => true,
376 RuntimeNamespace::Type => true,
377 }
378 }
379}
380
381impl Identifier<Local> {
382 pub fn set_local_site(&mut self, site: NonZeroU32) {
383 self.namespace.0 = RuntimeNamespace::Local { site: Some(site) }
384 }
385}
386
387impl Identifier<RuntimeNamespace> {
388 pub fn cast_concrete(
390 self,
391 runtime_namespace: RuntimeNamespace,
392 ) -> Identifier<RuntimeNamespace> {
393 assert!(
394 self.namespace
395 .runtime_value()
396 .concrete_namespaces()
397 .contains(&runtime_namespace)
398 );
399
400 Identifier {
401 boundaries_applied: self.boundaries_applied,
402 original: self.original,
403 words: self.words,
404 duplicate_id: self.duplicate_id,
405 namespace: runtime_namespace,
406 }
407 }
408}
409
410impl<T: Namespace + Default> Default for Identifier<T> {
411 fn default() -> Self {
412 Self {
413 boundaries_applied: Default::default(),
414 original: Default::default(),
415 words: Default::default(),
416 duplicate_id: Default::default(),
417 namespace: T::default(),
418 }
419 }
420}
421
422#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
423pub struct IdentifierRef<T: Namespace> {
424 original: Arc<String>,
425 id_type: T,
426}
427
428impl<T: Namespace> IdentifierRef<T> {
429 pub fn new(identifier_original: String) -> Self
430 where
431 T: Default,
432 {
433 Self {
434 original: Arc::new(identifier_original),
435 id_type: T::default(),
436 }
437 }
438
439 pub fn original(&self) -> &str {
440 &self.original
441 }
442
443 pub fn is_ref_to<U: Namespace>(&self, identifier: &Identifier<U>) -> bool {
444 identifier.namespace.runtime_value() == self.id_type.runtime_value()
445 && self.original() == identifier.original()
446 }
447}
448
449#[derive(Debug, Clone, PartialEq, Eq)]
450pub enum Error {
451 Empty,
452 EmptyAfterSplits,
453 InvalidCharacter {
454 byte_offset: usize,
455 invalid_char: char,
456 },
457 CannotConvert {
458 case_name: &'static str,
459 example: String,
460 },
461}
462
463impl std::error::Error for Error {}
464impl Display for Error {
465 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
466 match self {
467 Error::Empty => write!(f, "identifier is empty"),
468 Error::EmptyAfterSplits => write!(f, "identifier is empty after word split"),
469 Error::InvalidCharacter {
470 byte_offset,
471 invalid_char,
472 } => {
473 write!(
474 f,
475 "identifier contains an invalid character at byte offset {byte_offset}: '{invalid_char:?}'"
476 )
477 }
478 Error::CannotConvert { case_name, example } => {
479 write!(
480 f,
481 "cannot change the casing of the identifier. Identifier is `{example}` when converted to {case_name} case, but that's not correct casing"
482 )
483 }
484 }
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn simple_cases() {
494 assert_eq!(Identifier::<Global>::try_parse(""), Err(Error::Empty));
495 assert_eq!(
496 Identifier::<Global>::try_parse("1")
497 .unwrap()
498 .apply_boundaries(&[Boundary::Underscore])
499 .check_validity(),
500 Err(Error::InvalidCharacter {
501 byte_offset: 0,
502 invalid_char: '1'
503 })
504 );
505 assert_eq!(
506 Identifier::<Global>::try_parse("_1")
507 .unwrap()
508 .apply_boundaries(&[Boundary::Underscore])
509 .to_case(Case::Kebab),
510 "1"
511 );
512 assert_eq!(
513 Identifier::<Global>::try_parse("a1")
514 .unwrap()
515 .apply_boundaries(&[Boundary::Underscore])
516 .to_case(Case::Kebab),
517 "a1"
518 );
519 assert_eq!(
520 Identifier::<Global>::try_parse("a_1")
521 .unwrap()
522 .apply_boundaries(&[Boundary::Underscore])
523 .to_case(Case::Kebab),
524 "a-1"
525 );
526 assert_eq!(
527 Identifier::<Global>::try_parse("๐")
528 .unwrap()
529 .apply_boundaries(&[Boundary::Underscore])
530 .check_validity(),
531 Err(Error::InvalidCharacter {
532 byte_offset: 0,
533 invalid_char: '๐'
534 })
535 );
536 assert_eq!(
537 Identifier::<Global>::try_parse("abc๐")
538 .unwrap()
539 .apply_boundaries(&[Boundary::Underscore])
540 .check_validity(),
541 Err(Error::InvalidCharacter {
542 byte_offset: 3,
543 invalid_char: '๐'
544 })
545 );
546 assert_eq!(
547 Identifier::<Global>::try_parse("_")
548 .unwrap()
549 .apply_boundaries(&[Boundary::Space])
550 .to_case(Case::Kebab),
551 "_"
552 );
553 assert_eq!(
554 Identifier::<Global>::try_parse("_")
555 .unwrap()
556 .apply_boundaries(&[Boundary::Underscore])
557 .check_validity(),
558 Err(Error::EmptyAfterSplits)
559 );
560 assert_eq!(
561 Identifier::<Global>::try_parse("abc def")
562 .unwrap()
563 .apply_boundaries(&[Boundary::Underscore])
564 .check_validity(),
565 Err(Error::InvalidCharacter {
566 byte_offset: 3,
567 invalid_char: ' '
568 })
569 );
570 Identifier::<Global>::try_parse("abc def")
571 .unwrap()
572 .apply_boundaries(&[Boundary::Space])
573 .check_validity()
574 .unwrap();
575 assert_eq!(
576 Identifier::<Global>::try_parse("abc_def")
577 .unwrap()
578 .apply_boundaries(&[Boundary::Underscore])
579 .to_case(Case::Kebab),
580 "abc-def"
581 );
582 assert_eq!(
583 Identifier::<Global>::try_parse("_abc_def")
584 .unwrap()
585 .apply_boundaries(&[Boundary::Underscore])
586 .to_case(Case::Kebab),
587 "abc-def"
588 );
589 assert_eq!(
590 Identifier::<Global>::try_parse("Bar๐ฉbar")
591 .unwrap()
592 .apply_boundaries(&[Boundary::Underscore])
593 .check_validity(),
594 Err(Error::InvalidCharacter {
595 byte_offset: 3,
596 invalid_char: '๐ฉ'
597 })
598 );
599 }
600
601 #[test]
602 fn default_is_empty() {
603 assert!(Identifier::<Global>::default().is_empty());
604 }
605
606 #[test]
607 fn static_vs_runtime_equals() {
608 assert_eq!(
609 Identifier::<Type>::try_parse("a")
610 .unwrap()
611 .to_runtime_namespace(),
612 Identifier::try_parse_with_type("a", RuntimeNamespace::Type).unwrap()
613 );
614
615 assert_ne!(
616 Identifier::<Type>::try_parse("a")
617 .unwrap()
618 .to_runtime_namespace(),
619 Identifier::try_parse_with_type("a", RuntimeNamespace::Operation).unwrap()
620 );
621 }
622
623 #[test]
624 fn issue_274() {
625 Identifier::<Global>::try_parse("io_pad_i2c_b1")
627 .unwrap()
628 .apply_boundaries(&Boundary::defaults())
629 .check_validity()
630 .unwrap();
631
632 Identifier::<Global>::try_parse("io_pad_i2c-b1")
633 .unwrap()
634 .apply_boundaries(&[Boundary::Underscore])
635 .check_validity()
636 .unwrap_err();
637 }
638}