Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.

Commit b81c34c

Browse files
authored
Add map_err_with() (#951)
* Add `map_err_with()` * Add warning about relying on `.slice()` and `.span()`
1 parent 7d36b51 commit b81c34c

3 files changed

Lines changed: 101 additions & 4 deletions

File tree

guide/meet_the_parsers.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,14 @@ Combinators that manipulate or emit errors, along with fallibly validating parse
9494
| Name | Example | Description |
9595
|---------------------------------|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
9696
| [`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. |
97-
| [`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). |
97+
| [`Parser::map_err_with`] | `a.map_err_with(...)` | Like [`Parser::map_err`], but provides access to metadata associated with the output. |
98+
| [`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). |
9899
| [`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. |
99100
| [`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. |
100101
| [`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. |
101102
| [`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. |
102103
| [`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. |
103-
| [`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. |
104+
| [`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. |
104105
| [`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"). |
105106

106107
### Text-oriented parsing

src/combinator.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3149,7 +3149,7 @@ where
31493149
Self: Sized,
31503150
{
31513151
(&self.parser)
3152-
.map_err_with_state(|e, _, _| (self.mapper)(e))
3152+
.map_err_with(|e, _| (self.mapper)(e))
31533153
.go::<M>(inp)
31543154
}
31553155

@@ -3191,7 +3191,6 @@ where
31913191
// go_extra!(O);
31923192
// }
31933193

3194-
// TODO: Remove combinator, replace with map_err_with
31953194
/// See [`Parser::map_err_with_state`].
31963195
#[derive(Copy, Clone)]
31973196
pub struct MapErrWithState<A, F> {
@@ -3237,6 +3236,50 @@ where
32373236
go_extra!(O);
32383237
}
32393238

3239+
/// See [`Parser::map_err_with`].
3240+
#[derive(Copy, Clone)]
3241+
pub struct MapErrWith<A, F> {
3242+
pub(crate) parser: A,
3243+
pub(crate) mapper: F,
3244+
}
3245+
3246+
impl<'src, I, O, E, A, F> Parser<'src, I, O, E> for MapErrWith<A, F>
3247+
where
3248+
I: Input<'src>,
3249+
E: ParserExtra<'src, I>,
3250+
A: Parser<'src, I, O, E>,
3251+
F: Fn(E::Error, &mut MapExtra<'src, '_, I, E>) -> E::Error,
3252+
{
3253+
#[inline(always)]
3254+
fn go<M: Mode>(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult<M, O>
3255+
where
3256+
Self: Sized,
3257+
{
3258+
let start = inp.cursor();
3259+
let old_alt = inp.take_alt();
3260+
let res = self.parser.go::<M>(inp);
3261+
let new_alt = inp.take_alt();
3262+
3263+
if res.is_ok() {
3264+
inp.errors.alt = old_alt;
3265+
if let Some(new_alt) = new_alt {
3266+
inp.add_alt_err(&new_alt.pos, new_alt.err);
3267+
}
3268+
} else {
3269+
// Can't fail!
3270+
let mut new_alt = new_alt.unwrap();
3271+
new_alt.err = (self.mapper)(new_alt.err, &mut MapExtra::new(&start, inp));
3272+
3273+
inp.errors.alt = old_alt;
3274+
inp.add_alt_err(&new_alt.pos, new_alt.err);
3275+
}
3276+
3277+
res
3278+
}
3279+
3280+
go_extra!(O);
3281+
}
3282+
32403283
/// See [`Parser::validate`]
32413284
pub struct Validate<A, OA, F> {
32423285
pub(crate) parser: A,

src/lib.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,6 +1997,29 @@ pub trait Parser<'src, I: Input<'src>, O, E: ParserExtra<'src, I> = extra::Defau
19971997
}
19981998
}
19991999

2000+
/// Map the primary error of this parser to another value, making use of the parser state and
2001+
/// context.
2002+
///
2003+
/// This function is useful for augmenting errors to allow them to include context in non context-free
2004+
/// languages, or provide contextual notes on possible causes.
2005+
///
2006+
/// The output type of this parser is `O`, the same as the original parser.
2007+
///
2008+
/// Note: Chumsky permits parsers to leave the input in an unspecified state after an error
2009+
/// occurs and backtracking begins. As such, the output of [`MapExtra::span`] and
2010+
/// [`MapExtra::slice`] is unspecified within this function.
2011+
///
2012+
fn map_err_with<F>(self, f: F) -> MapErrWith<Self, F>
2013+
where
2014+
Self: Sized,
2015+
F: Fn(E::Error, &mut MapExtra<'src, '_, I, E>) -> E::Error,
2016+
{
2017+
MapErrWith {
2018+
parser: self,
2019+
mapper: f,
2020+
}
2021+
}
2022+
20002023
/// Validate an output, producing non-terminal errors if it does not fulfill certain criteria.
20012024
/// The errors will not immediately halt parsing on this path, but instead it will continue,
20022025
/// potentially emitting one or more other errors, only failing after the pattern has otherwise
@@ -4018,6 +4041,36 @@ mod tests {
40184041
);
40194042
}
40204043

4044+
#[test]
4045+
fn map_err_with() {
4046+
use crate::LabelError;
4047+
4048+
let parser = just::<char, &str, extra::Err<_>>('#')
4049+
.repeated()
4050+
.count()
4051+
.ignore_with_ctx(just('"').map_err_with(move |e: Rich<char>, extras| {
4052+
println!("Found = {:?}", e.found());
4053+
println!("Expected = {:?}", e.expected().collect::<Vec<_>>());
4054+
println!("Span = {:?}", e.span());
4055+
println!("Context = {:?}", extras.ctx());
4056+
LabelError::<&str, String>::expected_found(
4057+
[format!("after {} hashes", extras.ctx())],
4058+
e.found().copied().map(Into::into),
4059+
*e.span(),
4060+
)
4061+
}));
4062+
4063+
let mut err: Rich<_> =
4064+
LabelError::<&str, char>::expected_found(['#'], Some('H'.into()), (3..4).into());
4065+
err = LabelError::<&str, String>::merge_expected_found(
4066+
err,
4067+
["after 3 hashes".into()],
4068+
Some('H'.into()),
4069+
(3..4).into(),
4070+
);
4071+
assert_eq!(parser.parse("###H").into_result(), Err(vec![err]));
4072+
}
4073+
40214074
#[test]
40224075
fn try_map() {
40234076
use crate::{DefaultExpected, LabelError};

0 commit comments

Comments
 (0)