如何使用nom解析略有歧义的数据?

时间:2019-09-13 13:46:47

标签: rust nom

RFC1738中,domainlabel的BNF如下:

  

domainlabel =字母数字|字母数字* [字母数字| “-”]   字母数字

也就是说,它可以是字母数字,也可以是字符串,其中第一个/最后一个字符必须是字母数字,而中间字符可以是字母数字或破折号。

如何使用nom来实现?忽略单字符方案以简化情况,我的最后尝试是:

fn domain_label(s: &[u8]) -> IResult<&[u8], (&[u8], &[u8], &[u8])> {
    let left = take_while_m_n(1, 1, is_alphanumeric);
    let middle = take_while(|c| is_alphanumeric(c) || c == b'-');
    let right = take_while_m_n(1, 1, is_alphanumeric);
    let whole = tuple((left, middle, right));
    whole(s)
}

此问题是middle可以消耗最后一个字符,因此right失败,因为没有字符可以消耗。

println!("{:?}", domain_label(b"abcde"));
Err(Error(([], TakeWhileMN)))

解析器应该能够尝试所有可能的消耗路径,但是如何使用nom来做到这一点?

1 个答案:

答案 0 :(得分:3)

  

domainlabel =字母数字|字母数字* [字母数字| “-”]字母数字

它是由任意数量的字符-分隔的一系列字母数字序列。因此,这是一种实现方法:

use nom::bytes::complete::{tag, take_while1};
use nom::character::is_alphanumeric;
use nom::combinator::recognize;
use nom::multi::{many1, separated_list};
use nom::IResult;

fn domain_label(input: &[u8]) -> IResult<&[u8], &[u8]> {
    let alphadigits = take_while1(is_alphanumeric);
    let delimiter = many1(tag(b"-"));
    let parser = separated_list(delimiter, alphadigits);

    recognize(parser)(input)
}

fn main() {
    let (_, res) = domain_label(b"abcde").unwrap();
    assert_eq!(res, b"abcde");
    let (_, res) = domain_label(b"abcde-123-xyz-").unwrap();
    assert_eq!(res, b"abcde-123-xyz");
    let (_, res) = domain_label(b"rust-lang--1---37---0.org").unwrap();
    assert_eq!(res, b"rust-lang--1---37---0");
}

请注意,您不需要成功解析的各个部分。结果就是符合域标签BNF的最长输入。这就是recognize组合器的出现。