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