37.2 解析动作

默认情况下,解析过程仅会移动当前缓冲区中的点位置, 解析成功时最终返回 t,失败则返回 nil。 也可以定义 解析动作(parsing actions),在解析文本的特定位置执行任意 Elisp 代码。 这些动作可以选择性地影响一个被称为 解析栈(parsing stack) 的结构, 它是解析过程返回值组成的列表。这些动作仅在解析过程最终成功时才会执行 (并返回值);如果解析失败,动作代码完全不会运行。

动作可以添加在规则定义的任意位置。 它们与解析表达式的区别在于以反引号(‘`’)开头, 后跟一个括号形式,其中必须包含一对连字符(‘--’)。 连字符左侧的符号会绑定从栈中弹出的值(在某种程度上类似于 lambda 形式的参数列表)。 连字符右侧代码产生的值会被压入栈中(类似于 lambda 的返回值)。 例如,上文的文法可以添加动作,将解析出的数字作为实际整数返回:

(with-peg-rules ((number sign digit (* digit
                                       `(a b -- (+ (* a 10) b)))
                         `(sign val -- (* sign val)))
                 (sign (or (and "+" `(-- 1))
                           (and "-" `(-- -1))
                           (and ""  `(-- 1))))
                 (digit [0-9] `(-- (- (char-before) ?0))))
  (peg-run (peg number)))

栈中必须先存在值才能被弹出并返回 – 如果栈中没有足够的值绑定到动作左侧的项,它们会被绑定为 nil。 仅包含右侧项的动作会向栈中压入值;仅包含左侧项的动作会从栈中消耗(并丢弃)值。 解析结束时,栈中的值会以扁平列表形式返回。

要返回 PEX 匹配的字符串(而非仅移动点位置), 文法可以使用如下规则:

(one-word
  `(-- (point))
  (+ [word])
  `(start -- (buffer-substring start (point))))

上文的第一个动作将点的初始值压入栈中。 中间的 PEX 会将点移动到下一个词之后。 第二个动作从栈中弹出先前的值(绑定到变量 start), 然后使用该值从缓冲区提取子串并压入栈中。 这种模式非常常见,因此 PEG 提供了一个简写函数实现上述功能, 同时还有其他几种应对常见场景的简写:

(substring e)

匹配 PEX e 并将匹配的字符串压入栈中。

(region e)

匹配 e 并将匹配区域的起始与结束位置压入栈中。

(replace e replacement)

匹配 e 并将匹配区域替换为字符串 replacement

(list e)

匹配 e,将 e(及其子表达式)产生的所有值收集为列表, 并将该列表压入栈中。栈中的值通常以扁平列表返回; 该方式用于将值 “分组(grouping)”。