今後予定されている正規表現機能

Jakob Gruber
Yang Guo

ES2015 では、JavaScript 言語に多くの新機能が導入されています。たとえば、Unicode フラグ(/u)とスティッキー(/y)フラグを使用する正規表現構文が大幅に改善されています。しかし、その後も開発は止まっていません。V8 チームは、TC39(ECMAScript 標準化団体)の他のメンバーと緊密に連携し、正規表現をさらに強化するためのいくつかの新機能を提案し、共同設計しています。

これらの機能は現在、JavaScript 仕様に含めるために提案中です。提案が完全には承認されていませんが、すでに TC39 プロセスのステージ 3 に入っています。仕様が確定する前に、設計と実装に関するタイムリーなフィードバックをそれぞれの提案者に提供できるよう、これらの機能をフラグ(下記参照)で実装しています。

このブログ投稿では、このエキサイティングな未来についてご紹介します。以降の例で説明する場合は、chrome://flags/#enable-javascript-harmony で試験運用版の JavaScript 機能を有効にします。

名前付きキャプチャ

正規表現には、一致したテキストの一部をキャプチャできる、いわゆるキャプチャ(またはグループ)を含めることができます。今のところ、デベロッパーはこれらのキャプチャをインデックスでのみ参照できました。インデックスは、パターン内のキャプチャの位置によって決まります。

const pattern = /(\d{4})-(\d{2})-(\d{2})/u;
const result = pattern.exec('2017-07-10');
// result[0] === '2017-07-10'
// result[1] === '2017'
// result[2] === '07'
// result[3] === '10'

しかし、正規表現の読み取り、書き込み、保守が難しいことは周知の事実であり、数値参照はさらに複雑になる可能性があります。たとえば、より長いパターンでは、特定のキャプチャのインデックスを特定することが難しい場合があります。

/(?:(.)(.(?<=[^(])(.)))/  // Index of the last capture?

さらに悪いことに、パターンを変更すると、既存のすべてのキャプチャのインデックスが変化する可能性があります。

/(a)(b)(c)\3\2\1/     // A few simple numbered backreferences.
/(.)(a)(b)(c)\4\3\2/  // All need to be updated.

名前付きキャプチャは、デベロッパーがキャプチャに名前を割り当てられるようにすることで、これらの問題を軽減する予定の機能です。構文は Perl、Java、.Net、Ruby に似ています。

const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const result = pattern.exec('2017-07-10');
// result.groups.year === '2017'
// result.groups.month === '07'
// result.groups.day === '10'

名前付きキャプチャは、名前付き後方参照や String.prototype.replace でも参照できます。

// Named backreferences.
/(?<LowerCaseX>x)y\k<LowerCaseX>/.test('xyx');  // true

// String replacement.
const pattern = /(?<fst>a)(?<snd>b)/;
'ab'.replace(pattern, '$<snd>$<fst>');                              // 'ba'
'ab'.replace(pattern, (m, p1, p2, o, s, {fst, snd}) => fst + snd);  // 'ba'

この新機能について詳しくは、仕様案をご覧ください。

dotAll フラグ

デフォルトでは、正規表現の . アトムは、行終端子を除くすべての文字に一致します。

/foo.bar/u.test('foo\nbar');   // false

提案では、/s フラグで有効になる dotAll モードを導入します。dotAll モードでは、. はライン ターミネータも照合します。

/foo.bar/su.test('foo\nbar');  // true

この新機能について詳しくは、仕様案をご覧ください。

Unicode プロパティのエスケープ

ES2015 で Unicode 認識が導入されたことで、数字と見なされる文字が増えました。たとえば、丸で囲まれた数字の 1: 1 や、単語と見なされる「雪」を表す中国語の文字「雪」が、文字として認識されるようになっています。

いずれも \d\w とは一致しません。これらの省略形の意味を変更すると、既存の正規表現パターンが機能しなくなります。

代わりに、新しいプロパティのエスケープ シーケンスが導入されています。これらは、/u フラグで示される Unicode 対応の正規表現でのみ使用できます。

/\p{Number}/u.test('①');      // true
/\p{Alphabetic}/u.test('雪');  // true

その逆は \P で照合できます。

/\P{Number}/u.test('①');      // false
/\P{Alphabetic}/u.test('雪');  // false

Unicode コンソーシアムでは、数学記号やひらがななど、他にも多くのプロパティを定義しています。

/^\p{Math}+$/u.test('∛∞∉');                            // true
/^\p{Script_Extensions=Hiragana}+$/u.test('ひらがな');  // true

サポートされている Unicode プロパティ クラスの一覧については、現在の仕様案をご覧ください。その他の例については、こちらの情報記事をご覧ください。

後回しのアサーション

先読みアサーションは、当初から JavaScript の正規表現構文に含まれています。対応する後方のアサーションが、ついに導入されます。すでにかなり前から V8 に搭載されていたことを覚えている方もいらっしゃるかもしれません。さらに、ES2015 で指定された Unicode フラグを実装するために、内部でルックビハインド アサートを使用しています。

この名前はすでにその意味をよく表しています。これを使用すると、ルックビハインド グループのパターンが先行している場合にのみパターンが一致するように制限できます。一致するフレーバーと一致しないフレーバーの両方があります。

/(?<=\$)\d+/.exec('$1 is worth about ¥123');  // ['1']
/(?<!\$)\d+/.exec('$1 is worth about ¥123');  // ['123']

詳細については、後付けのアサーションに関する以前のブログ投稿と、関連する V8 テストケースの例をご覧ください。

謝辞

このブログ投稿は、言語仕様の実現に尽力した人々、特に言語チャンピオンの Mathias BynensDan EhrenbergClaude PacheBrian TerlsonThomas Wood、Gorkem Yakin、Irregexp の Guru 仕様の実装に貢献している、Irregexp GuruErik8 の機能の一人でもあることに言及しない限り、この説明は完成しません。

これらの新しい正規表現機能をぜひご活用ください。