我不确定我对整件事的想法是否错误。也许有更简单的解决方案。
在 nom 中,我想解析 C 风格的单行注释。我解析的每一行“理论上”都可以在右侧包含“//一些注释”。我写了一个解析器,可以解析这些评论:
pub fn parse_single_line_comments(i: &str) -> IResult<&str, &str> {
recognize(pair(tag("//"), is_not("\n\r")))(i)
}
它在存在评论的情况下起作用。但不幸的是,如果没有注释,它会返回一个错误。现在我希望它返回一个空字符串(或者稍后我可以返回一个选项,这会更优雅)。 在我的名义学习中,我经常遇到这个问题 - 我想用自定义的 OK 变体替换错误。但我永远不确定我是否以“正确”的方式做到了这一点,即 nom/rust 的惯用方式。当我匹配解析函数的返回值时,它总是感觉很丑陋。这样想吧:
pub fn parse_single_line_comments(i: &str) -> IResult<&str, &str> {
match recognize(pair(tag("//"), is_not("\n\r")))(i) {
Ok((rest, comment)) => Ok((rest, comment)),
_ => Ok((i, "")),
}
我觉得有点奇怪。应该有更好的方法来做到这一点,对吗?
ional 来解析零到一行注释,或使用
many0
来解析零到多行注释。然后将其与
preceded
结合起来,您可以轻松丢弃零到多注释(和空格)。让我们考虑一个简单的
parse_ident
来解析标识符,如下所示:
use nom::bytes::complete::take_while1;
use nom::{AsChar, IResult};
fn parse_ident(input: &str) -> IResult<&str, &str> {
take_while1(|c: char| c.is_alpha() || (c == '_'))(input)
}
现在,再次假设我们要预先跳过零到多的空格和注释。首先,我们可以定义行注释解析器(您已经这样做了):
fn parse_single_line_comment(input: &str) -> IResult<&str, &str> {
recognize(pair(tag("//"), is_not("\n\r")))(input)
}
现在我们将更改
parse_ident
以使用
preceded
和 many0
跳过零到多行注释。此外,我们还可以添加 multispace1
来跳过零到多个空格:use nom::branch::alt;
use nom::bytes::complete::{is_not, tag, take_while1};
use nom::character::complete::multispace1;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::sequence::{pair, preceded};
use nom::{AsChar, IResult};
fn parse_ident(input: &str) -> IResult<&str, &str> {
preceded(
// Parsers to skip anything that is ignored
many0(alt((
parse_single_line_comment,
multispace1,
))),
// Identifier parsing
take_while1(|c: char| c.is_alpha() || (c == '_')),
)(input)
}
现在我们可以成功解析以下内容:
assert_eq!(
parse_ident("identifier")
Ok(("", "identifier"))
);
assert_eq!(
parse_ident(" identifier"),
Ok(("", "identifier"))
);
assert_eq!(
parse_ident("// Comment\n identifier"),
Ok(("", "identifier"))
);
assert_eq!(
parse_ident("// Comment\n// Comment\n identifier"),
Ok(("", "identifier"))
);
根据您要解析的内容,您需要将
preceded
撒在各种解析器中。我们可以通过引入我们自己的
skip_ignored
解析器来稍微简化重复的代码:fn skip_ignored<'a, F>(parser: F) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str>
where
F: FnMut(&'a str) -> IResult<&'a str, &'a str>,
{
preceded(
many0(alt((
parse_single_line_comment,
multispace1,
))),
parser,
)
}
fn parse_ident(input: &str) -> IResult<&str, &str> {
skip_ignored(
take_while1(|c: char| c.is_alpha() || (c == '_')),
)(input)
}
是否有更简单的方法来做到这一点很大程度上取决于您的数据。但只要你只是想丢弃空格和注释,那么它就相对简单。
既然您实际上询问了自定义错误,那么您可以按照其他方式定义自己的
enum
,然后 impl
ParseError
:
use nom::error::{ErrorKind, ParseError};
#[derive(Debug)]
pub enum MyParseError<'a> {
IdentTooLong,
Nom(&'a str, ErrorKind),
}
impl<'a> ParseError<&'a str> for MyParseError<'a> {
fn from_error_kind(input: &'a str, kind: ErrorKind) -> Self {
Self::Nom(input, kind)
}
fn append(_: &'a str, _: ErrorKind, other: Self) -> Self {
other
}
}
使用它可能看起来像这样:
use nom::bytes::complete::take_while1;
use nom::{AsChar, IResult};
fn parse_ident<'a>(input: &'a str) -> IResult<&'a str, &'a str, MyParseError<'a>> {
let (input, ident) = take_while1(|c: char| c.is_alpha() || (c == '_'))(input)?;
// Return error if identifier is longer than 10 bytes
if ident.len() > 10 {
Err(nom::Err::Failure(MyParseError::IdentTooLong))
} else {
Ok((input, ident))
}
}
fn main() {
println!("{:?}", parse_ident(""));
// Err(Error(Nom("", TakeWhile1)))
println!("{:?}", parse_ident("hello"));
// Ok(("hello", "hello"))
println!("{:?}", parse_ident("this_is_a_very_long_name"));
// Err(Failure(IdentTooLong))
}
还有
,它与
map_res
携手合作。如果您想呼叫
str::parse()
并能够轻松地将其映射到您的 MyParseError
,这非常有用。另请参阅:
custom_error.rs
示例
error_management.md