【问题标题】:How can I match an exact tag using the nom library in Rust如何使用 Rust 中的 nom 库匹配精确标签
【发布时间】:2021-07-21 10:34:22
【问题描述】:

我正在开发一个用 Rust 编写的小型持续时间解析库,并使用 nom 库。在这个库中,我定义了一个second 解析器组合函数。它的职责是解析各种可接受的格式,以文本格式表示秒。

    pub fn duration(input: &str) -> IResult<&str, std::time::Duration> {
       // Some code combining the various time format combinators
       // to match the format "10 days, 8 hours, 7 minutes and 6 seconds"  
    }

    pub fn seconds(input: &str) -> IResult<&str, u64> {
        terminated(unsigned_integer_64, preceded(multispace0, second))(input)
    }

    fn second(input: &str) -> IResult<&str, &str> {
        alt((
            tag("seconds"),
            tag("second"),
            tag("secs"),
            tag("sec"),
            tag("s"),
        ))(input)
    }

到目前为止,标签组合器的行为符合我的预期。但是,我最近发现以下示例失败了,并且定义为失败:

assert!(second("se").is_err())

确实,文档指出“输入数据将与标签组合器的参数进行比较,并将返回与参数匹配的输入部分”。

但是,正如我的示例希望说明的那样,我想要实现的是某种标签,如果无法解析整个输入,它将失败。我在解析输入后明确检查是否有休息;并发现它会起作用。此外,使用 completetake 组合器的某些风格来实现这一目标的探索未成功。

解析单词的“完全匹配”并在部分结果上失败(返回休息)的惯用方法是什么?

【问题讨论】:

    标签: parsing rust parser-combinators nom


    【解决方案1】:

    您可以使用all consuming 组合器,如果整个输入已被其子解析器使用,则该组合器成功:

    // nom 6.1.2
    use nom::branch::alt;
    use nom::bytes::complete::tag;
    use nom::combinator::all_consuming;
    use nom::IResult;
    
    fn main() {
        assert!(second("se").is_err());
    }
    
    fn second(input: &str) -> IResult<&str, &str> {
        all_consuming(alt((
            tag("seconds"),
            tag("second"),
            tag("secs"),
            tag("sec"),
            tag("s"),
        )))(input)
    }
    

    更新

    我想我误解了你原来的问题。也许这更接近您的需要。关键是你应该编写更小的解析器,然后将它们组合起来:

    use nom::branch::alt;
    use nom::bytes::complete::tag;
    use nom::character::complete::digit1;
    use nom::combinator::all_consuming;
    use nom::sequence::{terminated, tuple};
    use nom::IResult;
    
    #[derive(Debug)]
    struct Time {
        min: u32,
        sec: u32,
    }
    
    fn main() {
        //OK
        let parsed = time("10 minutes, 5 seconds");
        println!("{:?}", parsed);
    
        //OK
        let parsed = time("10 mins, 5 s");
        println!("{:?}", parsed);
    
        //Error -> although `min` is a valid tag, it would expect `, ` afterwards, instead of `ts`
        let parsed = time("10 mints, 5 s");
        println!("{:?}", parsed);
    
        //Error -> there must not be anything left after "5 s"
        let parsed = time("10 mins, 5 s, ");
        println!("{:?}", parsed);
    
        // Error -> although it starts with `sec` which is a valid tag, it will fail, because it would expect EOF
        let parsed = time("10 min, 5 sections");
        println!("{:?}", parsed);
    }
    
    fn time(input: &str) -> IResult<&str, Time> {
        // parse the minutes section and **expect** a delimiter, because there **must** be another section afterwards
        let (rem, min) = terminated(minutes_section, delimiter)(input)?;
    
        // parse the minutes section and **expect** EOF - i.e. there should not be any input left to parse
        let (rem, sec) = all_consuming(seconds_section)(rem)?;
    
        // rem should be empty slice
        IResult::Ok((rem, Time { min, sec }))
    }
    
    // This function combines several parsers to parse the minutes section:
    // NUMBER[sep]TAG-MINUTES
    fn minutes_section(input: &str) -> IResult<&str, u32> {
        let (rem, (min, _sep, _tag)) = tuple((number, separator, minutes))(input)?;
    
        IResult::Ok((rem, min))
    }
    
    // This function combines several parsers to parse the seconds section:
    // NUMBER[sep]TAG-SECONDS
    fn seconds_section(input: &str) -> IResult<&str, u32> {
        let (rem, (sec, _sep, _tag)) = tuple((number, separator, seconds))(input)?;
    
        IResult::Ok((rem, sec))
    }
    
    fn number(input: &str) -> IResult<&str, u32> {
        digit1(input).map(|(remaining, number)| {
            // it can panic if the string represents a number
            // that does not fit into u32
            let n = number.parse().unwrap();
            (remaining, n)
        })
    }
    
    fn minutes(input: &str) -> IResult<&str, &str> {
        alt((
            tag("minutes"),
            tag("minute"),
            tag("mins"),
            tag("min"),
            tag("m"),
        ))(input)
    }
    
    fn seconds(input: &str) -> IResult<&str, &str> {
        alt((
            tag("seconds"),
            tag("second"),
            tag("secs"),
            tag("sec"),
            tag("s"),
        ))(input)
    }
    
    // This function parses the separator between the number and the tag:
    //N<separator>tag -> 5[sep]minutes
    fn separator(input: &str) -> IResult<&str, &str> {
        tag(" ")(input)
    }
    
    // This function parses the delimiter between the sections:
    // X minutes<delimiter>Y seconds -> 1 min[delimiter]2 sec
    fn delimiter(input: &str) -> IResult<&str, &str> {
        tag(", ")(input)
    }
    

    在这里,我为构建块创建了一组基本解析器,例如“数字”、“分隔符”、“分隔符”、各种标记(分钟、秒等)。这些都不期望是“完整的单词”。相反,您应该使用组合符,例如 terminatedtupleall_consuming 来标记“确切单词”的结束位置。

    【讨论】:

    • 太棒了!正是我想要的。我之前使用过all_consuming,但没想到在这种情况下使用它。非常感谢!
    • 当然 all_consuming 组合子应该只应用于完整的解析器,因为它不会再与其他解析器一起排序。
    • 我刚才确实遇到了这个问题。当我写了一个 duration 解析器来解析 N years, M months, and X seconds 时,每个使用 all_consuming 组合器的解析器确实会引发一个 EOF 错误,正如我根据您的评论所期望的那样。您是否想到了有助于避免这种行为的替代方法?
    • @Oleiade 据我了解您预期 EOF。但如果不是这样,您能否给出一个更完整的输入数据示例?在ssec 等之后会发生什么?
    • 我认为我的第二个示例解决了这个问题。您只需要告诉解析器在 tag() 之后会发生什么 - 您可以使用 tuple()terminated()all_consuming() 等来做到这一点。
    猜你喜欢
    • 2020-10-28
    • 2020-10-29
    • 1970-01-01
    • 1970-01-01
    • 2020-05-17
    • 1970-01-01
    • 2012-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多