37 解析表达式文法

Emacs Lisp 提供了多种用于文本解析与匹配的工具, 从正则表达式(see 正则表达式)到完整的 从左至右(又称 LL)文法解析器(see Bovine parser development)。解析表达式文法PEG)是另一种文本解析方式,它比正则表达式 具备更强的结构性与可组合性,但复杂度又低于上下文无关文法。

解析表达式文法(PEG)通过一组识别语言中字符串的规则 来描述形式语言。在 Emacs 中,一个 PEG 解析器被定义为 一组具名规则的列表,每条规则用于匹配文本模式,和/或包含对其他规则的引用。 解析过程通过函数 peg-run 或宏 peg-parse 启动(见下文), 它们会使用给定的规则集,解析当前缓冲区中点之后的文本。

PEG 中的每条规则被称为一个 解析表达式(parsing expression)PEX), 它可以被指定为字面量字符串、类似正则表达式的字符范围或集合、 形似 Emacs Lisp 函数调用的 PEG 专用结构、对其他规则的引用, 或是以上任意形式的组合。一个文法表现为一棵规则树, 其中通常有一条规则被视为 “根(root)” 或 “入口点(entry-point)” 规则。例如:

((number sign digit (* digit))
 (sign   (or "+" "-" ""))
 (digit  [0-9]))

文法定义完成后,可以通过多种方式解析当前缓冲区中点之后的文本。 宏 peg-parse 是最简单的方式:

Macro: peg-parse &rest pexs

在点位置匹配 pexs

(peg-parse
  (number sign digit (* digit))
  (sign   (or "+" "-" ""))
  (digit  [0-9]))

该宏虽然简单,但灵活性不足,因为规则必须直接写在源代码中。 通过组合使用其他函数与宏可以获得更高的灵活性。

Macro: with-peg-rules rules &rest body

在生效的 rules(一组 PEX 列表)环境下执行 body。 在 BODY 内部,通过调用 peg-run 启动解析。

Function: peg-run peg-matcher &optional failure-function success-function

该函数接收单个 peg-matcher,该匹配器是对具名规则 (通常是更大文法的入口点)调用 peg(见下文)后的结果。

解析结束时,根据解析是否成功,会调用 failure-functionsuccess-function 其中之一。如果提供了 success-function, 它应当是一个接收唯一参数的函数,该参数是一个匿名函数, 用于执行解析过程中收集到栈中的所有动作。 默认情况下该匿名函数会被直接执行。如果解析失败, 作为 failure-function 提供的函数会接收一个解析过程中失败的 PEG 表达式列表作为参数被调用。默认情况下该列表会被丢弃。

传递给 peg-runpeg-matcher 由调用 peg 生成:

Macro: peg &rest pexs

pexs 转换为适合传递给 peg-run 的单个 peg 匹配器。

上文的 peg-parse 示例会展开为一组对这些函数的调用, 其完整写法可以是:

(with-peg-rules
    ((number sign digit (* digit))
     (sign   (or "+" "-" ""))
     (digit  [0-9]))
  (peg-run (peg number)))

这种方式可以更显式地控制解析的 “入口点(entry-point)”, 并允许组合来自不同来源的规则。

也可以使用宏 define-peg-rule, 通过更接近 defun 的语法定义单条规则:

Macro: define-peg-rule name args &rest pexs

name 定义为一条 PEG 规则,它接收 args 并在点位置匹配 pexs

例如:

(define-peg-rule digit ()
  [0-9])

可以通过 funcall PEG 规则为规则传递参数(see PEX 定义)。

另一种方式是使用 define-peg-ruleset 定义一组具名规则:

Macro: define-peg-ruleset name &rest rules

name 定义为 rules 的标识符。

(define-peg-ruleset number-grammar
  ;; 此处的 `digit' 引用上文的定义。
  (number () sign digit (* digit))
  (sign () (or "+" "-" "")))

以此方式定义的规则与规则集,可以在后续调用 peg-runwith-peg-rules 时通过名称引用:

(with-peg-rules number-grammar
  (peg-run (peg number)))

默认情况下,调用 peg-runpeg-parse 不会产生输出: 解析仅会移动点位置。为了返回解析后的字符串或对其执行其他操作, 规则中可以包含 动作(actions),详见 解析动作