Skip to main content

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/// ```
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
254pub mod functions {
255    use super::MatchOne;
256
257    /// Call [`MatchOne::is_match_one`]
258    ///
259    /// Match `'-'` Please write at the beginning or end, e.g `"a-z-"`
260    ///
261    /// # Examples
262    ///
263    /// ```
264    /// # use char_classes::functions::any;
265    /// assert!(any("ab",       'a'));
266    /// assert!(any("ab",       'b'));
267    /// assert!(any("a-c",      'a'));
268    /// assert!(any("a-c",      'b'));
269    /// assert!(any("a-ce-f",   'e'));
270    /// assert!(any("a-ce-",    '-'));
271    /// assert!(any("a-ce-",    'e'));
272    /// assert!(any("a-c",      Some('b')));
273    /// assert!(any("a-c",      ['b', 'd']));
274    /// assert!(any("a-c",      "bd"));
275    ///
276    /// assert!(! any("a-c",    '-'));
277    /// assert!(! any("a-ce-",  'f'));
278    ///
279    /// assert!(! any("a-c",    None::<char>));
280    /// assert!(! any("a-c",    ['d', 'b']));
281    /// assert!(! any("a-c",    "db"));
282    /// ```
283    pub fn any<T>(pattern: impl AsRef<T::Pattern>, val: T) -> bool
284    where T: MatchOne,
285    {
286        val.is_match_one(pattern.as_ref())
287    }
288}
289
290fn match_impl<I>(pattern: I, val: &I::Item) -> bool
291where I: IntoIterator,
292      I::Item: PartialOrd + From<u8> + Copy,
293{
294    let pat = &mut pattern.into_iter();
295    let dash = &I::Item::from(b'-');
296
297    let Some(mut first) = pat.next() else {
298        return false;
299    };
300
301    while let Some(ref cur) = pat.next() {
302        if first == *val {
303            return true;
304        }
305        if cur != dash {
306            first = *cur;
307        } else if let Some(to) = pat.next() {
308            if (first..=to).contains(val) {
309                return true;
310            }
311            if let Some(next) = pat.next() {
312                first = next;
313            } else {
314                return false;
315            }
316        } else {
317            first = *cur;
318        }
319    }
320
321    first == *val
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327    use functions::any;
328
329    #[test]
330    fn basic_pattern() {
331        let datas = [
332            ("a", 'a'),
333            ("ab", 'a'),
334            ("ba", 'a'),
335            ("bac", 'a'),
336            ("bca", 'a'),
337            ("a-e", 'c'),
338            ("a-c", 'c'),
339            ("a-bc", 'c'),
340            ("a-bc", 'a'),
341            ("a-bc", 'b'),
342            ("你好", '你'),
343            ("你好", '好'),
344            ("a-", 'a'),
345            ("a-", '-'),
346            ("-a", '-'),
347            ("-", '-'),
348        ];
349
350        for (pat, val) in datas {
351            assert!(match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
352        }
353    }
354
355    #[test]
356    fn basic_not_pattern() {
357        let datas = [
358            ("", 'a'),
359            ("a-b", '-'),
360            ("a-", 'c'),
361            ("a-", 'b'),
362        ];
363
364        for (pat, val) in datas {
365            assert!(! match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
366        }
367    }
368
369    #[test]
370    fn first_pattern() {
371        let datas = [
372            ("a", "a"),
373            ("你", "你好"),
374            ("a-c", "a"),
375            ("a-c", "b"),
376        ];
377
378        for (pat, val) in datas {
379            assert!(any(pat, val));
380        }
381    }
382
383    #[test]
384    fn first_not_pattern_rest() {
385        let datas = [
386            ("a", "da"),
387            ("好", "你好"),
388            ("a-c", "da"),
389            ("a-c", "db"),
390            ("ab", "db"),
391        ];
392
393        for (pat, val) in datas {
394            assert!(! any(pat, val));
395        }
396    }
397
398    #[test]
399    fn first_not_pattern_empty() {
400        let datas = [
401            ("a", ""),
402            ("a-c", ""),
403        ];
404
405        for (pat, val) in datas {
406            assert!(! any(pat, val));
407        }
408    }
409
410    #[test]
411    fn dash_range() {
412        let datas = [
413            ("+--", "+"),
414            ("+--", ","),
415            ("+--", "-"),
416            ("+--a", "+"),
417            ("+--a", ","),
418            ("+--a", "-"),
419            ("+--a", "a"),
420            ("--0", "-"),
421            ("--0", "."),
422            ("--0", "/"),
423            ("--0", "0"),
424            ("--0a", "-"),
425            ("--0a", "."),
426            ("--0a", "/"),
427            ("--0a", "0"),
428            ("--0a", "a"),
429            ("a-c-e-g", "a"),
430            ("a-c-e-g", "b"),
431            ("a-c-e-g", "c"),
432            ("a-c-e-g", "e"),
433            ("a-c-e-g", "f"),
434            ("a-c-e-g", "g"),
435            ("a-c-e-g", "-"),
436        ];
437
438        for (pat, val) in datas {
439            assert!(any(pat, val), "{pat:?} cannot pattern {val:?}");
440        }
441    }
442
443    #[test]
444    fn dash_range_not_pat() {
445        let datas = [
446            ("+--a", "0"),
447            ("---a", "0"),
448            ("a-c-e-g", "d"),
449        ];
450
451        for (pat, val) in datas {
452            assert!(! any(pat, val));
453        }
454    }
455}