-
Hi, I'm porting a nom-based project to winnow, and while the redesigned API is very nice, I've noticed my error messages have degraded significantly: with nom, For comparison, I've created two roughly equivalent programs for parsing digits. I ask them both to parse the invalid number "0xZ123", and then print the detailed error: nom versionuse nom::branch::alt;
use nom::bytes::complete::{tag, take_while1};
use nom::combinator::{all_consuming, cut, map_res};
use nom::error::{context, VerboseError};
use nom::sequence::preceded;
use nom::IResult;
fn number(input: &str) -> IResult<&str, u64, VerboseError<&str>> {
context("number", alt((hex, decimal)))(input)
}
fn hex(input: &str) -> IResult<&str, u64, VerboseError<&str>> {
context(
"hex number",
preceded(
tag("0x"),
cut(context(
"hex digits",
map_res(take_while1(|c: char| c.is_ascii_hexdigit()), |s: &str| {
u64::from_str_radix(s, 16)
}),
)),
),
)(input)
}
fn decimal(input: &str) -> IResult<&str, u64, VerboseError<&str>> {
context(
"decimal number",
map_res(take_while1(|c: char| c.is_ascii_digit()), |s: &str| {
u64::from_str_radix(s, 10)
}),
)(input)
}
fn main() {
let test = "0xZ123";
// println!("{:#?}", number(test));
match all_consuming(number)(test) {
Ok((_, n)) => println!("number: {n}"),
Err(nom::Err::Error(e) | nom::Err::Failure(e)) => eprintln!("{e}"),
Err(err) => eprintln!("{err}"),
}
} Output:
winnow versionuse winnow::combinator::{alt, cut_err, preceded};
use winnow::error::{StrContext, TreeError};
use winnow::token::take_while;
use winnow::{PResult, Parser};
fn number<'i>(input: &mut &'i str) -> PResult<u64, TreeError<&'i str, StrContext>> {
alt((hex, decimal))
.context(StrContext::Label("number"))
.parse_next(input)
}
fn hex<'i>(input: &mut &'i str) -> PResult<u64, TreeError<&'i str, StrContext>> {
preceded(
"0x",
cut_err(
take_while(1.., |c: char| c.is_ascii_hexdigit())
.try_map(|s| u64::from_str_radix(s, 16))
.context(StrContext::Label("hex digits")),
),
)
.context(StrContext::Label("hex number"))
.parse_next(input)
}
fn decimal<'i>(input: &mut &'i str) -> PResult<u64, TreeError<&'i str, StrContext>> {
take_while(1.., |c: char| c.is_ascii_digit())
.try_map(|s| u64::from_str_radix(s, 10))
.context(StrContext::Label("decimal number"))
.parse_next(input)
}
fn main() {
let test = "0xZ123";
// println!("{:#?}", number.parse(test));
match number.parse(test) {
Ok(n) => println!("number: {n}"),
Err(err) => eprintln!("{err}"),
}
} Output:
I'm not sure if this is a bug or a difference in intention (the "invalid label at input" does convey a different idea from "in section label"), but stacking seems less useful. (Additionally: isn't that caret off by one character? Shouldn't it point to the Z?) |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
I did some quick experimentation by modifying winnow/src/combinator/parser.rs Lines 856 to 861 in 29ec665 My changes: trace(name, move |i: &mut I| {
let ci = i.clone();
(self.parser)
.parse_next(i)
.map_err(|err| err.add_context(&ci, self.context.clone()))
})
.parse_next(i) By cloning, the output matches what I expected:
At first glance, the performance impact of this change should be trivial: inputs are typically just a fat pointer, so cloning would be very cheap and increase the stack usage of |
Beta Was this translation helpful? Give feedback.
-
Fixed in #383 and released in 0.5.25 |
Beta Was this translation helpful? Give feedback.
I created #384 from this