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

ES2015 では、Unicode(/u)フラグとスティッキー(/y)フラグによる正規表現構文の大幅な改善など、JavaScript 言語に多くの新機能が導入されました。しかし、それ以降も開発は止まっていません。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

1 つのプロポーザルで、/s フラグで有効にする dotAll モードが導入されています。dotAll モードでは、. は行終端子にも一致します。

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

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

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

ES2015 で Unicode 認識が導入されたことで、数字と見なされる文字(丸で囲まれた数字 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 プロパティクラスの一覧については、現在の仕様案をご覧ください。その他の例については、こちらの記事をご覧ください。

後方参照アサーション

先読みアサーション(lookahead assertion)は、JavaScript の正規表現の構文の一部として最初から存在していました。対応する lookbehind アサーションもついに導入されました。これはすでにかなり長い間 V8 の一部であったことを覚えている方もいるかもしれません。内部では、ES2015 で指定された Unicode フラグを実装するために、ルックバック アサートを使用しています。

名前自体がその意味をよく表しています。ルックバック グループのパターンが先行する場合にのみパターンを照合するように制限できます。一致と非一致の両方のフレーバーがあります。

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

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

謝辞

このブログ投稿では、この実現に尽力した方々について言及したいと思います。特に、言語チャンピオンの Mathias Bynens 氏、Dan Ehrenberg 氏、Claude Pache 氏、Brian Terlson 氏、Thomas Wood 氏、Gorkem Yakin 氏、Irregexp の第一人者である Erik Corry 氏、そして、言語仕様と V8 でのこれらの機能の実装に貢献したすべての方々に感謝します。

これらの新しい正規表現機能が皆様のお役に立てば幸いです。