char_classes/
lib.rs

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