chj_rustbin/parse/
parsers.rs

1use std::any::type_name;
2use std::fmt::Display;
3use std::str::FromStr;
4
5use genawaiter::rc::Gen;
6
7use crate::parse::parse_error::ParseError;
8
9use super::parse_error::{Backing, IntoOwningBacking, StringParseContext};
10
11#[derive(Debug, Copy, PartialEq, Eq)]
12pub struct ParseableStr<'t, B: Backing> {
13    pub position: usize,
14    pub backing: &'t B,
15    pub s: &'t str,
16}
17
18impl<'t, B: Backing> Clone for ParseableStr<'t, B> {
19    fn clone(&self) -> Self {
20        let ParseableStr {
21            position,
22            backing,
23            s,
24        } = self;
25        Self {
26            position: *position,
27            backing: *backing,
28            s,
29        }
30    }
31}
32
33impl<'t, B: Backing> ParseableStr<'t, B> {
34    pub fn new(backing: &'t B) -> Self {
35        Self {
36            position: 0,
37            backing,
38            s: backing.as_ref(),
39        }
40    }
41
42    /// Panics if to is before self.
43    pub fn position_difference(&self, to: &Self) -> usize {
44        to.position
45            .checked_sub(self.position)
46            .expect("to is after self")
47    }
48
49    /// Panics if to is before self.
50    pub fn up_to(&self, to: &Self) -> Self {
51        let len = self.position_difference(to);
52        Self {
53            position: self.position,
54            backing: self.backing,
55            s: &self.s[0..len],
56        }
57    }
58
59    /// Return the end of this string, with the correct position.
60    pub fn eos(&self) -> ParseableStr<'t, B> {
61        ParseableStr {
62            position: self.position + self.s.len(),
63            backing: self.backing,
64            s: "",
65        }
66    }
67
68    // just Deref? vs. accidental use of extracting methods.
69
70    pub fn is_empty(&self) -> bool {
71        self.s.is_empty()
72    }
73
74    pub fn len(&self) -> usize {
75        self.s.len()
76    }
77
78    pub fn first(&self) -> Option<char> {
79        self.s.chars().next()
80    }
81
82    /// You usually want `drop_str` instead.
83    pub fn starts_with(&self, s: &str) -> bool {
84        self.s.starts_with(s)
85    }
86}
87
88impl<'s, B: Backing> IntoOwningBacking<B::Owned> for ParseableStr<'s, B>
89where
90    &'s B: Backing,
91    B::Owned: Backing,
92{
93    type Owning = StringParseContext<B::Owned>;
94
95    fn into_owning_backing(self) -> Self::Owning {
96        StringParseContext {
97            position: self.position,
98            backing: self.backing.to_owned(),
99        }
100    }
101}
102
103// XX hmm why both IntoOwningParseContext an From ?  Now thinking
104// about From and Into instead of ToOwned and Borrow?
105
106impl<'s, B: Backing> From<ParseableStr<'s, B>> for StringParseContext<&'s B>
107where
108    &'s B: Backing,
109{
110    fn from(value: ParseableStr<'s, B>) -> Self {
111        let ParseableStr {
112            position,
113            backing,
114            s: _,
115        } = value;
116        StringParseContext { position, backing }
117    }
118}
119
120impl<'r, 's, B: Backing> From<&'r ParseableStr<'s, B>>
121    for StringParseContext<&'s B>
122where
123    &'s B: Backing,
124{
125    fn from(value: &'r ParseableStr<'s, B>) -> Self {
126        let ParseableStr {
127            position,
128            backing,
129            s: _,
130        } = *value;
131        StringParseContext { position, backing }
132    }
133}
134
135#[derive(Debug, thiserror::Error)]
136#[error("expected {:?}", self.needle)]
137pub struct ExpectedString<'t, B: Backing> {
138    pub needle: &'t str,
139    pub context: ParseableStr<'t, B>,
140}
141
142#[derive(Debug, PartialEq, thiserror::Error)]
143#[error("expected {}", self.desc)]
144pub struct Expected<'t, B: Backing> {
145    pub desc: &'t str,
146    pub context: ParseableStr<'t, B>,
147}
148
149#[derive(Debug, PartialEq, thiserror::Error)]
150#[error("cannot parse as {type_}: {error}")]
151pub struct FromStrError {
152    // Don't be crazy and get the type name in the Error trait, as
153    // that would preclude passing FromStrError as dyn.
154    pub type_: &'static str,
155    pub error: String,
156    pub position: usize,
157}
158
159impl<'t, B: Backing> From<Box<ExpectedString<'t, B>>>
160    for ParseError<StringParseContext<&'t B>>
161where
162    &'t B: Backing,
163{
164    fn from(e: Box<ExpectedString<'t, B>>) -> Self {
165        // Do not use parseerror! macro, since the frame here isn't
166        // interesting, and could sometimes save an allocation.
167        ParseError {
168            message: e.to_string(),
169            context: e.context.into(),
170            backtrace: Vec::new(),
171        }
172    }
173}
174
175impl<'t, B: Backing> From<Box<Expected<'t, B>>>
176    for ParseError<StringParseContext<&'t B>>
177where
178    &'t B: Backing,
179{
180    fn from(e: Box<Expected<'t, B>>) -> Self {
181        // Do not use parseerror! macro, since the frame here isn't
182        // interesting, and could sometimes save an allocation.
183        ParseError {
184            message: e.to_string(),
185            context: e.context.into(),
186            backtrace: Vec::new(),
187        }
188    }
189}
190
191#[derive(Debug, thiserror::Error)]
192pub enum ParseFailure {
193    #[error("unexpected end of input")]
194    Eos,
195    #[error("invalid character")]
196    InvalidCharacter,
197}
198// pub struct ParseError {
199//     failure: ParseFailure,
200//     position: usize
201// }
202
203impl<'t, B: Backing> From<&'t B> for ParseableStr<'t, B> {
204    fn from(s: &'t B) -> Self {
205        ParseableStr {
206            position: 0,
207            backing: s,
208            s: s.as_ref(),
209        }
210    }
211}
212
213pub trait FromParseableStr<'s, B: Backing + 's>: Sized
214where
215    &'s B: Backing,
216{
217    fn from_parseable_str(
218        s: &ParseableStr<'s, B>,
219    ) -> Result<Self, ParseError<StringParseContext<&'s B>>>;
220}
221
222#[derive(Debug, Clone)]
223pub struct Separator {
224    /// Note: `required` does not matter if `alternatives` is empty
225    /// (no separator is actually expected in that case).
226    pub required: bool,
227    pub alternatives: &'static [&'static str],
228}
229
230impl<'t, B: Backing> ParseableStr<'t, B> {
231    /// Parse the whole string via `FromStr`, returning error information.
232    pub fn parse<T: FromStr>(&self) -> Result<T, Box<FromStrError>>
233    where
234        T::Err: Display,
235    {
236        self.s.parse().map_err(|e: T::Err| {
237            FromStrError {
238                type_: type_name::<T>(),
239                error: e.to_string(),
240                position: self.position,
241            }
242            .into()
243        })
244    }
245
246    /// Try to parse the whole string via `FromStr`.
247    // try_ prefix is usually used for Result; use opt_, maybe_ ?
248    pub fn opt_parse<T: FromStr>(&self) -> Option<T> {
249        self.s.parse().ok()
250    }
251
252    /// Skip the given number of bytes from the beginning. Panics if
253    /// num_bytes goes beyond the end of self, and will lead to later
254    /// panics if the result is not pointing at a character boundary.
255    pub fn skip_bytes(&self, num_bytes: usize) -> ParseableStr<'t, B> {
256        let ParseableStr {
257            position,
258            backing,
259            s,
260        } = self;
261        Self {
262            position: position + num_bytes,
263            backing,
264            s: &s[num_bytes..],
265        }
266    }
267
268    /// Split at the given number of bytes from the beginning. Panics if
269    /// mid > len, or not pointing at a character boundary.
270    pub fn split_at(
271        &self,
272        mid: usize,
273    ) -> (ParseableStr<'t, B>, ParseableStr<'t, B>) {
274        let ParseableStr {
275            position,
276            backing,
277            s,
278        } = *self;
279        (
280            Self {
281                position,
282                backing,
283                s: &s[..mid],
284            },
285            Self {
286                position: position + mid,
287                backing,
288                s: &s[mid..],
289            },
290        )
291    }
292
293    /// Trim whitespace from start end end.
294    pub fn trim(&self) -> ParseableStr<'t, B> {
295        let s1 = self.s.trim_start();
296        Self {
297            s: s1.trim_end(),
298            backing: self.backing,
299            position: self.position + (self.len() - s1.len()),
300        }
301    }
302
303    /// Trim whitespace from the end.
304    pub fn trim_start(&self) -> ParseableStr<'t, B> {
305        let s1 = self.s.trim_start();
306        Self {
307            s: s1,
308            backing: self.backing,
309            position: self.position + (self.len() - s1.len()),
310        }
311    }
312
313    /// Trim whitespace from the end.
314    pub fn trim_end(&self) -> ParseableStr<'t, B> {
315        Self {
316            s: self.s.trim_end(),
317            backing: self.backing,
318            position: self.position,
319        }
320    }
321
322    /// Find the first occurrence of `needle` in self, return
323    /// the needle and what follows.
324    pub fn find_str(&self, needle: &str) -> Option<ParseableStr<'t, B>> {
325        let ParseableStr {
326            position,
327            backing,
328            s,
329        } = self;
330        let offset = s.find(needle)?;
331        Some(ParseableStr {
332            position: position + offset,
333            backing,
334            s: &s[offset..],
335        })
336    }
337
338    /// Find the first occurrence of `needle` in self, return the
339    /// needle itself (with the position information of where it was
340    /// found) and the rest after it.
341    pub fn find_str_rest(
342        &self,
343        needle: &str,
344    ) -> Option<(ParseableStr<'t, B>, ParseableStr<'t, B>)> {
345        let ParseableStr {
346            position,
347            backing,
348            s,
349        } = self;
350        let offset = s.find(needle)?;
351        Some((
352            ParseableStr {
353                position: position + offset,
354                backing,
355                s: &s[offset..offset + needle.len()],
356            },
357            ParseableStr {
358                position: position + offset + needle.len(),
359                backing,
360                s: &s[offset + needle.len()..],
361            },
362        ))
363    }
364
365    /// Find the first occurrence of `needle` in self, return
366    /// the rest after it.
367    pub fn after_str(&self, needle: &str) -> Option<ParseableStr<'t, B>> {
368        let ParseableStr {
369            position,
370            backing,
371            s,
372        } = self;
373        let pos = s.find(needle)?;
374        let offset = pos + needle.len();
375        Some(ParseableStr {
376            position: position + offset,
377            backing,
378            s: &s[offset..],
379        })
380    }
381
382    /// Expect `beginning` at the start of self, if so return the
383    /// remainder after it.
384    pub fn drop_str(&self, beginning: &str) -> Option<ParseableStr<'t, B>> {
385        let ParseableStr {
386            position,
387            backing,
388            s,
389        } = *self;
390        if s.starts_with(beginning) {
391            Some(ParseableStr {
392                position: position + beginning.len(),
393                backing,
394                s: &s[beginning.len()..],
395            })
396        } else {
397            None
398        }
399    }
400
401    /// Same as `drop_str` but returns an error mentioning
402    /// `beginning` if it doesn't match
403    pub fn expect_str(
404        &self,
405        beginning: &'t str,
406    ) -> Result<ParseableStr<'t, B>, Box<ExpectedString<'t, B>>> {
407        let ParseableStr {
408            position,
409            backing,
410            s,
411        } = *self;
412        self.drop_str(beginning).ok_or_else(|| {
413            ExpectedString {
414                needle: beginning,
415                context: ParseableStr {
416                    position,
417                    backing,
418                    s,
419                },
420            }
421            .into()
422        })
423    }
424
425    pub fn expect_str_or_eos(
426        &self,
427        beginning: &'t str,
428    ) -> Result<ParseableStr<'t, B>, Box<ExpectedString<'t, B>>> {
429        if self.is_empty() {
430            return Ok(self.clone());
431        }
432        self.expect_str(beginning)
433    }
434
435    // ExpectedString error carries the strings from the Separator
436    // which are 'static, but it also contains self now, thus lifetime
437    // 't.
438    pub fn expect_separator(
439        &self,
440        separator: &Separator,
441    ) -> Result<ParseableStr<'t, B>, Box<ExpectedString<'t, B>>> {
442        let mut last_e = None;
443        for &sep in separator.alternatives {
444            match self.expect_str(sep) {
445                Ok(s) => return Ok(s),
446                Err(e) => last_e = Some(e),
447            }
448        }
449        if separator.required {
450            if let Some(last_e) = last_e {
451                // XX again should 'merge' error messages instead
452                Err(last_e)
453            } else {
454                Ok(self.clone())
455            }
456        } else {
457            Ok(self.clone())
458        }
459    }
460
461    /// Find the first occurrence of `needle` in self, return the part
462    /// left of it.
463    pub fn take_until_str(&self, needle: &str) -> Option<ParseableStr<'t, B>> {
464        let ParseableStr {
465            position,
466            backing,
467            s,
468        } = *self;
469        let pos = s.find(needle)?;
470        Some(ParseableStr {
471            position,
472            backing,
473            s: &s[0..pos],
474        })
475    }
476
477    /// Split at the first occurrence of `needle`, returning the parts
478    /// before and after it.
479    pub fn split_at_str(
480        &self,
481        needle: &str,
482    ) -> Option<(ParseableStr<'t, B>, ParseableStr<'t, B>)> {
483        let ParseableStr {
484            position,
485            backing,
486            s,
487        } = *self;
488        let pos = s.find(needle)?;
489        Some((
490            ParseableStr {
491                position,
492                backing,
493                s: &s[0..pos],
494            },
495            ParseableStr {
496                position: position + pos + needle.len(),
497                backing,
498                s: &s[pos + needle.len()..],
499            },
500        ))
501    }
502
503    /// Take every character for which `pred` returns true, return the
504    /// string making up those characters and the remainder.
505    pub fn take_while(
506        &self,
507        mut pred: impl FnMut(char) -> bool,
508    ) -> (ParseableStr<'t, B>, ParseableStr<'t, B>) {
509        let ParseableStr {
510            position,
511            backing,
512            s,
513        } = *self;
514        for (pos, c) in s.char_indices() {
515            if !pred(c) {
516                return (
517                    ParseableStr {
518                        position,
519                        backing,
520                        s: &s[0..pos],
521                    },
522                    ParseableStr {
523                        position: position + pos,
524                        backing,
525                        s: &s[pos..],
526                    },
527                );
528            }
529        }
530        (
531            self.clone(),
532            ParseableStr {
533                s: "",
534                position: position + s.len(),
535                backing,
536            },
537        )
538    }
539
540    /// Expect 1 character for which `pred` must return true, return
541    /// the string making up the remainder.
542    pub fn expect1_matching(
543        &self,
544        pred: impl FnOnce(char) -> bool,
545        desc: &'t str,
546    ) -> Result<ParseableStr<'t, B>, Box<Expected<'t, B>>> {
547        let ParseableStr {
548            position,
549            backing,
550            s,
551        } = self;
552        let err = || {
553            Err(Expected {
554                desc,
555                context: self.clone(),
556            }
557            .into())
558        };
559        if s.is_empty() {
560            return err();
561        }
562        let mut cs = s.char_indices();
563        let (_, c) = cs.next().unwrap();
564        if !pred(c) {
565            return err();
566        }
567        let (pos, _) = cs.next().unwrap_or_else(|| (s.len(), ' '));
568        Ok(ParseableStr {
569            position: position + pos,
570            backing,
571            s: &s[pos..],
572        })
573    }
574
575    pub fn take_identifier(
576        &self,
577    ) -> Result<(ParseableStr<'t, B>, ParseableStr<'t, B>), Box<Expected<'t, B>>>
578    {
579        let rest = self.expect1_matching(
580            |c| c.is_ascii_lowercase() && (c.is_ascii_alphabetic() || c == '_'),
581            "[a-z_] followed by [a-z0-9_]*",
582        )?;
583        let rest = rest.drop_while(|c| {
584            c.is_ascii_lowercase() && (c.is_ascii_alphanumeric() || c == '_')
585        });
586        let ParseableStr {
587            position,
588            backing,
589            s,
590        } = *self;
591        Ok((
592            ParseableStr {
593                position,
594                backing,
595                s: &s[0..(rest.position - position)],
596            },
597            rest,
598        ))
599    }
600
601    pub fn drop_while(
602        &self,
603        mut pred: impl FnMut(char) -> bool,
604    ) -> ParseableStr<'t, B> {
605        let ParseableStr {
606            position,
607            backing,
608            s,
609        } = self;
610        for (pos, c) in s.char_indices() {
611            if !pred(c) {
612                return ParseableStr {
613                    position: position + pos,
614                    backing,
615                    s: &s[pos..],
616                };
617            }
618        }
619        ParseableStr {
620            position: position + s.len(),
621            backing,
622            s: "",
623        }
624    }
625
626    /// Take `n` characters matching `pred`. Does not check whether
627    /// there are more than `n` characters matching `pred`, just
628    /// returns what was found so far.
629    pub fn take_n_while(
630        &self,
631        n: usize,
632        pred: impl FnMut(char) -> bool,
633        desc: &'t str,
634    ) -> Result<(ParseableStr<'t, B>, ParseableStr<'t, B>), Box<Expected<'t, B>>>
635    {
636        self.take_nrange_while(n, n, pred, desc)
637    }
638
639    /// Take `n_min` to `n_max` (inclusive) characters matching
640    /// `pred`. Does not check whether there are more than `n_max`
641    /// characters matching `pred`, just returns what was found so
642    /// far.
643    pub fn take_nrange_while(
644        &self,
645        n_min: usize,
646        n_max: usize, // inclusive
647        mut pred: impl FnMut(char) -> bool,
648        desc: &'t str,
649    ) -> Result<(ParseableStr<'t, B>, ParseableStr<'t, B>), Box<Expected<'t, B>>>
650    {
651        let ParseableStr {
652            position,
653            backing,
654            s,
655        } = *self;
656        let mut cs = s.char_indices();
657        let mut failed_pos = None;
658        for i in 0..n_max {
659            if let Some((pos, c)) = cs.next() {
660                if !pred(c) {
661                    if i < n_min {
662                        // Should it report the begin of the n characters
663                        // or the character failing? Or use Cow and
664                        // generate a new message saying where the begin
665                        // is? Or use another Expected type that carries
666                        // the info, rather.
667                        return Err(Expected {
668                            desc,
669                            context: ParseableStr {
670                                s,
671                                backing,
672                                position: position + pos,
673                            },
674                        }
675                        .into());
676                    }
677                    failed_pos = Some(pos);
678                    break;
679                }
680            } else {
681                // Eos
682                if i >= n_min {
683                    failed_pos = Some(s.len());
684                    break;
685                } else {
686                    return Err(Expected {
687                        desc,
688                        context: ParseableStr {
689                            s,
690                            backing,
691                            position: position + s.len(),
692                        },
693                    }
694                    .into());
695                }
696            }
697        }
698        let pos;
699        if let Some(failed_pos) = failed_pos {
700            pos = failed_pos;
701        } else {
702            (pos, _) = cs.next().unwrap_or_else(|| (s.len(), ' '));
703        }
704        Ok((
705            ParseableStr {
706                position,
707                backing,
708                s: &s[0..pos],
709            },
710            ParseableStr {
711                position: position + pos,
712                backing,
713                s: &s[pos..],
714            },
715        ))
716    }
717
718    pub fn drop_whitespace(&self) -> ParseableStr<'t, B> {
719        self.drop_while(|c| c.is_whitespace())
720    }
721
722    /// Split on a fixed string, `separator`; the `separator` string
723    /// is not included in the returned parts. If
724    /// `omit_empty_last_item` is true, if there's an empty string
725    /// after the last separator, it is not reported as an item.
726    pub fn split_str<'r, 'n>(
727        &'r self,
728        separator: &'n str,
729        omit_empty_last_item: bool,
730    ) -> Box<dyn Iterator<Item = ParseableStr<'t, B>> + 'n>
731    where
732        't: 'n,
733        'r: 'n,
734    {
735        Box::new(
736            Gen::new(|co| async move {
737                let mut rest = self.clone();
738                loop {
739                    if omit_empty_last_item && rest.is_empty() {
740                        break;
741                    }
742                    if let Some(p2) = rest.find_str(separator) {
743                        co.yield_(rest.up_to(&p2)).await;
744                        rest = p2.skip_bytes(separator.len());
745                    } else {
746                        co.yield_(rest).await;
747                        break;
748                    }
749                }
750            })
751            .into_iter(),
752        )
753    }
754}
755
756// pub fn expect_alternatives_2(
757//     s: ParseableStr,
758//     a: impl FnOnce(ParseableStr) -> Result<ParseableStr, ParseError>,
759//     b: impl FnOnce(ParseableStr) -> Result<ParseableStr, ParseError>,
760// ) -> Result<ParseableStr, ParseError> {
761//     match a(s) {
762//         Ok(s) => Ok(s),
763//         Err(e1) => match b(s) {
764//             Ok(s) => Ok(s),
765//             // Return the first error. Merging the errors wouldn't
766//             // really work because of the single position our api
767//             // wants to return (sigh).
768//             Err(_) => Err(e1)
769//         }
770//     }
771// }
772
773#[cfg(test)]
774mod tests {
775    use super::*;
776
777    // Oh my; should ParseableStr be able to own the backing, too? But
778    // that will be ugly to implement in multiple ways.
779    macro_rules! let_parseable {
780        { $backingvar:ident, $var:ident = $backing:expr } => {
781            let $backingvar: String = $backing.into();
782            let $var = ParseableStr::from(&$backingvar);
783        }
784    }
785    macro_rules! new {
786        { $backingvar:ident, $pos:expr, $string:expr } => {
787            ParseableStr {
788                position: $pos,
789                backing: &$backingvar,
790                s: $string
791            }
792        }
793    }
794
795    #[test]
796    fn t_split_at_str() {
797        let_parseable! { backing, s = "foo -- bar" }
798        assert_eq!(s.split_at_str("---"), None);
799
800        let_parseable! { backing, s = "foo -- bar" }
801        assert_eq!(
802            s.split_at_str("--"),
803            Some((
804                ParseableStr {
805                    s: "foo ",
806                    backing: &backing,
807                    position: 0
808                },
809                ParseableStr {
810                    s: " bar",
811                    backing: &backing,
812                    position: 6
813                },
814            ))
815        );
816    }
817
818    #[test]
819    fn t_split_str() {
820        {
821            let_parseable! { backing, s = "foo - bar- baz -  bam" }
822            let r1: Vec<ParseableStr<_>> = s.split_str(" - ", false).collect();
823            assert_eq!(
824                &*r1,
825                &[
826                    new!(backing, 0, "foo"),
827                    new!(backing, 6, "bar- baz"),
828                    new!(backing, 17, " bam")
829                ]
830            );
831        }
832        {
833            let_parseable! { backing, s = "foo - bar- baz - " }
834            let r1: Vec<ParseableStr<_>> = s.split_str(" - ", false).collect();
835            assert_eq!(
836                &*r1,
837                &[
838                    new!(backing, 0, "foo"),
839                    new!(backing, 6, "bar- baz"),
840                    new!(backing, 17, "")
841                ]
842            );
843        }
844        {
845            let_parseable! { backing, s = "foo - bar- baz - " }
846            let r1: Vec<ParseableStr<_>> = s.split_str(" - ", true).collect();
847            assert_eq!(
848                &*r1,
849                &[new!(backing, 0, "foo"), new!(backing, 6, "bar- baz"),]
850            );
851        }
852        {
853            let_parseable! { backing, s = " - " }
854            let r1: Vec<ParseableStr<_>> = s.split_str(" - ", false).collect();
855            assert_eq!(&*r1, &[new!(backing, 0, ""), new!(backing, 3, ""),]);
856        }
857        {
858            let_parseable! { backing, s = " - " }
859            let r1: Vec<ParseableStr<_>> = s.split_str(" - ", true).collect();
860            assert_eq!(&*r1, &[new!(backing, 0, ""),]);
861        }
862        {
863            let_parseable! { backing, s = "" }
864            let r1: Vec<ParseableStr<_>> = s.split_str(" - ", false).collect();
865            assert_eq!(&*r1, &[new!(backing, 0, ""),]);
866        }
867        {
868            let_parseable! { backing, s = "" }
869            let r1: Vec<ParseableStr<_>> = s.split_str(" - ", true).collect();
870            assert_eq!(&*r1, &[]);
871        }
872    }
873
874    #[test]
875    fn t_take_while() {
876        let_parseable! { backing, s = "" }
877        assert_eq!(
878            s.take_while(|c| c.is_alphanumeric()),
879            (new!(backing, 0, ""), new!(backing, 0, ""),)
880        );
881
882        let_parseable! { backing, s = " abc def" }
883        assert_eq!(
884            s.take_while(|c| c.is_alphanumeric()),
885            (new!(backing, 0, ""), new!(backing, 0, " abc def"),)
886        );
887
888        let_parseable! { backing, s = "abc def" }
889        assert_eq!(
890            s.take_while(|c| c.is_alphanumeric()),
891            (new!(backing, 0, "abc"), new!(backing, 3, " def"),)
892        );
893
894        let_parseable! { backing, s = "abcdef" }
895        assert_eq!(
896            s.take_while(|c| c.is_alphanumeric()),
897            (new!(backing, 0, "abcdef"), new!(backing, 6, ""),)
898        );
899    }
900
901    #[test]
902    fn t_take_n_while() {
903        fn t<'s, 't>(
904            s: &'s ParseableStr<'t, String>,
905            n: usize,
906        ) -> Result<
907            (ParseableStr<'t, String>, ParseableStr<'t, String>),
908            Box<Expected<'t, String>>,
909        > {
910            s.take_n_while(
911                n,
912                |c| c.is_ascii_digit(),
913                "digit as part of year number",
914            )
915        }
916
917        fn ok<'s, 't>(
918            backing: &'t String,
919            s0: &'t str,
920            position: usize,
921            s1: &'t str,
922        ) -> Result<
923            (ParseableStr<'t, String>, ParseableStr<'t, String>),
924            Box<Expected<'t, String>>,
925        > {
926            Ok((
927                ParseableStr {
928                    position: 0,
929                    backing,
930                    s: s0,
931                },
932                ParseableStr {
933                    position,
934                    backing,
935                    s: s1,
936                },
937            ))
938        }
939
940        let err = |backing, position, desc, s| {
941            Err(Box::new(Expected {
942                desc,
943                context: ParseableStr {
944                    position,
945                    backing,
946                    s,
947                },
948            }))
949        };
950
951        macro_rules! test {
952            { t($sourcestr:expr, $n:expr), ok($matchstr:expr, $matchpos:expr, $reststr:expr) } => {
953                let_parseable!{ backing, s = $sourcestr }
954                assert_eq!(t(&s, $n), ok(&backing, $matchstr, $matchpos, $reststr));
955            };
956            { t($sourcestr:expr, $n:expr), err($pos:expr, $msg:expr, $matchstr:expr) } => {
957                let_parseable!{ backing, s = $sourcestr }
958                assert_eq!(t(&s, $n), err(&backing, $pos, $msg, $matchstr));
959            }
960        }
961
962        test!(t("2024-10", 4), ok("2024", 4, "-10"));
963        test!(t("2024-10", 3), ok("202", 3, "4-10"));
964        test!(t("2024-10", 0), ok("", 0, "2024-10"));
965        // XX in these err tests: is it correct to carry the whole string in `s`?:
966        test!(
967            t("2024-10", 5),
968            err(4, "digit as part of year number", "2024-10")
969        );
970        test!(t("2024", 5), err(4, "digit as part of year number", "2024"));
971        test!(t("2024", 4), ok("2024", 4, ""));
972    }
973
974    #[test]
975    fn t_take_nrange_while() {
976        fn t<'s, 't>(
977            s: &ParseableStr<'t, String>,
978            n_min: usize,
979            n_max: usize,
980        ) -> Result<
981            (ParseableStr<'t, String>, ParseableStr<'t, String>),
982            Box<Expected<'t, String>>,
983        > {
984            s.take_nrange_while(
985                n_min,
986                n_max,
987                |c| c.is_ascii_digit(),
988                "digit as part of year number",
989            )
990        }
991
992        fn ok<'s, 't>(
993            backing: &'t String,
994            s0: &'t str,
995            position: usize,
996            s1: &'t str,
997        ) -> Result<
998            (ParseableStr<'t, String>, ParseableStr<'t, String>),
999            Box<Expected<'t, String>>,
1000        > {
1001            Ok((
1002                ParseableStr {
1003                    position: 0,
1004                    backing,
1005                    s: s0,
1006                },
1007                ParseableStr {
1008                    position,
1009                    backing,
1010                    s: s1,
1011                },
1012            ))
1013        }
1014
1015        let err = |backing, position, desc, s| {
1016            Err(Box::new(Expected {
1017                desc,
1018                context: ParseableStr {
1019                    position,
1020                    backing,
1021                    s,
1022                },
1023            }))
1024        };
1025
1026        macro_rules! test {
1027            { t($sourcestr:expr, $n_min:expr, $n_max:expr), ok($matchstr:expr, $matchpos:expr, $reststr:expr) } => {
1028                let_parseable!{ backing, s = $sourcestr }
1029                assert_eq!(t(&s, $n_min, $n_max), ok(&backing, $matchstr, $matchpos, $reststr));
1030            };
1031            { t($sourcestr:expr, $n_min:expr, $n_max:expr), err($pos:expr, $msg:expr, $matchstr:expr) } => {
1032                let_parseable!{ backing, s = $sourcestr }
1033                assert_eq!(t(&s, $n_min, $n_max), err(&backing, $pos, $msg, $matchstr));
1034            }
1035        }
1036
1037        test!(t("2024-10", 3, 4), ok("2024", 4, "-10"));
1038        test!(t("2024-10", 1, 3), ok("202", 3, "4-10"));
1039        test!(t("2024-10", 8, 3), ok("202", 3, "4-10"));
1040        test!(t("2024-10", 3, 0), ok("", 0, "2024-10"));
1041        test!(t("2024-10", 4, 5), ok("2024", 4, "-10"));
1042        // XX is it correct to carry the whole string in `s`?:
1043        test!(
1044            t("2024-10 abc", 5, 6),
1045            err(4, "digit as part of year number", "2024-10 abc")
1046        );
1047        test!(t("2024", 3, 4), ok("2024", 4, ""));
1048        test!(t("2024x", 4, 5), ok("2024", 4, "x"));
1049        test!(t("2024", 4, 5), ok("2024", 4, ""));
1050    }
1051}