From c5b78b76bded1550f43fb9850d22c00768df0e06 Mon Sep 17 00:00:00 2001 From: Thomas M Kehrenberg Date: Mon, 19 Jan 2026 13:27:24 +0100 Subject: [PATCH 1/2] Add `map_err_with()` --- guide/meet_the_parsers.md | 5 ++-- src/combinator.rs | 47 +++++++++++++++++++++++++++++++++++-- src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/guide/meet_the_parsers.md b/guide/meet_the_parsers.md index df7e5f9e..0d6100db 100644 --- a/guide/meet_the_parsers.md +++ b/guide/meet_the_parsers.md @@ -94,13 +94,14 @@ Combinators that manipulate or emit errors, along with fallibly validating parse | Name | Example | Description | |---------------------------------|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [`Parser::map_err`] | `a.map_err(...)` | Parse a pattern. On failure, map the parser error to another value. Often used to customise error messages or add extra information to them. | -| [`Parser::map_err_with_state`] | `a.lazy()` | Like [`Parser::map_err`], but provides access to the parser state (see [`Parser::parse_with_state`] for more information). | +| [`Parser::map_err_with`] | `a.map_err_with(...)` | Like [`Parser::map_err`], but provides access to metadata associated with the output. | +| [`Parser::map_err_with_state`] | `a.map_err_with_state(...)` | Like [`Parser::map_err`], but provides access to the parser state (see [`Parser::parse_with_state`] for more information). | | [`Parser::try_foldl`] | `a.try_foldl(...)` | Left-fold the output of the parser into a single value, possibly failing during the reduction. If the function produces an error, the parser fails with that error. | | [`Parser::try_map`] | `a.try_map(...)` | Map the output of a parser using the given fallible mapping function. If the function produces an error, the parser fails with that error. | | [`Parser::try_map_with`] | `a.try_map_with(...)` | Map the output of a parser using the given fallible mapping function, with access to output metadata. If the function produces an error, the parser fails with that error. | | [`Parser::validate`] | `a.validate(...)` | Parse a pattern. On success, map the output to another value with the opportunity to emit extra secondary errors. Commonly used to check the validity of patterns in the parser. | | [`Parser::filter`] | `any().filter(char::is_lowercase)` | Parse a pattern and apply the given filtering function to the output. If the filter function returns [`false`], the parser fails. | -| [`Parser::filter_map`] | `any().filter_map(...) | Parse a pattern and apply the given filter-map function to the output. If the filter-map function returns [`None`], the parser fails. | +| [`Parser::filter_map`] | `any().filter_map(...)` | Parse a pattern and apply the given filter-map function to the output. If the filter-map function returns [`None`], the parser fails. | | [`Parser::labelled`] | `a.labelled("a")` | Parse a pattern, labelling it. What exactly this does depends on the error type, but it is generally used to give a pattern a more general name (for example, "expression"). | ### Text-oriented parsing diff --git a/src/combinator.rs b/src/combinator.rs index 6471707a..ba270604 100644 --- a/src/combinator.rs +++ b/src/combinator.rs @@ -3149,7 +3149,7 @@ where Self: Sized, { (&self.parser) - .map_err_with_state(|e, _, _| (self.mapper)(e)) + .map_err_with(|e, _| (self.mapper)(e)) .go::(inp) } @@ -3191,7 +3191,6 @@ where // go_extra!(O); // } -// TODO: Remove combinator, replace with map_err_with /// See [`Parser::map_err_with_state`]. #[derive(Copy, Clone)] pub struct MapErrWithState { @@ -3237,6 +3236,50 @@ where go_extra!(O); } +/// See [`Parser::map_err_with`]. +#[derive(Copy, Clone)] +pub struct MapErrWith { + pub(crate) parser: A, + pub(crate) mapper: F, +} + +impl<'src, I, O, E, A, F> Parser<'src, I, O, E> for MapErrWith +where + I: Input<'src>, + E: ParserExtra<'src, I>, + A: Parser<'src, I, O, E>, + F: Fn(E::Error, &mut MapExtra<'src, '_, I, E>) -> E::Error, +{ + #[inline(always)] + fn go(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult + where + Self: Sized, + { + let start = inp.cursor(); + let old_alt = inp.take_alt(); + let res = self.parser.go::(inp); + let new_alt = inp.take_alt(); + + if res.is_ok() { + inp.errors.alt = old_alt; + if let Some(new_alt) = new_alt { + inp.add_alt_err(&new_alt.pos, new_alt.err); + } + } else { + // Can't fail! + let mut new_alt = new_alt.unwrap(); + new_alt.err = (self.mapper)(new_alt.err, &mut MapExtra::new(&start, inp)); + + inp.errors.alt = old_alt; + inp.add_alt_err(&new_alt.pos, new_alt.err); + } + + res + } + + go_extra!(O); +} + /// See [`Parser::validate`] pub struct Validate { pub(crate) parser: A, diff --git a/src/lib.rs b/src/lib.rs index 16baa6af..30b15ff6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1997,6 +1997,25 @@ pub trait Parser<'src, I: Input<'src>, O, E: ParserExtra<'src, I> = extra::Defau } } + /// Map the primary error of this parser to another value, making use of the parser state and + /// context. + /// + /// This function is useful for augmenting errors to allow them to include context in non context-free + /// languages, or provide contextual notes on possible causes. + /// + /// The output type of this parser is `O`, the same as the original parser. + /// + fn map_err_with(self, f: F) -> MapErrWith + where + Self: Sized, + F: Fn(E::Error, &mut MapExtra<'src, '_, I, E>) -> E::Error, + { + MapErrWith { + parser: self, + mapper: f, + } + } + /// Validate an output, producing non-terminal errors if it does not fulfill certain criteria. /// The errors will not immediately halt parsing on this path, but instead it will continue, /// potentially emitting one or more other errors, only failing after the pattern has otherwise @@ -4018,6 +4037,36 @@ mod tests { ); } + #[test] + fn map_err_with() { + use crate::LabelError; + + let parser = just::>('#') + .repeated() + .count() + .ignore_with_ctx(just('"').map_err_with(move |e: Rich, extras| { + println!("Found = {:?}", e.found()); + println!("Expected = {:?}", e.expected().collect::>()); + println!("Span = {:?}", e.span()); + println!("Context = {:?}", extras.ctx()); + LabelError::<&str, String>::expected_found( + [format!("after {} hashes", extras.ctx())], + e.found().copied().map(Into::into), + *e.span(), + ) + })); + + let mut err: Rich<_> = + LabelError::<&str, char>::expected_found(['#'], Some('H'.into()), (3..4).into()); + err = LabelError::<&str, String>::merge_expected_found( + err, + ["after 3 hashes".into()], + Some('H'.into()), + (3..4).into(), + ); + assert_eq!(parser.parse("###H").into_result(), Err(vec![err])); + } + #[test] fn try_map() { use crate::{DefaultExpected, LabelError}; From 6fb8f5d4299e2872ec35c0701722b150737f633a Mon Sep 17 00:00:00 2001 From: Thomas M Kehrenberg Date: Fri, 23 Jan 2026 10:13:45 +0100 Subject: [PATCH 2/2] Add warning about relying on `.slice()` and `.span()` --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 30b15ff6..c63fe951 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2005,6 +2005,10 @@ pub trait Parser<'src, I: Input<'src>, O, E: ParserExtra<'src, I> = extra::Defau /// /// The output type of this parser is `O`, the same as the original parser. /// + /// Note: Chumsky permits parsers to leave the input in an unspecified state after an error + /// occurs and backtracking begins. As such, the output of [`MapExtra::span`] and + /// [`MapExtra::slice`] is unspecified within this function. + /// fn map_err_with(self, f: F) -> MapErrWith where Self: Sized,