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