如何使用 nom 解析稍微模糊的数据?
How to parse slightly ambiguous data using nom?
在 RFC1738 中,domainlabel
的 BNF 如下:
domainlabel = alphadigit | alphadigit *[ alphadigit | "-" ]
alphadigit
也就是说,它要么是字母数字,要么是一个字符串,其中 first/last 个字符必须是字母数字,但中间字符可以是字母数字或破折号。
如何使用 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
如何做到这一点?
domainlabel = alphadigit | alphadigit *[ alphadigit | "-" ] alphadigit
它是由任意数量的字符分隔的一系列字母数字序列-
。所以这是一种方法:
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
组合子的用武之地。
在 RFC1738 中,domainlabel
的 BNF 如下:
domainlabel = alphadigit | alphadigit *[ alphadigit | "-" ] alphadigit
也就是说,它要么是字母数字,要么是一个字符串,其中 first/last 个字符必须是字母数字,但中间字符可以是字母数字或破折号。
如何使用 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
如何做到这一点?
domainlabel = alphadigit | alphadigit *[ alphadigit | "-" ] alphadigit
它是由任意数量的字符分隔的一系列字母数字序列-
。所以这是一种方法:
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
组合子的用武之地。