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