Pest 与 PEG 文法

引入

解析表达式文法 (Parsing expression grammar, PEG) 是一种相比 CFG 更现代的语言表示形式。具有如下特点:

  • 不同选项不可交换。越靠前的越优先选择。 例子:对于 A -> B | C,在 CFG 中,B 和 C 都可以匹配,B | CC | B 并无不同。而 PEG 中,对于 A -> B | C 如果 B 匹配成功,则不再匹配 C

  • 支持断言,从而消除歧义。

  • 贪婪匹配,也就是说在一个选项之内,会尽可能地匹配此选项规则所允许的字符

  • 但是不支持左递归。

规则

匹配任何字符

any_char = {ANY}

匹配 “abc”,然后 “def”

sequence = {"abc" ~ "def"}

匹配任何字符,除了 "

any_but_quote = { !"\"" ~ ANY }

!"c" 的作用是检查是否不是字符 c。如果满足,则继续,否则失败。就像是 assert not eq&""

!& 不会消耗输入。

重复匹配

匹配任意次

expr = { "a"* }
// 或者
expr = { "a"{0,} }

匹配至少一次

expr = { "a"+ }
// 或者
expr = { "a"{1,} }

Pair

Pair 是一种区间结构。它是 Pest 对结构化的文本的抽象。举个例子:

if (a) doA() else doB()

可以对应于下面的 Pair:

  +---------------------+
  |       Pair 1       |        if (a) doA() else doB()
  +---------------------+
  |   +---------------+|
  |   |   Pair 2      ||        (a)
  |   +---------------+|
  |   |   +-----------+|
  |   |   | Pair 3    ||        doA()
  |   |   +-----------+|
  |   +---------------+|
  |   |   +-----------+|
  |   |   | Pair 4    ||        doB()
  |   |   +-----------+|
  +---------------------+

对应于如下的文法规则:

cond_stmt = { "if" ~ "(" ~ cond_expr ~ ")" ~ block ~ "else" ~ block }

静默规则

不产生 Pair 和 Token 的规则称为静默规则。例如常见的 WHITESPACE

WHITESPACE = _{ " " | "\t" | "\r" | "\n" }

这个规则中,我们读取空白字符,但由于文法允许任意空白字符,因此我们设置为静默规则,从而不产生空白字符的 Token 干扰语法树的构建。

需要注意的是,如果右手边不是具体的字面量,而是另一个规则,那么效果等同于直接展开到对应规则。例如:

int_literal = ${ "-"? ~ digits }
digits = _{ oct_digits | ( "0x" ~ hex_digits ) | dec_digits }

上述两条规则,由于 digits 是静默规则,因此 int_literal 中的子结构 digits 会被直接展开,而不是作为一个 Pair。等价于:

int_literal = ${ "-"? ~ ( oct_digits | ( "0x" ~ hex_digits ) | dec_digits ) }

相应的制导代码:

 1fn parse_int_literal(pair: Pair<Rule>) -> ParseResult<i64> {
 2    let is_neg = pair.as_str().starts_with('-');
 3    let pairs = pair.into_inner();
 4    let pair = pairs.peek().unwrap(); // digits
 5    let rule = pair.as_rule();
 6    let radix = match rule {
 7        Rule::hex_digits => 16,
 8        Rule::oct_digits => 8,
 9        Rule::dec_digits => 10,
10        _ => unreachable!(),
11    };
12    // ...
13}

原子规则和复合原子规则

原子规则和复合原子规则都不跳过空格。对于解决 MyClass<Int,Double> a;Myclass < Int, Double > a; 这样的歧义,是有用的。

记法:

atomic = @{ ... }
compound_atomic = ${ ... }

区别:复合原子规则不会产生空格的 token。

典型的例子是字符串。字符串中的空格是有意义的,因此不能跳过空格。

str_literal = ${ "\"" ~ str_inner ~ "\"" }
str_inner = _{ (str_esc | str_char)* }
str_char = { !("\"" | "\\") ~ ANY }
str_esc = { "\\" ~ ("\"" | "\\" | "n" | "r" | "t") }

这里 str_literal 是复合原子规则,因此中间的空格会被保留,但不会产生空格的 token。

参考