正文
logic_op = {
"and"
|
"or"
}
sub_expr_group = { ... }
sub_expr = { name ~ op ~ value }
name = @{
"platform"
|
"country"
|
"date"
| ... }
op = @{
"=="
|
">="
| ... }
array = {
"["
~ value ~ (
","
~ value)* ~
"]"
}
value = _{ date | (
"\""
~ string ~
"\""
) | array }
string = @{ ASCII_ALPHA* }
date = ${ md ~
"/"
~ md ~
"/"
~ year }
md = { ASCII_DIGIT{1,2} }
year = { ASCII_DIGIT{4} }
代码并不难懂。
我采用了自顶向下的方式来描述这个语法。首先,我让所有的空格自动解析,自动忽略。
然后是顶层的逻辑:policy 从输入开始(Start Of Input),读取一个表达式(expr),后接 任意多的逻辑运算符( logic op)和表达式(expr),最后输入结束(End Of Input)。
那么表达式长什么样?表达式是不带括号的子表达式(sub_expr)或者带括号的子表达式(sub_expr_group),二选一。
那么不带括号的子表达式长什么样?变量名(name)+ 操作符(op)+ 值(value)。
剩下的我就不一一赘述了,很好理解。
我们可以看到,pest 声明的语法结构和 Bison 很像。为了方便解析和生成合适的语法树,pest 提供了一些方法可以控制哪些内容在语法树中生成:
写好的规则存成文件(比如 expr.pest)后,可以在 Rust 代码里这么引用:
#[derive(Parser)]
#[grammar = "expr.pest"]
pub struct ExprParser;
然后,就可以生成语法树了:
let result = ExprParser::parse(Rule::policy, "date > 1/1/2021").unwrap();
生成的语法树如下:
[
Pair {
rule: sub_expr,
span: Span {
str: "date > 1/1/2021",
start: 0,
end: 15,
},
inner: [
Pair {
rule: name,
span: Span {
str: "date",
start: 0,
end: 4,
},
inner: [],
},
Pair {
rule: op,
span