「这是我参与11月更文挑战的第 17 天,活动详情查看:2021最后一次更文挑战」
如果你以前没有做过 parser
,那么编写分析器可能会让人感到害怕。它使人联想到晦涩难懂的语法被输入到神秘的工具中,从而产生数千行不可读的代码。
但它其实不一定是这样的。像 nom
这样的解析器库提供了一个小函数库,每个函数聚焦一件事,然后允许你把它们放在一起,就可以创建一个完整的解析器。
我最近建立了一个有意思的实用程序,将 roamresearch graph
导出为 HTML
,这项任务的一部分涉及到解析Roam markdown类似格式。编写解析器的经验还不错,所以我在这里对解析器文件做了注释,以便你能看到它是如何组合在一起的。
首先我们只是导入所有的解析器和组合器。
use nom::{
branch::alt,
bytes::complete::{is_not, tag, take_until, take_while1},
character::complete::{char, multispace0},
character::{is_newline, is_space},
combinator::{all_consuming, map, map_parser, opt, recognize},
error::context,
sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
IResult,
};
use urlocator::{UrlLocation, UrlLocator};
分析器的输入是单个块的标记,输出是 Vec<Expression>
。有些表达式,如Bold,包含其他表达式。至于其他表达式,例如 Image,就直接渲染出 HTML。
大多数这些表达式实际渲染成HTML的过程并不是那么有趣,但如果有人关心的话,下次再说吧。
#[derive(Debug, PartialEq, Eq)]
pub enum Expression<'a> {
Text(&'a str),
RawHyperlink(&'a str),
Image {
alt: &'a str,
url: &'a str,
},
BraceDirective(&'a str),
Table,
PageEmbed(&'a str),
BlockEmbed(&'a str),
TripleBacktick(&'a str),
SingleBacktick(&'a str),
Hashtag(&'a str, bool),
Link(&'a str),
MarkdownLink {
title: &'a str,
url: &'a str,
},
BlockRef(&'a str),
Attribute {
name: &'a str,
value: Vec<Expression<'a>>,
},
Bold(Vec<Expression<'a>>),
Italic(Vec<Expression<'a>>),
Strike(Vec<Expression<'a>>),
Highlight(Vec<Expression<'a>>),
BlockQuote(Vec<Expression<'a>>),
HRule,
}
首先,先实现一个实用函数来匹配任何非空白字符:
fn nonws_char(c: char) -> bool {
!is_space(c as u8) && !is_newline(c as u8)
}
我们的第一个解析器:word,只是得到一个单词。
take_while1(nonws_char)
创建一个分析器,读取至少一个与 nonws_char
匹配的字符。
每个解析器都遵循这个格式。它把输入作为一个参数,并返回一个 IResult<REM, DATA>
,其中REM是字符串的剩余未解析部分,DATA是解析器返回的类型。内置的解析器也可以使用这种类型的返回值,所以我们在这里不需要做任何特别的事情。
对于这个解析器来说,输入都是字符串,但nom也是可以处理二进制格式,所以 &[u8]
在其他情况下也常见到。
fn word(input: &str) -> IResult<&str, &str> {
take_while1(nonws_char)(input)
}