diff --git a/src/combinator.rs b/src/combinator.rs index 3ba767d2..fdcbbe07 100644 --- a/src/combinator.rs +++ b/src/combinator.rs @@ -2753,6 +2753,81 @@ where go_extra!(U); } +/// See [`Parser::then_or_not`] +pub struct ThenOrNot { + pub(crate) parser_a: A, + pub(crate) parser_b: B, +} + +impl Copy for ThenOrNot {} +impl Clone for ThenOrNot { + fn clone(&self) -> Self { + ThenOrNot { + parser_a: self.parser_a.clone(), + parser_b: self.parser_b.clone(), + } + } +} + +impl<'src, I, T, U, E, A, B> Parser<'src, I, Option<(T, U)>, E> for ThenOrNot +where + I: Input<'src>, + E: ParserExtra<'src, I>, + A: Parser<'src, I, T, E>, + B: Parser<'src, I, U, E>, +{ + fn go(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult> + where + Self: Sized, + { + let before = inp.save(); + let Ok(pre) = self.parser_a.go::(inp) else { + inp.rewind(before); + return Ok(M::bind(|| None)); + }; + let post = self.parser_b.go::(inp)?; + Ok(M::combine(M::bind(move || pre), post, |a, b| Some((a, b)))) + } + + go_extra!(Option<(T, U)>); +} + +impl<'src, I, T, U, E, A, B> IterParser<'src, I, (T, U), E> for ThenOrNot +where + I: Input<'src>, + E: ParserExtra<'src, I>, + A: Parser<'src, I, T, E>, + B: Parser<'src, I, U, E>, +{ + type IterState = Option>; + + fn make_iter( + &self, + inp: &mut InputRef<'src, '_, I, E>, + ) -> PResult> { + let before = inp.save(); + Ok(match self.parser_a.go::(inp) { + Ok(v) => Some(v), + Err(()) => { + inp.rewind(before); + None + } + }) + } + + fn next( + &self, + inp: &mut InputRef<'src, '_, I, E>, + state: &mut Self::IterState, + ) -> IPResult { + let Some(pre) = state.take() else { + return Ok(None); + }; + let post = self.parser_b.go::(inp)?; + Ok(Some(M::combine(pre, post, |a, b| (a, b)))) + } +} + // /// See [`Parser::or_else`]. // #[derive(Copy, Clone)] // pub struct OrElse { diff --git a/src/lib.rs b/src/lib.rs index a9e7aa34..63adcb0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1957,6 +1957,31 @@ pub trait Parser<'src, I: Input<'src>, O, E: ParserExtra<'src, I> = extra::Defau } } + /// Parse another pattern if the current pattern succeeds. + /// + /// The output type of this parser is `Option<(O, U)>`, combining the outputs of both parsers if + /// the former succeed. + /// + /// # Examples + /// + /// ``` + /// # use chumsky::prelude::*; + /// let range = text::int::<_, extra::Err>>(10) + /// .then(just("..").then_or_not(text::int(10))); + /// assert_eq!(range.parse("1..2").into_result(), Ok(("1", Some(("..", "2"))))); + /// assert_eq!(range.parse("1").into_result(), Ok(("1", None))); + /// assert!(range.parse("1..").has_errors()); + /// ``` + fn then_or_not>(self, other: B) -> ThenOrNot + where + Self: Sized, + { + ThenOrNot { + parser_a: self, + parser_b: other, + } + } + // /// Map the primary error of this parser to a result. If the result is [`Ok`], the parser succeeds with that value. // /// // /// Note that, if the closure returns [`Err`], the parser will not consume any input.