正規表現の後読みアサーション
ECMA-262仕様の第3版で導入された正規表現は、1999年以来JavaScriptの一部となっています。機能性と表現力において、JavaScriptの正規表現の実装は他のプログラミング言語とほぼ同等です。
JavaScriptの正規表現であまり注目されないが、時には非常に便利な機能に「先読みアサーション」があります。例えば、パーセント記号が続く数字のシーケンスをマッチさせるには、/\d+(?=%)/
を使用します。この場合、パーセント記号自体はマッチ結果の一部にはなりません。これの否定形である/\d+(?!%)/
は、パーセント記号が続かない数字のシーケンスをマッチさせます。
/\d+(?=%)/.exec('100% of US presidents have been male'); // ['100']
/\d+(?!%)/.exec('that’s all 44 of them'); // ['44']
先読みの反対である後読みアサーションは、JavaScriptには存在していませんでしたが、.NETフレームワークのような他の正規表現の実装には存在しています。アサーション内でマッチを見つけるために正規表現エンジンが後ろに向かって読み進めます。例えば、ドル記号に続く数字のシーケンスは/(?<=\$)\d+/
でマッチでき、この場合ドル記号はマッチ結果に含まれません。これの否定形である/(?<!\$)\d+/
は、ドル記号以外のものに続く数字のシーケンスをマッチさせます。
/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill'); // ['100']
/(?<!\$)\d+/.exec('it’s worth about €90'); // ['90']
一般的に、後読みアサーションには2つの実装方法があります。一例として、Perlでは後読みパターンに固定長が必要で、*
や+
といった量指定子は使用できません。この方法では、正規表現エンジンが固定長分だけ後ろに移動し、その位置から先読みと同じ方法でマッチを実行できます。
.NETフレームワークの正規表現エンジンは異なるアプローチを採用しています。後読みパターンが何文字マッチするかを知る必要がなく、単純に後ろ向きに後読みパターンをマッチさせ、通常の読み方向と逆の方向で文字を読みます。このため、後読みパターンでは完全な正規表現の構文を利用して任意の長さのパターンをマッチさせることができます。
明らかに、2番目の方法は1番目の方法よりも強力です。このため、V8チームとこの機能のTC39のチャンピオンは、JavaScriptがより表現力豊かなバージョンを採用すべきであると合意しました。この実装はやや複雑ではありますが。
後読みアサーションは後ろ向きにマッチするため、他では驚くべきものと考えられる微妙な動作があります。例えば、量指定子を持つキャプチャグループは最後のマッチをキャプチャします。通常、それは一番右側のマッチです。しかし、後読みアサーション内では右から左にマッチするため、一番左側のマッチがキャプチャされます:
/h(?=(\w)+)/.exec('hodor'); // ['h', 'r']
/(?<=(\w)+)r/.exec('hodor'); // ['r', 'h']
キャプチャグループはキャプチャされた後に後方参照で参照できます。通常、後方参照はキャプチャグループの右側にある必要があります。それ以外の場合、何もキャプチャされていないため空文字列にマッチします。しかし、後読みアサーション内ではマッチ方向が逆転します:
/(?<=(o)d\1)r/.exec('hodor'); // null
/(?<=\1d(o))r/.exec('hodor'); // ['r', 'o']
後読みアサーションは現在、TC39仕様プロセスの非常に初期段階にあります。しかし、この機能は正規表現構文の明らかな拡張であるため、実装を優先することにしました。すでにV8バージョン4.9以降で--harmony
を使用するか、Chromeバージョン49以降で実験的なJavaScript機能を有効にする(about:flags
を使用)ことで、後読みアサーションを試すことができます。