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