Previous: , Up: Rx Notation   [Contents][Index]


35.3.3.3 新たなrxフォームの定義

新たなシンボル他のrx式にたいしてパラメーター化されたフォームを定義することにより、rx表記を拡張できます。これにより手軽にパーツを複数のregexp間で共有して、より小さな断片を互いに組み合わせて、複雑なregexpをより簡単に構築、理解することができます。

たとえば(one-or-more letter)を意味するnameや、任意のxにたいして(seq ?' x ?')を意味する(quoted x)を定義できます。これらのフォームは他のrx式と同様に使用できます。(rx (quoted name))はシングルクォート内の非空の文字シーケンスにマッチするでしょう。

以下のLispマクロは定義にたいして名前を構築する別の手段を提供します。以下のルールはこれらすべてにたいして共通です:

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

すべての後続するrxおよびrx-to-string呼び出しにおいて、グローバルにnameを定義する。arglistを省略すると、namerx-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フォームの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…

body内のrxマクロ呼び出しでbindings内のrxをローカルに使用可能にしてから評価する。

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をバインディングのリストに評価して、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内で定義される関数の内部では不可視。