JavaScriptの正規表現の戻り読みはPerlのそれよりも表現力が高い2018年12月29日 22時37分

ECMAScript 2018で正規表現の戻り読み(lookbehind)が追加されました。

/(?<=foo)bar/.test('foobar'); // => true
'foobar'.replace(/(?<=foo)bar/, 'baz'); // => 'foobaz

正規表現の戻り読みと言えばPerlでは1998年7月リリースのバージョン5.005からサポートしており、そこから20年もたってと思いたくなるかもしれません。しかし、ECMAScript (JavaScript)のそれはPerlのものとは一味違います。なんと戻り読みの中で量指定子(*+?{n}など)を使えるのです。

// JavaScriptなら(?<=...)の中で+が使える。
/(?<=fo+)bar/.test('foobar'); // => true
# Perlでは(?<=...)の中で+を使おうとするとエラーになる。
"foobar" =~ m/(?<=fo+)bar/;
# => Variable length lookbehind not implemented in regex m/(?<=fo+)bar/ at - line 1.

Perlの場合、戻り読みとして指定されたパターンの文字数だけ戻ってから(文字列末尾方向に向けて)マッチしていきます。あらかじめ戻る文字数がわからないといけないので、戻り読みの中で量指定子を使えません。

一方、ECMAScriptでは戻り読みとして指定されたパターンそのものを逆方向に解釈し、1文字ずつ逆向きに(文字列先頭方向に向けて)マッチしていきます。ですから量指定子が含まれていても問題ないのです。

パターンが逆方向に解釈されるので、後方参照を使えるようになるのも戻る方向になります。

// 1番目の(...)よりも\1のほうが文字列先頭方向側にある。
                                 //     $&     $1
/(?<=f\1(o))bar/.exec('foobar'); // => ["bar", "o"]

ただし、後方参照の番号はパターン内での出現順のままです。

                                              //     $&          $1     $2
/([0-9]+)([0-9]+)兆円/.exec('5000兆円');      // => ["5000兆円", "500", "0"  ]
/(?<=([0-9]+)([0-9]+))兆円/.exec('5000兆円'); // => ["兆円",     "5",   "000"]