3. 替代和组合
在上一章中,我们了解了如何使用 tag 函数创建简单的解析器;以及一些 Nom 预构建的解析器。
在本章中,我们将探讨 Nom 的另外两个广泛使用的功能:替代和组合。
替代
有时,我们可能想在两个解析器之间进行选择;并且无论使用其中哪一种都可以解决问题。
Nom 通过组合器赋予我们类似的能力 alt()。
use nom::branch::alt;
组合器 alt() 将执行元组中的每个解析器,直到找到一个不出错的解析器。如果全部出错,则默认会给出最后一个错误的错误。
我们可以看到 alt() 下面的一个基本例子。
extern crate nom;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::IResult;
use std::error::Error;
fn parse_abc_or_def(input: &str) -> IResult<&str, &str> {
alt((
tag("abc"),
tag("def")
))(input)
}
fn main() -> Result<(), Box<dyn Error>> {
let (leftover_input, output) = parse_abc_or_def("abcWorld")?;
assert_eq!(leftover_input, "World");
assert_eq!(output, "abc");
assert!(parse_abc_or_def("ghiWorld").is_err());
Ok(())
}
组合
现在我们可以创建更有趣的正则表达式了,我们可以将它们组合在一起。最简单的方法就是按顺序对它们进行解析:
extern crate nom;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::IResult;
use std::error::Error;
fn parse_abc(input: &str) -> IResult<&str, &str> {
tag("abc")(input)
}
fn parse_def_or_ghi(input: &str) -> IResult<&str, &str> {
alt((
tag("def"),
tag("ghi")
))(input)
}
fn main() -> Result<(), Box<dyn Error>> {
let input = "abcghi";
let (remainder, abc) = parse_abc(input)?;
let (remainder, def_or_ghi) = parse_def_or_ghi(remainder)?;
println!("first parsed: {abc}; then parsed: {def_or_ghi};");
Ok(())
}
编写标签是一项非常常见的要求,事实上,Nom 有一些内置的组合器可以做到这一点。其中最简单的是 tuple() 。tuple() 组合器接受一个解析器元组,要么返回 Ok 所有成功解析的元组,要么返回 Err 第一个失败解析器的。
use nom::sequence::tuple;
extern crate nom;
use nom::branch::alt;
use nom::sequence::tuple;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::{digit1};
use nom::IResult;
use std::error::Error;
fn parse_base(input: &str) -> IResult<&str, &str> {
alt((
tag_no_case("a"),
tag_no_case("t"),
tag_no_case("c"),
tag_no_case("g")
))(input)
}
fn parse_pair(input: &str) -> IResult<&str, (&str, &str)> {
// 组合子 many_m_n 在这里也是正确的
tuple((
parse_base,
parse_base,
))(input)
}
fn main() -> Result<(), Box<dyn Error>> {
let (remaining, parsed) = parse_pair("aTcG")?;
assert_eq!(parsed, ("a", "T"));
assert_eq!(remaining, "cG");
assert!(parse_pair("Dct").is_err());
Ok(())
}
额外 Nom 工具
使用 alt() 和之后 tuple() ,您可能还会对其他一些执行类似操作的解析器感兴趣:
| 组合子 | 用法 | 输入 | 输出 | 注释 |
|---|---|---|---|---|
| delimited | delimited(char('('), take(2), char(')')) | "(ab)cd" | Ok(("cd", "ab")) | 匹配来自第一个解析器的对象并丢弃它,然后从第二个解析器中获取一个对象,最后匹配来自第三个解析器的对象并丢弃它。 |
| preceded | preceded(tag("ab"), tag("XY")) | "abXYZ" | Ok(("Z", "XY")) | 匹配来自第一个解析器的对象并将其丢弃,然后从第二个解析器中获取一个对象。 |
| terminated | terminated(tag("ab"), tag("XY")) | "abXYZ" | Ok(("Z", "ab")) | 从第一个解析器获取一个对象,然后匹配来自第二个解析器的一个对象并丢弃它。 |
| pair | pair(tag("ab"), tag("XY")) | "abXYZ" | Ok(("Z", ("ab", "XY"))) | 从第一个解析器获取一个对象,然后从第二个解析器获取另一个对象。 |
| separated_pair | separated_pair(tag("hello"), char(','), tag("world")) | "hello,world!" | Ok(("!", ("hello", "world"))) | 从第一个解析器获取一个对象,然后从 sep_parser 匹配一个对象并丢弃它,然后从第二个解析器获取另一个对象。 |
