1. Nom 之道
首先,我们需要了解 nom 的解析方式。正如介绍中所述,nom 允许我们构建简单的解析器,然后将它们组合起来(使用 “组合器”)。
让我们讨论一下 “解析器” 实际上做什么。解析器接受输入并返回结果,其中:
Ok
表示解析器成功找到了它所寻找的内容;或者Err
表示解析器无法找到所需内容。
如果解析器成功,它将返回一个元组。元组的第一个字段将包含解析器未处理的所有内容。第二个字段将包含解析器处理的所有内容。这个想法是,解析器可以顺利地解析输入的第一部分,而不必解析整个内容。
如果解析器失败,则可能会返回多个错误。但为了简单起见,在下一章中我们将不探讨这些错误。
┌─► Ok(
│ 解析器没有接触到的部分,
│ 解析器匹配到的部分
│ )
┌─────────┐ │
my input───►│my parser├──►either──┤
└─────────┘ └─► Err(...)
为了表示这个模型,nom 使用类型 IResult<I, O>
。Ok
变体采用两种类型 —— I
,输入的类型;和 O
,输出的类型,而 Err
变体存储错误。
您可以从以下位置导入:
use nom::IResult;
您会注意到 I
和 O
是参数化的 —— 尽管本书中的大多数示例都带有 &str
(即解析字符串);但它们不必是字符串;也不必须是相同的类型(考虑简单的例子,其中 I = &str
,和 O = u64
—— 这会将字符串解析为无符号整数)。
让我们编写第一个解析器!我们可以编写的最简单的解析器是成功不执行任何操作的解析器。
这个解析器应该接受 &str
:
- 因为它应该会成功,所以我们知道它会返回 Ok 变量。
- 由于它对我们的输入没有任何影响,剩余的输入与输入相同。
- 因为它不解析任何内容,所以它也应该只返回一个空字符串。
pub fn do_nothing_parser(input: &str) -> IResult<&str, &str> {
Ok((input, ""))
}
fn main() -> Result<(), Box<dyn Error>> {
let (remaining_input, output) = do_nothing_parser("my_input")?;
assert_eq!(remaining_input, "my_input");
assert_eq!(output, "");
}
就这么简单!
关于rust代码文档
rust 的代码中,为了表明一个既定的输出,会使用 assert_eq!
这样的宏断言,比如上文中的 main 函数:
fn main() -> Result<(), Box<dyn Error>> {
let (remaining_input, output) = do_nothing_parser("my_input")?;
assert_eq!(remaining_input, "my_input");
assert_eq!(output, "");
}
第三行和第四行都使用了宏断言,这就代表,remaining_input
它的值就是 "my_input"
, output
它的值就是 ""
。在rust第三方库的函数使用文档中(一般我们都喜欢把一个函数的使用和对应的例子写在函数上方的注释里面,方便生成完整的文档),为了表明“如果按照预期,这个我写的函数输出就应该是这样”,我们都会使用 assert_eq!
。比如你开发了一个函数 add
,负责实现两个 usize
的加法,那么为了让用户明白你的函数的作用,你最好给出一两个使用例子,比如这样:
assert_eq!(add(1, 1), 2);
这样大家都知道你这个就是实现加法了,因为函数作用和用户心中的假设拉钩了,这样可以利于用户理解你的函数,而且你还不用使用自然语言去写又臭又长的使用文档。