35.3.3.3 Определение новых форм rx

Нотация rx может быть расширена путём определения новых символов и параметризованных форм через другие выражения rx. Это удобно для совместного использования частей между несколькими регулярными выражениями, а также для упрощения создания и понимания сложных выражений путём их сборки из более мелких частей.

Например, вы можете определить name как (one-or-more letter), а (quoted x) как (seq ?' x ?') для любого x. Эти формы затем можно использовать в выражениях rx как любые другие: (rx (quoted name)) будет соответствовать непустой последовательности букв внутри одинарных кавычек.

Макросы Lisp, приведённые ниже, предоставляют различные способы привязки имён к определениям. Для всех них действуют следующие правила:

Макрос: rx-define имя [список-аргументов] rx-форма

Определяет имя глобально во всех последующих вызовах rx и rx-to-string. Если список-аргументов отсутствует, то имя определяется как простой символ, заменяемый на rx-форму. Пример:

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

Если список-аргументов присутствует, он должен быть списком из нуля или более имён аргументов, а имя тогда определяется как параметризованная форма. При использовании в выражении rx как (имя арг…), каждый арг заменяет соответствующее имя аргумента внутри rx-формы.

список-аргументов может заканчиваться на &rest и одно последнее имя аргумента, обозначающее параметр остатка. Параметр остатка раскрывается во все дополнительные фактические значения аргументов, не сопоставленные ни с каким другим параметром в списке-аргументов, вставляясь в rx-форму в место своего появления. Пример:

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

Поскольку определение глобально, рекомендуется давать имени префикс пакета во избежание конфликтов имён с определениями в других местах, как это обычно делается при именовании нелокальных переменных и функций.

Формы, определённые таким образом, выполняют только простую подстановку шаблонов. Для произвольных вычислений используйте их вместе с формами 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]+\\)>"
Макрос: rx-let (привязки…) тело…

Делает определения rx из привязок локально доступными для вызовов макроса rx в теле, которое затем вычисляется.

Каждый элемент привязок имеет форму (имя [список-аргументов] rx-форма), где части имеют тот же смысл, что и в rx-define выше. Пример:

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

Определения доступны только во время макро-раскрытия тела и, следовательно, не присутствуют во время выполнения скомпилированного кода.

rx-let можно использовать не только внутри функции, но и на верхнем уровне для включения глобальных определений переменных и функций, которым требуется общий набор форм rx. Поскольку имена являются локальными внутри тела, нет необходимости в каких-либо префиксах пакетов. Пример:

(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 лексическая, что означает, что они не видны за пределами самого тела, даже в функциях, вызываемых из тела.

Макрос: rx-let-eval привязки тело…

Вычисляет привязки в список привязок, как в rx-let, и вычисляет тело с этими привязками, действующими для вызовов rx-to-string.

Этот макрос похож на rx-let, за исключением того, что аргумент привязки вычисляется (и поэтому его нужно кавычить, если это литерал списка), а определения подставляются во время выполнения, что требуется для работы 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 состоит в том, что привязки имеют динамическую область видимости и, следовательно, также доступны в функциях, вызываемых из тела. Однако они не видны внутри функций, определённых в теле.