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 mod functions {
255 use super::MatchOne;
256
257 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}