rx 形式 ¶可通过基于其他 rx 表达式定义新符号和参数化形式
来扩展 rx 表示法。
这便于在多个正则表达式之间共享部分内容,
并通过将复杂正则表达式由较小部分组合而成,
使其更易于构建和理解。
例如,可将 name 定义为
(one-or-more letter),
将 (quoted x) 定义为
(seq ?' x ?')(适用于任意 x)。
这些形式可像其他形式一样在 rx 表达式中使用:
(rx (quoted name))
将匹配单引号内的非空字母序列。
以下 Lisp 宏提供了将名称绑定到定义的不同方式。 所有宏均遵循以下规则:
rx 形式(如 digit 和 group)
不可重定义。
-regexp 之类的后缀;
它们不会与其他内容冲突。
rx 或 rx-to-string 时展开,
而非仅因存在于定义宏中就展开。
这意味着定义的顺序无关紧要,
即使它们相互引用也是如此;
语法错误仅在使用定义时出现,而非定义时。
rx 表达式的位置均允许使用用户定义形式;
例如,在 zero-or-one 形式的主体中,
但不在 any 或 category 形式内部。
它们也可在 not 和 intersection 形式内部使用。
在后续所有对 rx 和 rx-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 形式 eval、regexp 或 literal 一起使用。
示例:
(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]+\\)>"
使 bindings 中的 rx 定义
在 body 内的 rx 宏调用中局部可用,
随后对 body 进行求值。
bindings 的每个元素格式为
(name [arglist] rx-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 调用的函数中也是如此。
像 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 中定义的函数内部不可见。