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