正規表現 /g と \G

Perl正規表現、 \G の項目がどうにも分からん!
だって末尾に /g をつけたら最後まで検索しちゃうんでしょ?
$html =~ s/\G\s*<h[1-6]>/<span class="dekamoji">/g; みたいなのが何の意味があるのさ?


↑つい二時間前まではこう思ってた。
でも、それは大きなミステイクだった。
\Gもまた、正規表現の能力を単なるマッチや置換から大幅にパワーアップさせてくれる要素だったんだ。


勘違いの始まりは、正規表現を学べば誰もが目にする s///g からだった。
$mojiretsu =~ s/hogehoge/fugafuga/g;
とすると、検索文字列 $mojiretsu 中のすべての hogehoge を fugafuga に置換する。
でも、それだけだとあまりよい理解じゃなかったんだ。


s///g と似た形をしている、 m//g というコンテキストでの /g の働きをみてみよう。
$mojiretsu =~ m/hogehoge/g;
とした場合は、この正規表現でマッチした場所を記憶して、次回のマッチングの開始位置とする、という意味がある。
例えば上の行に m/チした場/g という正規表現を施したら、次にこの行にマッチを実行するときはそのマッチングの終了位置である '場' の後ろ、 '所' の前からマッチがスタートする。


この二つを見比べてみると、気づくことがあるはずだ。
そう、 s///g はただの全置換ではなく、暗黙のうちに m//g のようなマッチを繰り返し行っている、と考えられるのだ。
(じっさい、\Gの動作はその裏付けになっている)
それさえ認めれば \G についても理解しやすいはず。
\Gは前のマッチ位置を意味しているので、これを使うことで前のマッチ位置の直後との関係について指示できるわけだ。


それが何の役に立つの?っていうと…。
単なる置換だったら \G を使うことはない。ただ探し出して置換していけばいいからだ。
けど、\G を使うってことは正規表現をもっと幅広く使えるようになるってこと。「前の続き」だということを指示できるのだから。


プログラムを解析することを考えてほしい。例えばC言語を。
上の方から見ていって、#includeや#defineを発見したら行末まで読んで判断する。
main関数を発見したら、以後は別処理。
変数の型宣言があって、for文やwhile文があって、と。
こんなこと、いちいち正規表現でやってたら気が狂います。こういった処理はプログラム言語に任せればよい。
正規表現が受け持つのは、「前のマッチ位置から次のマッチを探す」ことだけ。
そして、それをより柔軟にしてくれるのが \G だ。

#include <stdio.h>

int main(void){
  int ikkome = 1;
  double nikome = 2.0;
  int sankome = 3;

  printf("%d %f %d\n",ikkome,nikome,sankome);
}

このソースからintとdoubleの変数を探すプログラムを考えてみよう。
単純に考えれば m/(int|double)\s+(\w+)/g で良い。*1
でも、変数宣言が行われるのはmain関数の先頭だけじゃあない。

#include <stdio.h>

int main(void){
  int ikkome = 1;
  int i;
  printf("%d\n",ikkome);
  for(i=0; i<10; i++){
    dobule nikome = 2.0;
    int sankome = 3;
    i += sankome;
    printf("i = %d\n", i);
  }
}

あの正規表現を上の例に適用すると、for文の中の変数も発見してしまう。
つまり、変数宣言の境界を発見できない。
ここらが正規表現の限界だな…、と思ってもらっては困る。ここで \G が活躍するのだ。


変数宣言を行っているエリアは、【変数の型】があって【変数名】があって、その後に【=即値】があるかもしれなくて最後に【セミコロン】、という形式で一行ができている。とすればその行は、 m/(double|int)\s+(\w+)\s*(=\s*\d+)?;/gc *2みたいなのでマッチできる。
けどこのままでは、依然として変数宣言エリアから外れてからもマッチしてしまう。


これでは困るので、少し考えてみよう。
今回の例では、【セミコロン】までで宣言終了で、そのあとに空白を挟んだりして次の文が始まる。つまり、前のマッチの位置の次には空白があって、そして次の宣言があるならそれも変数宣言だと分かる。
そう、「前のマッチ終了位置」さえ指示できれば、変数宣言の範囲が指定できることになる!


そしてそれを示すのが、 \G というわけだ。
今回の例では、
修正前: m/(double|int)\s+(\w+)\s*(=\s*\d+)?;/gc
修正後: m/\G\s*(double|int)\s+(\w+)\s*(=\s*\d+)?;/gc
このように、ほにゃららなマッチの先頭に \G\s* を付け加えることによって、「前のマッチ位置のあとに空白を挟んでほにゃらら」なものにのみマッチさせることができるようになる。
おまけに /c を設定しているので、このマッチが失敗してもマッチ位置は変数宣言の最後尾にある。プログラムの続きを引き続き正規表現で解析することもできるわけだ!


ふつうの置換では\Gをあえて使うようなことはないだろうけど、このように前のマッチ位置が大事になってくる場合がある。
そんなときは \G を使えば楽ちんに作業できるかもしれない。
知ってる人にとってはほんとに些細なことだろうけど、この動作を理解したと同時にいくつかの勘違いに気づけたので、記事にしてみました。


なお三行目に書いた例は特に意味ないです。

*1:これだと int main にもマッチするので、もっとまじめに書いた方が良いがご愛敬

*2:ここでは即値は\d+ということにしといてください。ちゃんと書くと目にうるさいので。