35.3.3.3 定义新的 rx 形式

可通过基于其他 rx 表达式定义新符号和参数化形式 来扩展 rx 表示法。 这便于在多个正则表达式之间共享部分内容, 并通过将复杂正则表达式由较小部分组合而成, 使其更易于构建和理解。

例如,可将 name 定义为 (one-or-more letter), 将 (quoted x) 定义为 (seq ?' x ?')(适用于任意 x)。 这些形式可像其他形式一样在 rx 表达式中使用: (rx (quoted name)) 将匹配单引号内的非空字母序列。

以下 Lisp 宏提供了将名称绑定到定义的不同方式。 所有宏均遵循以下规则:

Macro: rx-define name [arglist] rx-form

在后续所有对 rxrx-to-string 的调用中 全局定义 name。 若缺失 arglist, 则将 name 定义为普通符号,替换为 rx-form。 示例:

(rx-define haskell-comment (seq "--" (zero-or-more nonl)))
(rx haskell-comment)
     ⇒ "--.*"

若存在 arglist, 则它必须是包含零个或多个参数名的列表, 此时将 name 定义为参数化形式。 在 rx 表达式中以 (name arg…) 形式使用时, 每个 arg 将替换 rx-form 内对应的参数名。

arglist 可以 &rest 和一个最终参数名结尾, 表示剩余参数。 剩余参数将展开为 arglist 中其他参数未匹配到的所有额外实际参数值, 并在 rx-form 中出现的位置插入。 示例:

(rx-define moan (x y &rest r) (seq x (one-or-more y) r "!"))
(rx (moan "MOO" "A" "MEE" "OW"))
     ⇒ "MOOA+MEEOW!"

由于定义是全局的, 建议像命名非局部变量和函数时通常那样, 为 name 添加包前缀以避免与其他位置的定义冲突。

以此方式定义的形式仅执行简单模板替换。 若要进行任意计算, 可将其与 rx 形式 evalregexpliteral 一起使用。 示例:

(defun n-tuple-rx (n element)
  `(seq "<"
        (group-n 1 ,element)
        ,@(mapcar (lambda (i) `(seq ?, (group-n ,i ,element)))
                  (number-sequence 2 n))
        ">"))
(rx-define n-tuple (n element) (eval (n-tuple-rx n 'element)))
(rx (n-tuple 3 (+ (in "0-9"))))
  ⇒ "<\\(?1:[0-9]+\\),\\(?2:[0-9]+\\),\\(?3:[0-9]+\\)>"
Macro: rx-let (bindings…) body…

使 bindings 中的 rx 定义 在 body 内的 rx 宏调用中局部可用, 随后对 body 进行求值。

bindings 的每个元素格式为 (name [arglistrx-form), 各部分含义与上述 rx-define 相同。 示例:

(rx-let ((comma-separated (item) (seq item (0+ "," item)))
         (number (1+ digit))
         (numbers (comma-separated number)))
  (re-search-forward (rx "(" numbers ")")))

定义仅在 body 的宏展开期间可用, 因此在编译代码的执行期间不存在。

rx-let 不仅可在函数内部使用, 也可在顶层用于需要共享一组公共 rx 形式的 全局变量和函数定义。 由于名称在 body 内部是局部的, 无需任何包前缀。 示例:

(rx-let ((phone-number (seq (opt ?+) (1+ (any digit ?-)))))
  (defun find-next-phone-number ()
    (re-search-forward (rx phone-number)))
  (defun phone-number-p (string)
    (string-match-p (rx bos phone-number eos) string)))

rx-let 绑定的作用域是词法的, 这意味着它们在 body 外部不可见, 即使在从 body 调用的函数中也是如此。

Macro: rx-let-eval bindings body…

rx-let 一样对 bindings 求值以获取绑定列表, 并在这些绑定生效的情况下对 body 求值, 用于对 rx-to-string 的调用。

此宏与 rx-let 类似, 区别在于 bindings 参数会被求值(因此若为列表字面量则需要引用), 且定义在运行时替换, 这是 rx-to-string 正常工作所必需的。 示例:

(rx-let-eval
    '((ponder (x) (seq "Where have all the " x " gone?")))
  (looking-at (rx-to-string
               '(ponder (or "flowers" "young girls"
                            "left socks")))))

rx-let 的另一个区别是, bindings 是动态作用域的, 因此在从 body 调用的函数中也可用。 但它们在 body 中定义的函数内部不可见。