rx
フォームの定義 ¶新たなシンボル他のrx
式にたいしてパラメーター化されたフォームを定義することにより、rx
表記を拡張できます。これにより手軽にパーツを複数のregexp間で共有して、より小さな断片を互いに組み合わせて、複雑なregexpをより簡単に構築、理解することができます。
たとえば(one-or-more letter)
を意味するname
や、任意のxにたいして(seq
?' x ?')
を意味する(quoted
x)
を定義できます。これらのフォームは他のrx
式と同様に使用できます。(rx (quoted
name))
はシングルクォート内の非空の文字シーケンスにマッチするでしょう。
以下のLispマクロは定義にたいして名前を構築する別の手段を提供します。以下のルールはこれらすべてにたいして共通です:
digit
やgroup
のようなビルトインのrx
フォームは再定義不可。
-regexp
のようなサフィックスを付加する必要はない。これらが他と衝突することはあり得ない。
rx
やrx-to-string
の呼び出し内でのみ展開される。これはたとえ定義が互いを参照している際でも定義の順序は問題ではなく、定義の構文エラーは定義時ではなく使用時のみ顕現することを意味する。
rx
式が期待される任意の箇所でユーザー定義フォームが許される。たとえばzero-or-one
フォームのbody内では許されるが、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が与えられた場合には0個以上の引数名のリストでなければならず、その場合にはnameはパラメーター化されたフォームとして定義される。(name
arg…)
としてrx
式内で使用時には、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]+\\)>"
body内のrx
マクロ呼び出しでbindings内のrx
をローカルに使用可能にしてから評価する。
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をバインディングのリストに評価して、rx-to-string
呼び出しに有効なこれらバインディングによってbodyを評価する。
このマクロは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内で定義される関数の内部では不可視。