1 use std::cmp::{Ord, Ordering, PartialOrd};
2 use std::str;
3 
4 #[derive(Copy, Clone, Eq, PartialEq)]
5 pub enum Atom<'a> {
6     /// A sequence of underscores.
7     Underscore(usize),
8     /// A sequence of digits.
9     Number(&'a str),
10     /// A sequence of characters.
11     Chars(&'a str),
12 }
13 
14 impl Atom<'_> {
underscores(&self) -> usize15     pub fn underscores(&self) -> usize {
16         match *self {
17             Atom::Underscore(n) => n,
18             _ => 0,
19         }
20     }
21 }
22 
23 impl PartialOrd for Atom<'_> {
partial_cmp(&self, other: &Self) -> Option<Ordering>24     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
25         Some(self.cmp(other))
26     }
27 }
28 
29 impl Ord for Atom<'_> {
cmp(&self, other: &Self) -> Ordering30     fn cmp(&self, other: &Self) -> Ordering {
31         use self::Atom::*;
32 
33         match (self, other) {
34             (Underscore(l), Underscore(r)) => l.cmp(r),
35             (Underscore(_), _) => Ordering::Less,
36             (_, Underscore(_)) => Ordering::Greater,
37             (Number(l), Number(r)) => cmp_numeric(l, r),
38             (Number(_), Chars(_)) => Ordering::Less,
39             (Chars(_), Number(_)) => Ordering::Greater,
40             (Chars(l), Chars(r)) => cmp_ignore_case(l, r),
41         }
42     }
43 }
44 
cmp_numeric(l: &str, r: &str) -> Ordering45 fn cmp_numeric(l: &str, r: &str) -> Ordering {
46     // Trim leading zeros.
47     let l = l.trim_start_matches('0');
48     let r = r.trim_start_matches('0');
49 
50     match l.len().cmp(&r.len()) {
51         Ordering::Equal => l.cmp(r),
52         non_eq => non_eq,
53     }
54 }
55 
cmp_ignore_case(l: &str, r: &str) -> Ordering56 fn cmp_ignore_case(l: &str, r: &str) -> Ordering {
57     for (a, b) in l.bytes().zip(r.bytes()) {
58         match a.to_ascii_lowercase().cmp(&b.to_ascii_lowercase()) {
59             Ordering::Equal => match a.cmp(&b) {
60                 Ordering::Equal => {}
61                 non_eq => return non_eq,
62             },
63             non_eq => return non_eq,
64         }
65     }
66 
67     l.len().cmp(&r.len())
68 }
69 
iter_atoms(string: &str) -> AtomIter70 pub fn iter_atoms(string: &str) -> AtomIter {
71     AtomIter {
72         bytes: string.as_bytes(),
73         offset: 0,
74     }
75 }
76 
77 pub struct AtomIter<'a> {
78     bytes: &'a [u8],
79     offset: usize,
80 }
81 
82 impl<'a> Iterator for AtomIter<'a> {
83     type Item = Atom<'a>;
84 
next(&mut self) -> Option<Atom<'a>>85     fn next(&mut self) -> Option<Atom<'a>> {
86         if self.offset >= self.bytes.len() {
87             return None;
88         }
89 
90         let x = self.bytes[self.offset];
91 
92         match x {
93             b'_' => {
94                 self.offset += 1;
95 
96                 let mut n = 1;
97                 while self.offset < self.bytes.len() {
98                     match self.bytes[self.offset] {
99                         b'_' => {
100                             self.offset += 1;
101                             n += 1;
102                         }
103                         _ => break,
104                     }
105                 }
106 
107                 Some(Atom::Underscore(n))
108             }
109             b'0'..=b'9' => {
110                 let start = self.offset;
111 
112                 self.offset += 1;
113                 while self.offset < self.bytes.len() {
114                     match self.bytes[self.offset] {
115                         b'0'..=b'9' => self.offset += 1,
116                         _ => break,
117                     }
118                 }
119 
120                 let bytes = &self.bytes[start..self.offset];
121                 let number = str::from_utf8(bytes).expect("valid utf8");
122                 Some(Atom::Number(number))
123             }
124             _ => {
125                 let start = self.offset;
126 
127                 self.offset += 1;
128                 while self.offset < self.bytes.len() {
129                     match self.bytes[self.offset] {
130                         b'_' | b'0'..=b'9' => break,
131                         _ => self.offset += 1,
132                     }
133                 }
134 
135                 let bytes = &self.bytes[start..self.offset];
136                 let chars = str::from_utf8(bytes).expect("valid utf8");
137                 Some(Atom::Chars(chars))
138             }
139         }
140     }
141 }
142