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