含まない正規表現の罠

ある文字列を含まない正規表現について調べるとPerlでは(?:(?!foo).)+とある。
fooの部分には任意のパターンが来ることができる。
時々このパターンは便利で、例えばJR○○駅のうち、JR難波駅以外というのは、こういうパターンで書ける。

JR(?:(?!難波).)+?駅

まぁこんな調子で抜き出すことができる。
まぁ便利な正規表現です。


ところが調べていたらこんな欠陥が浮かび上がってきた。
というのもgankaiという文字列を含まないパターンを作ってこう調べた。

print join(',' , 'gankai'=~m/(?:(?!gankai).)+/g),"\n";

するとankaiとヒットする。確かに正解です。
けど直感的にはgankaでもいいように見えますね。
普通は頭からマッチさせていってマッチしたらそれで進めて行くという風になっています。
いくら正規表現が欲張りだからってマッチしたものを破棄してまで進めたりはしない。
なのでgankaになると思ったのですが、実際にはgから始まらず次のaから始まっている。
さらにこういうのも調べてみた。

print 'gankai'=~m/^(?:(?!gankai).)+/ ? "Hit!" : "None!";

さっきのことが本当か、先頭からマッチさせてマッチするか調べた。
するとマッチしないという。gから始まるのでは何かがだめらしい。


そこで調べて気付いたのだが、実は(?!~)っていう拡張正規表現の意味は後ろにマッチするものがないという意味らしい。
Perlの拡張正規表現には先読みと後読みという機能がある。
先読みというのはこれから読むところを先にチェックすること。
後読みというのは既に読んだところを改めてチェックすること。
例えば豚(?=肉)とすると、肉が後ろに続く豚という文字列にのみマッチする。
このように後ろにある後で読もうとすることを先にチェックすることを先読みというわけ。
逆に(?<=豚)肉とすると、豚が前に来る肉という文字列にのみマッチする。これが後読み。
このうち先読みを活用したのがさっきの正規表現。
(?=xxx)はxxxに後ろがマッチする、(?!xxx)はxxxが後ろにマッチしないという正規表現。
そして(?:(?!gankai).)+の意味を解くと、
gankaiという文字列が後ろに続かないところにある1文字にマッチするというパターンが1回以上つづくという意味。
gankaiという文字列については、gの前でgankaiという文字列が後ろに続かないという条件を満たさないので、
1文字目のgからマッチが始まらないと言うこと。その次のaの字の前から見ればgankaiという文字列はないので問題なし。
nの前にも…そして最後にiを読みとって終わり。
そのためさっきのような問題になったわけですね。


じゃあ後読みを使えばいいのかというとそういうわけでもないでしょう。
後読みの場合もそれまでにその文字列を読み取っていないかだけのチェックですから。
だから正規表現ではその文字列を含まないパターンを作るのはきわめて難しいと。
まぁ簡単なパターンを組み合わせて長い長いパターンを作るという手もあるけど。
まぁ確実なのはこんなパターンですけど。

(.+)(?(?{$1!~m|gankai|})|(?!))

ようは読み取った部分がマッチしなければいいんですよ。
コードを埋め込んだ全知全能の正規表現の記事で取り上げたとおりにそれを書きました…
けどクラッシュしました。まぁ無茶ですね。
なかなかままならないものです。
まぁ一般的には(?:(?!xxx).)+というパターンは問題ないように思います。
けどこういう問題も実は存在すると言うことを忘れてはならないと思う。
僕も今日初めて気付いたけど(?!xxx)っていうパターンは含まない文字列専用じゃないということ。
それを忘れないで欲しいですね。
それさえ分かってれば結構納得のいく話だと思います。
だって含まない文字列専用ならなんでこれと他のものを組み合わせて(?:(?!xxx).)+なんて書かないといけないのか納得いきませんからね。
これも工夫です。