rx フォームの定義 ¶rx 記法は、他の rx
式を用いて新しい記号やパラメータ化された形式を定義することで、拡張可能です。これは、複数の正規表現間で部分を共有したり、複雑な正規表現を小さな構成要素から組み合わせることで、構築や理解を容易にしたりする際に便利です。
例えば、任意の x に対して、name を (one-or-more letter) の意味に、(quoted
x) を (seq ?' x ?') の意味に定義することができます。これらの形式は、他の形式と同様に rx 式で使用でき、例えば (rx (quoted
name)) はシングルクォートで囲まれた空でない文字シーケンスにマッチします。
以下のLispマクロは、名前を定義に結びつけるためのさまざまな方法を提供します。これらすべてに共通するルールは以下の通りです。
digit や group のような組み込みの
rx フォームは、再定義できません。
-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 が存在する場合、それは0個以上の引数名のリストである必要があり、name はパラメータ化された形式として定義されます。rx
式の中で (name arg…) として使用される場合、各 arg は rx-form 内の対応する引数名を置き換えます。
arglist は &rest
と最後の引数名で終了する場合があり、これは rest パラメータを表します。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]+\\)>"
Make the rx definitions in bindings available locally for rx macro invocations in body,
which is then evaluated.
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
の外部からは参照できません。
bindings を rx-let
と同様に束縛のリストとして評価し、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で定義された関数内では参照できません。