1#![no_std]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5#[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 fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out>;
67
68 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
160pub 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
239pub 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}