专栏名称: 程序人生
十年漫漫程序人生,打过各种杂,也做过让我骄傲的软件;管理过数十人的团队,还带领一班兄弟姐妹创过业,目前在硅谷一家创业公司担任 VP。关注程序人生,了解程序猿,学做程序猿,做好程序猿,让我们的程序人生精彩满满。
目录
相关文章推荐
51好读  ›  专栏  ›  程序人生

再探 Parser 和 Parser Combinator

程序人生  · 公众号  · 程序员  · 2021-02-21 09:12

正文

请到「今天看啥」查看全文



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 提供了一些方法可以控制哪些内容在语法树中生成:

  • _{}:如果一条规则前加 _ ,意味着这个规则本身不会出现在语法树中(只出现其子规则)。

  • @{}:如果规则前加 @ ,意味着这是原子规则(atomic rule),里面的空格需要被显式定义,且其子规则不会生成在语法树中。

  • ${}:如果规则前加 $ ,意味着这是复合原子规则(compound atomic rule),里面的空格需要给显式定义,但子规则会生成在语法树中。

写好的规则存成文件(比如 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






请到「今天看啥」查看全文