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 是最简单的方式:
在点位置匹配 pexs。
(peg-parse (number sign digit (* digit)) (sign (or "+" "-" "")) (digit [0-9]))
该宏虽然简单,但灵活性不足,因为规则必须直接写在源代码中。 通过组合使用其他函数与宏可以获得更高的灵活性。
在生效的 rules(一组 PEX 列表)环境下执行 body。
在 BODY 内部,通过调用 peg-run 启动解析。
该函数接收单个 peg-matcher,该匹配器是对具名规则
(通常是更大文法的入口点)调用 peg(见下文)后的结果。
解析结束时,根据解析是否成功,会调用 failure-function 或 success-function 其中之一。如果提供了 success-function, 它应当是一个接收唯一参数的函数,该参数是一个匿名函数, 用于执行解析过程中收集到栈中的所有动作。 默认情况下该匿名函数会被直接执行。如果解析失败, 作为 failure-function 提供的函数会接收一个解析过程中失败的 PEG 表达式列表作为参数被调用。默认情况下该列表会被丢弃。
传递给 peg-run 的 peg-matcher 由调用 peg 生成:
将 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 的语法定义单条规则:
将 name 定义为一条 PEG 规则,它接收 args 并在点位置匹配 pexs。
例如:
(define-peg-rule digit () [0-9])
可以通过 funcall PEG 规则为规则传递参数(see PEX 定义)。
另一种方式是使用 define-peg-ruleset 定义一组具名规则:
将 name 定义为 rules 的标识符。
(define-peg-ruleset number-grammar ;; 此处的 `digit' 引用上文的定义。 (number () sign digit (* digit)) (sign () (or "+" "-" "")))
以此方式定义的规则与规则集,可以在后续调用 peg-run
或 with-peg-rules 时通过名称引用:
(with-peg-rules number-grammar (peg-run (peg number)))
默认情况下,调用 peg-run 或 peg-parse 不会产生输出:
解析仅会移动点位置。为了返回解析后的字符串或对其执行其他操作,
规则中可以包含 动作(actions),详见 解析动作。