Skip to main content

unicase/unicode/
mod.rs

1use alloc::string::String;
2use core::cmp::Ordering;
3use core::hash::{Hash, Hasher};
4
5use self::map::lookup;
6mod map;
7
8#[derive(Clone, Copy, Debug, Default)]
9pub struct Unicode<S>(pub S);
10
11impl<S: AsRef<str>> Unicode<S> {
12    fn to_folded_chars(&self) -> impl Iterator<Item = char> + '_ {
13        self.0.as_ref().chars().flat_map(lookup)
14    }
15
16    pub fn to_folded_case(&self) -> String {
17        self.to_folded_chars().collect()
18    }
19}
20
21impl<S1: AsRef<str>, S2: AsRef<str>> PartialEq<Unicode<S2>> for Unicode<S1> {
22    #[inline]
23    fn eq(&self, other: &Unicode<S2>) -> bool {
24        self.to_folded_chars().eq(other.to_folded_chars())
25    }
26}
27
28impl<S: AsRef<str>> Eq for Unicode<S> {}
29
30impl<T: AsRef<str>> PartialOrd for Unicode<T> {
31    #[inline]
32    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
33        Some(self.cmp(other))
34    }
35}
36
37impl<T: AsRef<str>> Ord for Unicode<T> {
38    #[inline]
39    fn cmp(&self, other: &Self) -> Ordering {
40        self.to_folded_chars().cmp(other.to_folded_chars())
41    }
42}
43
44impl<S: AsRef<str>> Hash for Unicode<S> {
45    #[inline]
46    fn hash<H: Hasher>(&self, hasher: &mut H) {
47        let mut buf = [0; 4];
48        for c in self.to_folded_chars() {
49            let len = c.encode_utf8(&mut buf).len();
50            // we can't use `write(buf)` because the ASCII variant uses
51            // `write_u8`. The docs for Hash say that's technically different.
52            // ¯\_(ツ)_/¯
53            for &b in &buf[..len] {
54                hasher.write_u8(b);
55            }
56        }
57        // prefix-freedom
58        hasher.write_u8(0xFF);
59    }
60}
61
62// internal mod so that the enum can be 'pub'
63// thanks privacy-checker :___(
64mod fold {
65    #[derive(Clone, Copy)]
66    pub enum Fold {
67        Zero,
68        One(char),
69        Two(char, char),
70        Three(char, char, char),
71    }
72
73    impl Iterator for Fold {
74        type Item = char;
75        #[inline]
76        fn next(&mut self) -> Option<char> {
77            match *self {
78                Fold::Zero => None,
79                Fold::One(one) => {
80                    *self = Fold::Zero;
81                    Some(one)
82                }
83                Fold::Two(one, two) => {
84                    *self = Fold::One(two);
85                    Some(one)
86                }
87                Fold::Three(one, two, three) => {
88                    *self = Fold::Two(two, three);
89                    Some(one)
90                }
91            }
92        }
93
94        #[inline]
95        fn size_hint(&self) -> (usize, Option<usize>) {
96            match *self {
97                Fold::Zero => (0, Some(0)),
98                Fold::One(..) => (1, Some(1)),
99                Fold::Two(..) => (2, Some(2)),
100                Fold::Three(..) => (3, Some(3)),
101            }
102        }
103    }
104    impl From<(char,)> for Fold {
105        #[inline]
106        fn from((one,): (char,)) -> Fold {
107            Fold::One(one)
108        }
109    }
110
111    impl From<(char, char)> for Fold {
112        #[inline]
113        fn from((one, two): (char, char)) -> Fold {
114            Fold::Two(one, two)
115        }
116    }
117
118    impl From<(char, char, char)> for Fold {
119        #[inline]
120        fn from((one, two, three): (char, char, char)) -> Fold {
121            Fold::Three(one, two, three)
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::Unicode;
129
130    macro_rules! eq {
131        ($left:expr, $right:expr) => {{
132            assert_eq!(Unicode($left), Unicode($right));
133        }};
134    }
135
136    #[test]
137    fn test_ascii_folding() {
138        eq!("foo bar", "FoO BAR");
139    }
140
141    #[test]
142    fn test_simple_case_folding() {
143        eq!("στιγμας", "στιγμασ");
144    }
145
146    #[test]
147    fn test_full_case_folding() {
148        eq!("flour", "flour");
149        eq!("Maße", "MASSE");
150        eq!("ᾲ στο διάολο", "ὰι στο διάολο");
151    }
152
153    #[test]
154    fn test_three_char_case_folding() {
155        // Characters whose full case folding is three characters.
156        eq!("ffi", "ffi");
157        eq!("ffl", "FFL");
158        eq!("ΐ", "ι\u{308}\u{301}");
159        eq!("ᾷ", "α\u{342}ι");
160        assert_eq!(Unicode("ffi").to_folded_case(), "ffi");
161    }
162
163    #[test]
164    fn test_to_folded_case() {
165        assert_eq!(Unicode("Maße").to_folded_case(), "masse");
166    }
167
168    #[cfg(feature = "nightly")]
169    #[bench]
170    fn bench_ascii_folding(b: &mut ::test::Bencher) {
171        b.bytes = b"foo bar".len() as u64;
172        b.iter(|| eq!("foo bar", "FoO BAR"));
173    }
174
175    #[cfg(feature = "nightly")]
176    #[bench]
177    fn bench_simple_case_folding(b: &mut ::test::Bencher) {
178        b.bytes = "στιγμας".len() as u64;
179        b.iter(|| eq!("στιγμας", "στιγμασ"));
180    }
181}