shrimple_parser/
pattern.rs

1//! Abstractions for working with patterns.
2
3use {
4    crate::{
5        tuple::{first, map_second, Tuple},
6        Input, Parser, ParsingError,
7    },
8    core::{ops::Not, convert::Infallible},
9};
10
11/// This trait represents an object that can be matched onto a string.
12/// This includes functions, characters, [arrays of] characters, strings, but also custom patterns
13/// like [`NotEscaped`]
14///
15/// See built-in patterns and parser adapters for patterns in the [`pattern`](self) module
16///
17/// Hint: on the success path, the 1st element of the return tuple is the rest of the input (with
18/// or without the matched pattern at the start)
19pub trait Pattern {
20    /// The return values are (rest of the input, matched fragment at the beginning).
21    ///
22    /// # Errors
23    /// In the case of no match, the original `input` is returned as the [`Err`] variant.
24    ///
25    /// Used by [`parse`].
26    fn immediate_match<I: Input>(&self, input: I) -> Result<(I, I), I>;
27
28    /// The return values are (rest of the input, contiguous matched fragments from the beginning).
29    ///
30    /// 0 is also a valid number of matches.
31    ///
32    /// Used by [`parse_while`]
33    #[expect(
34        clippy::unwrap_used,
35        reason = "this will only panic if the pattern does"
36    )]
37    fn immediate_matches<I: Input>(&self, input: I) -> (I, I) {
38        let mut rest = Some(input.clone());
39        let rest_ptr = loop {
40            match self.immediate_match(rest.take().unwrap()) {
41                Ok((x, _)) => rest = Some(x),
42                Err(x) => break x.as_ptr(),
43            }
44        };
45        let input_ptr = input.as_ptr();
46        input.split_at(rest_ptr as usize - input_ptr as usize).rev()
47    }
48
49    /// Like [`Pattern::immediate_matches`], but also counts the number of matches.
50    ///
51    /// Used by the [`Pattern`] impl of [`NotEscaped`]
52    #[expect(
53        clippy::unwrap_used,
54        reason = "this will only panic if the pattern does"
55    )]
56    fn immediate_matches_counted<I: Input>(&self, input: I) -> (I, (I, usize)) {
57        let mut rest = Some(input.clone());
58        let mut n = 0;
59        let rest_ptr = loop {
60            match self.immediate_match(rest.take().unwrap()) {
61                Ok((x, _)) => {
62                    rest = Some(x);
63                    n += 1;
64                }
65                Err(x) => break x.as_ptr(),
66            }
67        };
68        let input_ptr = input.as_ptr();
69        input
70            .split_at(rest_ptr as usize - input_ptr as usize)
71            .rev()
72            .map_second(|s| (s, n))
73    }
74
75    /// Like [`Pattern::immediate_match`], but matches at the end of `input`.
76    /// The return values are (the input before the match, the match)
77    ///
78    /// # Errors
79    /// In the case of no match, the original `input` is returned as the [`Err`] variant.
80    ///
81    /// Used by the [`Pattern`] impl of [`NotEscaped`]
82    fn trailing_match<I: Input>(&self, input: I) -> Result<(I, I), I>;
83
84    /// Like [`Pattern::immediate_matches_counted`], but matches at the end of `input`,
85    /// and doesn't return the matched fragment of the input.
86    ///
87    /// Used by the [`Pattern`] impl of [`NotEscaped`]
88    #[expect(
89        clippy::unwrap_used,
90        reason = "this will only panic if the pattern does"
91    )]
92    fn trailing_matches_counted<I: Input>(&self, input: I) -> (I, usize) {
93        let mut rest = Some(input);
94        let mut n = 0;
95        loop {
96            match self.trailing_match(rest.take().unwrap()) {
97                Ok((before, _)) => {
98                    rest = Some(before);
99                    n += 1;
100                }
101                Err(rest) => break (rest, n),
102            }
103        }
104    }
105
106    /// The return values are (the match + rest of the input, (string before the match, the match)).
107    ///
108    /// # Errors
109    /// Returns the provided `input` unchanged in the [`Err`] variant if there's no match.
110    ///
111    /// Used by [`parse_until`].
112    fn first_match<I: Input>(&self, input: I) -> Result<(I, (I, I)), I>;
113
114    /// Like [`Pattern::first_match`], but the match is excluded from the rest of the input.
115    ///
116    /// # Errors
117    /// Returns the provided `input` unchanged in the [`Err`] variant if there's no match.
118    ///
119    /// Used by [`parse_until_ex`].
120    fn first_match_ex<I: Input>(&self, input: I) -> Result<(I, (I, I)), I>;
121
122    /// Get the pattern by reference to avoid moving it, which will happen in generic code
123    ///
124    /// Do not override this method.
125    fn by_ref(&self) -> impl Pattern + Copy {
126        #[repr(transparent)]
127        struct Ref<'this, T: ?Sized>(&'this T);
128
129        impl<T: ?Sized> Clone for Ref<'_, T> {
130            fn clone(&self) -> Self {
131                *self
132            }
133        }
134
135        impl<T: ?Sized> Copy for Ref<'_, T> {}
136
137        impl<T: Pattern + ?Sized> Pattern for Ref<'_, T> {
138            fn immediate_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
139                T::immediate_match(self.0, input)
140            }
141
142            fn immediate_matches<I: Input>(&self, input: I) -> (I, I) {
143                T::immediate_matches(self.0, input)
144            }
145
146            fn immediate_matches_counted<I: Input>(&self, input: I) -> (I, (I, usize)) {
147                T::immediate_matches_counted(self.0, input)
148            }
149
150            fn trailing_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
151                T::trailing_match(self.0, input)
152            }
153
154            fn trailing_matches_counted<I: Input>(&self, input: I) -> (I, usize) {
155                T::trailing_matches_counted(self.0, input)
156            }
157
158            fn first_match<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
159                T::first_match(self.0, input)
160            }
161
162            fn first_match_ex<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
163                T::first_match_ex(self.0, input)
164            }
165        }
166
167        Ref(self)
168    }
169
170    /// Combine `self` and another pattern into a pattern that matches either of them in a
171    /// short-circuiting manner, with `self` tried first.
172    ///
173    /// Do not override this method.
174    fn or<Other: Pattern>(self, other: Other) -> Union<Self, Other>
175    where
176        Self: Sized,
177    {
178        Union(self, other)
179    }
180}
181
182impl<F: Fn(char) -> bool> Pattern for F {
183    fn immediate_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
184        match input.chars().next().filter(|c| self(*c)) {
185            Some(c) => Ok(input.split_at(c.len_utf8()).rev()),
186            None => Err(input),
187        }
188    }
189
190    fn immediate_matches<I: Input>(&self, input: I) -> (I, I) {
191        let mid = input.find(|c| !self(c)).unwrap_or(input.len());
192        input.split_at(mid).rev()
193    }
194
195    fn immediate_matches_counted<I: Input>(&self, input: I) -> (I, (I, usize)) {
196        let mut char_index = 0;
197        let byte_index = input
198            .char_indices()
199            .inspect(|_| char_index += 1)
200            .find_map(|(bi, c)| self(c).not().then_some(bi))
201            .inspect(|_| char_index -= 1)
202            .unwrap_or(input.len());
203        input
204            .split_at(byte_index)
205            .rev()
206            .map_second(|s| (s, char_index))
207    }
208
209    fn trailing_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
210        match input.strip_suffix(self).map(str::len) {
211            Some(len) => Ok(input.split_at(len)),
212            None => Err(input),
213        }
214    }
215
216    fn first_match<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
217        match input.char_indices().find(|(_, c)| self(*c)) {
218            Some((at, ch)) => {
219                let (before, after) = input.split_at(at);
220                let r#match = after.clone().before(ch.len_utf8());
221                Ok((after, (before, r#match)))
222            }
223            None => Err(input),
224        }
225    }
226
227    fn first_match_ex<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
228        match input.char_indices().find(|(_, c)| self(*c)) {
229            Some((at, ch)) => {
230                let (before, after) = input.split_at(at);
231                let (r#match, after) = after.split_at(ch.len_utf8());
232                Ok((after, (before, r#match)))
233            }
234            None => Err(input),
235        }
236    }
237}
238
239/// This is a specialised, optimised impl for matching any `char` in the array. For a more general
240/// pattern combinator, use the [`Union`] pattern by calling the [`Pattern::or`] method
241impl<const N: usize> Pattern for [char; N] {
242    // TODO: specialise for `[char; N]`
243    fn immediate_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
244        match input.strip_prefix(self) {
245            Some(rest) => {
246                let matched_pat_len = input.len() - rest.len();
247                Ok(input.split_at(matched_pat_len).rev())
248            }
249            None => Err(input),
250        }
251    }
252
253    fn trailing_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
254        match input.strip_suffix(self) {
255            Some(rest) => {
256                let rest_len = rest.len();
257                Ok(input.split_at(rest_len))
258            }
259            None => Err(input),
260        }
261    }
262
263    fn first_match<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
264        match input.find(self) {
265            Some(at) => {
266                let (prev, match_and_rest) = input.split_at(at);
267                let matched_pat_len = match_and_rest.chars().next().map_or(0, char::len_utf8);
268                let r#match = match_and_rest.clone().before(matched_pat_len);
269                Ok((match_and_rest, (prev, r#match)))
270            }
271            None => Err(input),
272        }
273    }
274
275    fn first_match_ex<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
276        match input.find(self) {
277            Some(at) => {
278                let (prev, match_and_rest) = input.split_at(at);
279                let matched_pat_len = match_and_rest.chars().next().map_or(0, char::len_utf8);
280                let (r#match, rest) = match_and_rest.split_at(matched_pat_len);
281                Ok((rest, (prev, r#match)))
282            }
283            None => Err(input),
284        }
285    }
286}
287
288impl Pattern for &str {
289    fn immediate_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
290        if input.starts_with(*self) {
291            Ok(input.split_at(self.len()).rev())
292        } else {
293            Err(input)
294        }
295    }
296
297    fn immediate_matches<I: Input>(&self, input: I) -> (I, I) {
298        let rest_len = input.trim_start_matches(self).len();
299        let input_len = input.len();
300        input.split_at(input_len - rest_len).rev()
301    }
302
303    fn immediate_matches_counted<I: Input>(&self, input: I) -> (I, (I, usize)) {
304        self.immediate_matches(input)
305            .map_second(|s| (s.len().checked_div(self.len()).unwrap_or(0), s).rev())
306    }
307
308    fn trailing_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
309        if input.ends_with(self) {
310            let mid = input.len() - self.len();
311            Ok(input.split_at(mid))
312        } else {
313            Err(input)
314        }
315    }
316
317    fn trailing_matches_counted<I: Input>(&self, input: I) -> (I, usize) {
318        let trimmed_len = input.trim_end_matches(self).len();
319        let input_len = input.len();
320        (
321            input.before(trimmed_len),
322            (input_len - trimmed_len) / self.len(),
323        )
324    }
325
326    fn first_match<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
327        match input.find(*self) {
328            Some(at) => {
329                let (before, after) = input.split_at(at);
330                let r#match = after.clone().before(self.len());
331                Ok((after, (before, r#match)))
332            }
333            None => Err(input),
334        }
335    }
336
337    fn first_match_ex<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
338        match input.find(*self) {
339            Some(at) => {
340                let (before, after) = input.split_at(at);
341                let (r#match, after) = after.split_at(self.len());
342                Ok((after, (before, r#match)))
343            }
344            None => Err(input),
345        }
346    }
347}
348
349impl Pattern for char {
350    fn immediate_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
351        if input.starts_with(*self) {
352            Ok(input.split_at(self.len_utf8()).rev())
353        } else {
354            Err(input)
355        }
356    }
357
358    fn immediate_matches<I: Input>(&self, input: I) -> (I, I) {
359        let rest_len = input.trim_start_matches(*self).len();
360        let input_len = input.len();
361        input.split_at(input_len - rest_len).rev()
362    }
363
364    fn immediate_matches_counted<I: Input>(&self, input: I) -> (I, (I, usize)) {
365        self.immediate_matches(input)
366            .map_second(|s| (s.len() / self.len_utf8(), s).rev())
367    }
368
369    fn trailing_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
370        if input.ends_with(*self) {
371            let mid = input.len() - self.len_utf8();
372            Ok(input.split_at(mid))
373        } else {
374            Err(input)
375        }
376    }
377
378    fn trailing_matches_counted<I: Input>(&self, input: I) -> (I, usize) {
379        let trimmed_len = input.trim_end_matches(*self).len();
380        let input_len = input.len();
381        (
382            input.before(trimmed_len),
383            (input_len - trimmed_len) / self.len_utf8(),
384        )
385    }
386
387    fn first_match<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
388        match input.find(*self) {
389            Some(at) => {
390                let (before, after) = input.split_at(at);
391                let r#match = after.clone().before(self.len_utf8());
392                Ok((after, (before, r#match)))
393            }
394            None => Err(input),
395        }
396    }
397
398    fn first_match_ex<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
399        match input.find(*self) {
400            Some(at) => {
401                let (before, after) = input.split_at(at);
402                let (r#match, after) = after.split_at(self.len_utf8());
403                Ok((after, (before, r#match)))
404            }
405            None => Err(input),
406        }
407    }
408}
409
410/// Pattern that matches pattern `Inner` not escaped by `Prefix`.
411/// "escaped" here means that the pattern `Inner` is preceded by an odd number
412/// of contiguous `Prefix`es.
413///
414/// For example, for a pattern `NotEscaped('\', '0')`, the strings "0", "\\0" & "\\\\\\0" will have
415/// a match, but the strings "\0", "\\ \0" & "\\\\\\\0" won't.
416pub struct NotEscaped<Prefix: Pattern, Inner: Pattern>(pub Prefix, pub Inner);
417
418impl<Prefix: Pattern, Inner: Pattern> Pattern for NotEscaped<Prefix, Inner> {
419    fn immediate_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
420        self.1.immediate_match(input)
421    }
422
423    fn trailing_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
424        let (rest, r#match) = self.1.trailing_match(input.clone())?;
425        let (rest, n_prefixes) = self.0.trailing_matches_counted(rest);
426        (n_prefixes % 2 == 0)
427            .then_some((rest, r#match))
428            .ok_or(input)
429    }
430
431    fn trailing_matches_counted<I: Input>(&self, input: I) -> (I, usize) {
432        let (rest, n) = self.1.trailing_matches_counted(input);
433        if n == 0 {
434            return (rest, 0);
435        }
436        let no_1st_prefix = match self.0.trailing_match(rest.clone()) {
437            Ok((x, _)) => x,
438            Err(rest) => return (rest, n),
439        };
440        let (_, n_prefixes_minus_one) = self.0.trailing_matches_counted(no_1st_prefix.clone());
441        if n_prefixes_minus_one % 2 != 0 {
442            (rest, n)
443        } else {
444            (no_1st_prefix, n - 1)
445        }
446    }
447
448    fn first_match<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
449        let mut rest = input.clone();
450        while !rest.is_empty() {
451            let (before, r#match);
452            (rest, (before, r#match)) = self.1.first_match(rest)?;
453            let before = match self.0.trailing_match(before) {
454                Ok((x, _)) => x,
455                Err(before) => return Ok((rest, (before, r#match))),
456            };
457            let (before, n_prefixes_minus_one) = self.0.trailing_matches_counted(before);
458            if n_prefixes_minus_one % 2 != 0 {
459                return Ok((rest, (before, r#match)));
460            }
461        }
462        Err(input)
463    }
464
465    fn first_match_ex<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
466        let mut rest = input.clone();
467        loop {
468            let (before, r#match);
469            (rest, (before, r#match)) = self.1.first_match_ex(rest)?;
470            let Ok((before, _)) = self.0.trailing_match(before) else {
471                let index = r#match.as_ptr() as usize - input.as_ptr() as usize;
472                let before = input.before(index);
473                return Ok((rest, (before, r#match)));
474            };
475            let (_, n_prefixes_minus_one) = self.0.trailing_matches_counted(before);
476            if n_prefixes_minus_one % 2 != 0 {
477                let index = r#match.as_ptr() as usize - input.as_ptr() as usize;
478                let before = input.before(index);
479                return Ok((rest, (before, r#match)));
480            }
481        }
482    }
483}
484
485/// A pattern that matches anything.
486pub struct AnyChar;
487
488impl Pattern for AnyChar {
489    fn immediate_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
490        match input.chars().next() {
491            Some(ch) => Ok(input.split_at(ch.len_utf8()).rev()),
492            None => Err(input),
493        }
494    }
495
496    fn trailing_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
497        match input.chars().next_back() {
498            Some(ch) => Ok(input.split_at(ch.len_utf8())),
499            None => Err(input),
500        }
501    }
502
503    fn first_match<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
504        Ok((input.clone(), (I::default(), input)))
505    }
506
507    fn first_match_ex<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
508        Ok((I::default(), (I::default(), input)))
509    }
510}
511
512/// A pattern that matches either of the 2 patterns in a short-circuiting manner,
513/// with `self` tried first. May be created by [`Pattern::or`] for convenience.
514///
515/// # Note
516/// If you want to match either of N chars, use an array of them as a pattern instead, as this
517/// struct has a general impl that may miss optimisations applicable to the case of `[char; N]`
518/// being the pattern. However, unlike the array pattern, the combination of patterns using this
519/// struct is not commutative, since the second pattern is only tried if the former has not been
520/// found in the input.
521pub struct Union<P1: Pattern, P2: Pattern>(pub P1, pub P2);
522
523impl<P1: Pattern, P2: Pattern> Pattern for Union<P1, P2> {
524    fn immediate_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
525        self.0
526            .immediate_match(input)
527            .or_else(|input| self.1.immediate_match(input))
528    }
529
530    fn trailing_match<I: Input>(&self, input: I) -> Result<(I, I), I> {
531        self.0
532            .trailing_match(input)
533            .or_else(|input| self.1.trailing_match(input))
534    }
535
536    fn first_match<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
537        self.0
538            .first_match(input)
539            .or_else(|input| self.1.first_match(input))
540    }
541
542    fn first_match_ex<I: Input>(&self, input: I) -> Result<(I, (I, I)), I> {
543        self.0
544            .first_match_ex(input)
545            .or_else(|input| self.1.first_match_ex(input))
546    }
547}
548
549/// Parses 1 instance of pattern `pat`.
550///
551/// # Errors
552/// The returned parser returns a recoverable error if the pattern didn't match at the beginning of
553/// the input.
554pub fn parse<In: Input, Reason>(pat: impl Pattern) -> impl Parser<In, In, Reason> {
555    move |input| {
556        pat.immediate_match(input)
557            .map_err(ParsingError::new_recoverable)
558    }
559}
560
561/// Parses contiguous instances of pattern `pat`.
562///
563/// The returned parser never returns an error, if no matches are found at the start of the input,
564/// the returned string is empty (but also points to the start of the input)
565///
566/// See also [`parse_until`], [`parse_until_ex`].
567pub fn parse_while<In: Input, Reason>(pat: impl Pattern) -> impl Parser<In, In, Reason> {
568    move |input| Ok(pat.immediate_matches(input))
569}
570
571/// Parses a span of the input until a match of pattern `pat` is met.
572///
573/// The returned rest of the input will still have the match.
574///
575/// The returned parser never returns an error, if `pred` returns `false` for all the characters
576/// in the input, then the output is the entire input, and the rest of the input is an empty string.
577///
578/// See also [`parse_while`], [`parse_until_ex`].
579pub fn parse_until<In: Input, Reason>(pat: impl Pattern) -> impl Parser<In, In, Reason> {
580    move |input| {
581        Ok({
582            pat.first_match(input)
583                .map_or_else(|input| (In::default(), input), map_second(first))
584        })
585    }
586}
587
588/// Like [`parse_until`], but also removes the match of `pat` from the rest of the input.
589///
590/// # Errors
591/// Unlike [`parse_until`], this parser returns a recoverable error if `pred` returned `false` for
592/// all the characters in the input.
593pub fn parse_until_ex<In: Input, Reason>(pat: impl Pattern) -> impl Parser<In, In, Reason> {
594    move |input| {
595        pat.first_match_ex(input)
596            .map(map_second(first))
597            .map_err(ParsingError::new_recoverable)
598    }
599}
600
601/// Parse a balanced group of `open` & `close` patterns.
602///
603/// # Errors
604/// - If no initial `open` was found, a recoverable error is returned.
605/// - If the end was reached before a matching `close` pattern, a fatal error is returned.
606///
607/// An example use of this is parsing balanced parentheses:
608/// ```rust
609/// # fn main() {
610/// use shrimple_parser::{pattern::parse_group, ParsingError};
611/// let src = "(foo ()) bar";
612/// assert_eq!(parse_group('(', ')')(src), Ok((" bar", "foo ()")));
613///
614/// let src = "(oops";
615/// assert_eq!(parse_group('(', ')')(src), Err(ParsingError::new("(oops", ())));
616/// # }
617/// ```
618#[expect(
619    clippy::missing_panics_doc,
620    clippy::unwrap_used,
621    reason = "Panics only if the pattern does"
622)]
623pub fn parse_group<In: Input>(open: impl Pattern, close: impl Pattern) -> impl Parser<In, In, ()> {
624    move |input| {
625        let (open, close) = (open.by_ref(), close.by_ref());
626        let (mut rest, _) = parse(open)(input.clone())?;
627        let after_1st_open = rest.clone();
628        let mut depth = 0usize;
629        loop {
630            let mut group;
631            match parse_until_ex::<_, Infallible>(close)(rest) {
632                Ok(x) => (rest, group) = x,
633                Err(_) => break Err(ParsingError::new(input, ())),
634            }
635            let mut after_open = Some(group);
636            group = loop {
637                match parse_until_ex::<_, Infallible>(open)(after_open.take().unwrap()) {
638                    Ok((x, _)) => {
639                        depth += 1;
640                        after_open = Some(x);
641                    }
642                    Err(x) => break x.rest,
643                };
644            };
645            if depth == 0 {
646                let len = group.as_ptr() as usize + group.len() - after_1st_open.as_ptr() as usize;
647                break Ok((rest, after_1st_open.before(len)));
648            }
649            depth -= 1;
650        }
651    }
652}
653
654#[test]
655fn char_pat() {
656    assert_eq!(
657        parse_until_ex::<_, Infallible>('"')
658            .parse(r#"this is what they call a \"test\", right?" - he said"#),
659        Ok((
660            r#"test\", right?" - he said"#,
661            r"this is what they call a \"
662        )),
663    );
664}
665
666#[test]
667fn not_escaped_pat() {
668    assert_eq!(
669        parse_until_ex::<_, Infallible>(NotEscaped('\\', '"'))
670            .parse(r#"this is what they call a \"test\", right?" - he said"#),
671        Ok((" - he said", r#"this is what they call a \"test\", right?"#)),
672    );
673}
674
675#[test]
676fn str_pat() {
677    assert_eq!(parse::<_, Infallible>("abc")("abcdef"), Ok(("def", "abc")));
678}
679
680#[test]
681fn array_pat() {
682    assert_eq!(
683        parse_until_ex::<_, Infallible>([';', '\''])("abc;def'xyz"),
684        Ok(("def'xyz", "abc"))
685    );
686}
687
688#[test]
689fn union_pat() {
690    let src = "abc;def'xyz";
691    assert_eq!(
692        parse_until_ex::<_, Infallible>(';'.or('\''))(src),
693        parse_until_ex([';', '\''])(src)
694    );
695}