37.3 编写 PEG 规则

编写 PEG 规则时需要注意它们是贪婪的。 可以消耗可变长度文本的规则总会尽可能消耗最多内容, 即使这会导致后续原本可能匹配的规则失败——不存在回溯。 例如,这条规则永远不会成功:

(forest (+ "tree" (* [blank])) "tree" (eol))

PEX (+ "tree" (* [blank])) 会消耗掉所有单词 ‘tree’ 的重复出现, 导致没有剩余内容匹配最后的 ‘tree’。

在这类场景中,可以通过使用谓词与守卫(即 notifguard 表达式) 约束行为以获得预期结果。例如:

(forest (+ "tree" (* [blank])) (not (eol)) "tree" (eol))

ifnot 运算符接收一个解析表达式并将其解释为布尔值, 且不会移动点位置。guard 运算符中的内容会作为普通 Lisp 代码 (而非 PEX)求值,并应返回布尔值。nil 值会导致匹配失败。

另一个可能出乎意料的行为是,即使解析最终失败, 解析过程仍会尽可能移动点位置。这条规则:

(end-game "game" (eob))

在点之后包含文本 “game over” 的缓冲区中运行时, 会将点移动到 “game” 之后,然后停止解析并返回 nil。 成功的解析总会返回 t,或是解析栈中的内容。