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