Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions src/combinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2753,6 +2753,81 @@ where
go_extra!(U);
}

/// See [`Parser::then_or_not`]
pub struct ThenOrNot<A, B> {
pub(crate) parser_a: A,
pub(crate) parser_b: B,
}

impl<A: Copy, B: Copy> Copy for ThenOrNot<A, B> {}
impl<A: Clone, B: Clone> Clone for ThenOrNot<A, B> {
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<A, B>
where
I: Input<'src>,
E: ParserExtra<'src, I>,
A: Parser<'src, I, T, E>,
B: Parser<'src, I, U, E>,
{
fn go<M: Mode>(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult<M, Option<(T, U)>>
where
Self: Sized,
{
let before = inp.save();
let Ok(pre) = self.parser_a.go::<Emit>(inp) else {
inp.rewind(before);
return Ok(M::bind(|| None));
};
let post = self.parser_b.go::<M>(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<A, B>
where
I: Input<'src>,
E: ParserExtra<'src, I>,
A: Parser<'src, I, T, E>,
B: Parser<'src, I, U, E>,
{
type IterState<M: Mode> = Option<M::Output<T>>;

fn make_iter<M: Mode>(
&self,
inp: &mut InputRef<'src, '_, I, E>,
) -> PResult<Emit, Self::IterState<M>> {
let before = inp.save();
Ok(match self.parser_a.go::<M>(inp) {
Ok(v) => Some(v),
Err(()) => {
inp.rewind(before);
None
}
})
}

fn next<M: Mode>(
&self,
inp: &mut InputRef<'src, '_, I, E>,
state: &mut Self::IterState<M>,
) -> IPResult<M, (T, U)> {
let Some(pre) = state.take() else {
return Ok(None);
};
let post = self.parser_b.go::<M>(inp)?;
Ok(Some(M::combine(pre, post, |a, b| (a, b))))
}
}

// /// See [`Parser::or_else`].
// #[derive(Copy, Clone)]
// pub struct OrElse<A, F> {
Expand Down
25 changes: 25 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Simple<char>>>(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<U, B: Parser<'src, I, U, E>>(self, other: B) -> ThenOrNot<Self, B>
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.
Expand Down