Rust 将解析错误转换(映射)为 Ok 结果的惯用方式是什么?

问题描述 投票:0回答:1

我不确定我对整件事的想法是否错误。也许有更简单的解决方案。

在 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, "")), }

我觉得有点奇怪。应该有更好的方法来做到这一点,对吗?

dictionary rust nom
1个回答
0
投票

opt

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)) }

还有 

FromExternalError

,它与 
map_res
 携手合作。如果您想呼叫 
str::parse()
并能够轻松地将其映射到您的
MyParseError
,这非常有用。
另请参阅:

  • custom_error.rs示例
    
    
  • error_management.md
    
    
© www.soinside.com 2019 - 2024. All rights reserved.