char_classes/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4#[doc = include_str!("../README.md")]
5pub trait MatchOne: FirstElem {
6    type Pattern: ?Sized;
7
8    /// Match one element or first element
9    ///
10    /// Match `'-'` Please write at the beginning or end, e.g `"a-z-"`
11    ///
12    /// # Examples
13    ///
14    /// ```
15    /// # use char_classes::MatchOne;
16    /// assert_eq!(Some('a'), 'a'.match_one("a-c"));
17    /// assert_eq!(Some('b'), 'b'.match_one("a-c"));
18    /// assert_eq!(None,      '-'.match_one("a-c"));
19    /// assert_eq!(Some('-'), '-'.match_one("a-"));
20    ///
21    /// assert_eq!(Some('a'), "ab".match_one("a"));
22    /// assert_eq!(None,      "ab".match_one("b"));
23    /// ```
24    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out>;
25
26    /// Call [`.match_one(pattern).is_some()`](#tymethod.match_one)
27    fn is_match_one(&self, pattern: &Self::Pattern) -> bool {
28        self.match_one(pattern).is_some()
29    }
30}
31
32impl<T: MatchOne> MatchOne for Option<T> {
33    type Pattern = T::Pattern;
34
35    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
36        self.as_ref().and_then(|val| val.match_one(pattern))
37    }
38}
39
40impl<T: MatchOne + ?Sized> MatchOne for &T {
41    type Pattern = T::Pattern;
42
43    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
44        (**self).match_one(pattern)
45    }
46}
47
48impl<T: MatchOne + ?Sized> MatchOne for &mut T {
49    type Pattern = T::Pattern;
50
51    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
52        (**self).match_one(pattern)
53    }
54}
55
56impl MatchOne for char {
57    type Pattern = str;
58
59    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
60        match_impl(pattern.chars(), self).then_some(*self)
61    }
62}
63
64impl MatchOne for u8 {
65    type Pattern = [u8];
66
67    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
68        match_impl(pattern.iter().copied(), self).then_some(*self)
69    }
70}
71
72impl MatchOne for str {
73    type Pattern = str;
74
75    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
76        self.chars().next().match_one(pattern)
77    }
78}
79
80impl MatchOne for () {
81    type Pattern = ();
82
83    fn match_one(&self, _pattern: &Self::Pattern) -> Option<Self::Out> {
84        None
85    }
86}
87
88impl<T: MatchOne> MatchOne for [T] {
89    type Pattern = T::Pattern;
90
91    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
92        self.first().match_one(pattern)
93    }
94}
95
96impl<T: MatchOne, const N: usize> MatchOne for [T; N] {
97    type Pattern = T::Pattern;
98
99    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
100        self.first().match_one(pattern)
101    }
102}
103
104macro_rules! impl_first_elem_trivial {
105    ($($ty:ty),+ $(,)?) => {
106        $(
107            impl FirstElem for $ty {
108                type Out = $ty;
109
110                fn first_elem(&self) -> Option<Self::Out> {
111                    Some(*self)
112                }
113            }
114        )+
115    };
116}
117
118/// Get the first element
119pub trait FirstElem {
120    type Out;
121
122    fn first_elem(&self) -> Option<Self::Out>;
123}
124
125impl_first_elem_trivial! {
126    u8,
127    u16,
128    u32,
129    u64,
130    u128,
131    i8,
132    i16,
133    i32,
134    i64,
135    i128,
136    char,
137    f32,
138    f64,
139}
140
141impl FirstElem for () {
142    type Out = core::convert::Infallible;
143
144    fn first_elem(&self) -> Option<Self::Out> {
145        None
146    }
147}
148
149impl<T: FirstElem + ?Sized> FirstElem for &T {
150    type Out = T::Out;
151
152    fn first_elem(&self) -> Option<Self::Out> {
153        (**self).first_elem()
154    }
155}
156
157impl<T: FirstElem + ?Sized> FirstElem for &mut T {
158    type Out = T::Out;
159
160    fn first_elem(&self) -> Option<Self::Out> {
161        (**self).first_elem()
162    }
163}
164
165impl<T: FirstElem> FirstElem for Option<T> {
166    type Out = T::Out;
167
168    fn first_elem(&self) -> Option<Self::Out> {
169        self.as_ref().and_then(T::first_elem)
170    }
171}
172
173impl FirstElem for str {
174    type Out = char;
175
176    fn first_elem(&self) -> Option<Self::Out> {
177        self.chars().next()
178    }
179}
180
181impl<T: FirstElem> FirstElem for [T] {
182    type Out = T::Out;
183
184    fn first_elem(&self) -> Option<Self::Out> {
185        self.first().and_then(T::first_elem)
186    }
187}
188
189impl<T: FirstElem, const N: usize> FirstElem for [T; N] {
190    type Out = T::Out;
191
192    fn first_elem(&self) -> Option<Self::Out> {
193        self.first().and_then(T::first_elem)
194    }
195}
196
197/// Call [`MatchOne::is_match_one`]
198///
199/// Match `'-'` Please write at the beginning or end, e.g `"a-z-"`
200///
201/// # Examples
202///
203/// ```
204/// # use char_classes::any;
205/// assert!(any("ab",       'a'));
206/// assert!(any("ab",       'b'));
207/// assert!(any("a-c",      'a'));
208/// assert!(any("a-c",      'b'));
209/// assert!(any("a-ce-f",   'e'));
210/// assert!(any("a-ce-",    '-'));
211/// assert!(any("a-ce-",    'e'));
212/// assert!(any("a-c",      Some('b')));
213/// assert!(any("a-c",      ['b', 'd']));
214/// assert!(any("a-c",      "bd"));
215///
216/// assert!(! any("a-c",    '-'));
217/// assert!(! any("a-ce-",  'f'));
218///
219/// assert!(! any("a-c",    None::<char>));
220/// assert!(! any("a-c",    ['d', 'b']));
221/// assert!(! any("a-c",    "db"));
222/// ```
223pub fn any<T>(pattern: impl AsRef<T::Pattern>, val: T) -> bool
224where T: MatchOne,
225{
226    val.is_match_one(pattern.as_ref())
227}
228
229fn match_impl<I>(pattern: I, val: &I::Item) -> bool
230where I: IntoIterator,
231      I::Item: PartialOrd + From<u8> + Copy,
232{
233    let pat = &mut pattern.into_iter();
234    let dash = &I::Item::from(b'-');
235
236    let Some(mut first) = pat.next() else {
237        return false;
238    };
239
240    while let Some(ref cur) = pat.next() {
241        if first == *val {
242            return true;
243        }
244        if cur != dash {
245            first = *cur;
246        } else if let Some(to) = pat.next() {
247            if (first..=to).contains(val) {
248                return true;
249            }
250            if let Some(next) = pat.next() {
251                first = next;
252            } else {
253                return false;
254            }
255        } else {
256            first = *cur;
257        }
258    }
259
260    first == *val
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[test]
268    fn basic_pattern() {
269        let datas = [
270            ("a", 'a'),
271            ("ab", 'a'),
272            ("ba", 'a'),
273            ("bac", 'a'),
274            ("bca", 'a'),
275            ("a-e", 'c'),
276            ("a-c", 'c'),
277            ("a-bc", 'c'),
278            ("a-bc", 'a'),
279            ("a-bc", 'b'),
280            ("你好", '你'),
281            ("你好", '好'),
282            ("a-", 'a'),
283            ("a-", '-'),
284            ("-a", '-'),
285            ("-", '-'),
286        ];
287
288        for (pat, val) in datas {
289            assert!(match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
290        }
291    }
292
293    #[test]
294    fn basic_not_pattern() {
295        let datas = [
296            ("", 'a'),
297            ("a-b", '-'),
298            ("a-", 'c'),
299            ("a-", 'b'),
300        ];
301
302        for (pat, val) in datas {
303            assert!(! match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
304        }
305    }
306
307    #[test]
308    fn first_pattern() {
309        let datas = [
310            ("a", "a"),
311            ("你", "你好"),
312            ("a-c", "a"),
313            ("a-c", "b"),
314        ];
315
316        for (pat, val) in datas {
317            assert!(any(pat, val));
318        }
319    }
320
321    #[test]
322    fn first_not_pattern_rest() {
323        let datas = [
324            ("a", "da"),
325            ("好", "你好"),
326            ("a-c", "da"),
327            ("a-c", "db"),
328            ("ab", "db"),
329        ];
330
331        for (pat, val) in datas {
332            assert!(! any(pat, val));
333        }
334    }
335
336    #[test]
337    fn first_not_pattern_empty() {
338        let datas = [
339            ("a", ""),
340            ("a-c", ""),
341        ];
342
343        for (pat, val) in datas {
344            assert!(! any(pat, val));
345        }
346    }
347
348    #[test]
349    fn dash_range() {
350        let datas = [
351            ("+--", "+"),
352            ("+--", ","),
353            ("+--", "-"),
354            ("+--a", "+"),
355            ("+--a", ","),
356            ("+--a", "-"),
357            ("+--a", "a"),
358            ("--0", "-"),
359            ("--0", "."),
360            ("--0", "/"),
361            ("--0", "0"),
362            ("--0a", "-"),
363            ("--0a", "."),
364            ("--0a", "/"),
365            ("--0a", "0"),
366            ("--0a", "a"),
367            ("a-c-e-g", "a"),
368            ("a-c-e-g", "b"),
369            ("a-c-e-g", "c"),
370            ("a-c-e-g", "e"),
371            ("a-c-e-g", "f"),
372            ("a-c-e-g", "g"),
373            ("a-c-e-g", "-"),
374        ];
375
376        for (pat, val) in datas {
377            assert!(any(pat, val), "{pat:?} cannot pattern {val:?}");
378        }
379    }
380
381    #[test]
382    fn dash_range_not_pat() {
383        let datas = [
384            ("+--a", "0"),
385            ("---a", "0"),
386            ("a-c-e-g", "d"),
387        ];
388
389        for (pat, val) in datas {
390            assert!(! any(pat, val));
391        }
392    }
393}