我有一个小解析器,如果它解析的数字超出边界,则会失败,
use nom::IResult;
use nom::bytes::complete::tag;
use nom::character::complete::digit1;
use nom::combinator::fail;
fn dup(s: &str) -> IResult<&str, u64> {
let (s, _) = tag("dup")(s)?;
let (s, n) = digit1(s)?;
let n = match n {
"0" => 0,
"1" => 1,
"2" => 2,
_ => return fail(s), // FIXME: Annotate.
};
Ok((s, n))
}
(参见操场)
但我希望错误更有意义。
我如何在n
越界的上下文中注释此错误?
。代替fail()
,使用提供错误消息的东西。
或者以某种方式将解析器的一部分包装在提供此上下文的内容中。
我知道你可以创建自己的自定义ErrorKind
,但它可以做到没有吗?(当你有一个ErrorKind
,error_position!()
和error_node_position!()
宏将工作)
感谢masklin的回答(我已经标记为接受),添加自定义错误消息而不添加自定义错误类型的一种方法是使用VerboseError
和convert_error()
而不是默认的Error
,因为这能够嵌入上下文。下面是一个修改后的例子(也是在操场上):
use nom::IResult;
use nom::bytes::complete::tag;
use nom::character::complete::digit1;
use nom::combinator::fail;
use nom::error::{context, VerboseError};
use nom::error::convert_error;
use nom::Finish;
fn dup(s: &str) -> IResult<&str, u64, VerboseError<&str>> {
let (s, _) = tag("dup")(s)?;
let (sd, n) = digit1(s)?;
let n = match n {
"0" => 0,
"1" => 1,
"2" => 2,
_ => return fail(s), // FIXME: Annotate.
};
Ok((sd, n))
}
fn main() {
let input = "dup3";
let result = context("dup", dup)(input).finish().err().unwrap();
println!("{}", convert_error(input, result));
}
添加context("dup", dup)
为错误消息提供了一个非常漂亮和可读的上下文:
0: at line 1, in Fail:
dup3
^
1: at line 1, in dup:
dup3
^
,但它不能增加最内层的清晰度。如果我在fail
行添加上下文:
let n = match n {
"0" => 0,
"1" => 1,
"2" => 2,
_ => return context("using an out-of-bounds dup", fail)(s),
};
则消息变为
0: at line 1, in Fail:
dup3
^
1: at line 1, in using an out-of-bounds dup:
dup3
^
2: at line 1, in dup:
dup3
^
就是我想要的!但是我真的只想替换掉失败"在使用超出范围的"时,不要添加"。这里值得一提的是错误管理文档对convert_error()
的描述:
注意
VerboseError
和convert_error
是作为语言错误的起点,但是它们不能涵盖所有用例。所以应该编写一个自定义的convert_error
函数。
所以我发现添加自定义注释/上下文到错误消息的最不复杂的方式,是使用VerboseError
,但也取代convert_error
与一个弹出ErrorKind::Fail
和ErrorKind::Eof
,如果他们后面跟着一个上下文,在这种情况下,我希望上下文驻留在同一位置,导致重复的条目:
fn pretty_print_error(s: &str, mut e: VerboseError<&str>) -> String {
let (_root_s, root_error) = e.errors[0].clone();
if matches!(root_error, VerboseErrorKind::Nom(ErrorKind::Fail))
|| matches!(root_error, VerboseErrorKind::Nom(ErrorKind::Eof))
{
e.errors.remove(0);
}
convert_error(s, e)
}
欢迎更简单的解决方案。
您可能需要阅读错误管理。
一般来说,nom::error::Error
是解析器错误的低开销类型,这就是为什么它只有解析器错误。
如果你想附加更多的上下文,nom提供了一个nom::error::VerboseError
类型,也有辅助的crate提供进一步的错误包装。
ParseError
特性的,所以你可以有一个完全自定义的错误类型和实现。后一个选项显然开销最大,但灵活性也最高。