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