1#![no_std]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5#[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 fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out>;
82
83 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
175pub 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 fn any<T>(pattern: impl AsRef<T::Pattern>, val: T) -> bool
281where T: MatchOne,
282{
283 val.is_match_one(pattern.as_ref())
284}
285
286fn match_impl<I>(pattern: I, val: &I::Item) -> bool
287where I: IntoIterator,
288 I::Item: PartialOrd + From<u8> + Copy,
289{
290 let pat = &mut pattern.into_iter();
291 let dash = &I::Item::from(b'-');
292
293 let Some(mut first) = pat.next() else {
294 return false;
295 };
296
297 while let Some(ref cur) = pat.next() {
298 if first == *val {
299 return true;
300 }
301 if cur != dash {
302 first = *cur;
303 } else if let Some(to) = pat.next() {
304 if (first..=to).contains(val) {
305 return true;
306 }
307 if let Some(next) = pat.next() {
308 first = next;
309 } else {
310 return false;
311 }
312 } else {
313 first = *cur;
314 }
315 }
316
317 first == *val
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn basic_pattern() {
326 let datas = [
327 ("a", 'a'),
328 ("ab", 'a'),
329 ("ba", 'a'),
330 ("bac", 'a'),
331 ("bca", 'a'),
332 ("a-e", 'c'),
333 ("a-c", 'c'),
334 ("a-bc", 'c'),
335 ("a-bc", 'a'),
336 ("a-bc", 'b'),
337 ("你好", '你'),
338 ("你好", '好'),
339 ("a-", 'a'),
340 ("a-", '-'),
341 ("-a", '-'),
342 ("-", '-'),
343 ];
344
345 for (pat, val) in datas {
346 assert!(match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
347 }
348 }
349
350 #[test]
351 fn basic_not_pattern() {
352 let datas = [
353 ("", 'a'),
354 ("a-b", '-'),
355 ("a-", 'c'),
356 ("a-", 'b'),
357 ];
358
359 for (pat, val) in datas {
360 assert!(! match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
361 }
362 }
363
364 #[test]
365 fn first_pattern() {
366 let datas = [
367 ("a", "a"),
368 ("你", "你好"),
369 ("a-c", "a"),
370 ("a-c", "b"),
371 ];
372
373 for (pat, val) in datas {
374 assert!(any(pat, val));
375 }
376 }
377
378 #[test]
379 fn first_not_pattern_rest() {
380 let datas = [
381 ("a", "da"),
382 ("好", "你好"),
383 ("a-c", "da"),
384 ("a-c", "db"),
385 ("ab", "db"),
386 ];
387
388 for (pat, val) in datas {
389 assert!(! any(pat, val));
390 }
391 }
392
393 #[test]
394 fn first_not_pattern_empty() {
395 let datas = [
396 ("a", ""),
397 ("a-c", ""),
398 ];
399
400 for (pat, val) in datas {
401 assert!(! any(pat, val));
402 }
403 }
404
405 #[test]
406 fn dash_range() {
407 let datas = [
408 ("+--", "+"),
409 ("+--", ","),
410 ("+--", "-"),
411 ("+--a", "+"),
412 ("+--a", ","),
413 ("+--a", "-"),
414 ("+--a", "a"),
415 ("--0", "-"),
416 ("--0", "."),
417 ("--0", "/"),
418 ("--0", "0"),
419 ("--0a", "-"),
420 ("--0a", "."),
421 ("--0a", "/"),
422 ("--0a", "0"),
423 ("--0a", "a"),
424 ("a-c-e-g", "a"),
425 ("a-c-e-g", "b"),
426 ("a-c-e-g", "c"),
427 ("a-c-e-g", "e"),
428 ("a-c-e-g", "f"),
429 ("a-c-e-g", "g"),
430 ("a-c-e-g", "-"),
431 ];
432
433 for (pat, val) in datas {
434 assert!(any(pat, val), "{pat:?} cannot pattern {val:?}");
435 }
436 }
437
438 #[test]
439 fn dash_range_not_pat() {
440 let datas = [
441 ("+--a", "0"),
442 ("---a", "0"),
443 ("a-c-e-g", "d"),
444 ];
445
446 for (pat, val) in datas {
447 assert!(! any(pat, val));
448 }
449 }
450}