2018年1月23日から25日にかけて、TC39の第62回ミーティングが行われました。TC39のミーティングでは、提案されているECMAScriptの新機能 (proposal) について審議し、各proposalのステージの移動を決定します。
今回のミーティングでは正規表現に関する幾つかのproposalがStage 4になりました。Stage 4になったproposalはES2018に組み込まれ、JavaScript (ECMAScript) に正式採用ということになります。
この記事では、JavaScriptに追加された正規表現の4つの新機能を紹介します。
s
(dotAll
) flag for regular expressions
Proposal: https://github.com/tc39/proposal-regexp-dotall-flag
正規表現の新たなフラグとしてs
が追加されました。フラグというのは、大文字小文字を無視するi
や繰り返しマッチを意味するg
などのことです。
s
フラグの効果は、.
が真に全ての文字にマッチするようになるというものです。
というのも、.
は全ての文字にマッチする記号として知られていますが、実は本当は全ての文字にマッチするわけではありません。改行文字(CR, LF, U+2028, U+2029)にマッチしないからです。
console.log(/foo.bar/.test(`foo
bar`)); // false
ES2018で新しく導入されたs
フラグが付いている正規表現は、.
が真に全ての文字にマッチするようになります。
console.log(/foo.bar/s.test(`foo
bar`)); // true
これにより、全ての文字を[\s\S]
や[^]
で表すといったテクニックは不要になります。(改行にはマッチしない.
も使いたいというような場合は別ですが。)
RegExp Named Capture Groups
Proposal: https://github.com/tc39/proposal-regexp-named-groups
これはキャプチャグループに名前を付けられるようになるというものです。
キャプチャグループというのは、正規表現で使える( ... )
という構文のことです。特に、グループにマッチした部分文字列はmatchなどのメソッドで取得できます。
const str = 'foo bar';
const [, left, right] = str.match(/^(\w+)\s+(\w+)$/);
console.log(left); // 'foo'
console.log(right); // 'bar'
この例では、matchの返り値である配列の1番目をleft, 2番目をrightとして表示しています。このように、従来キャプチャグループは番号で識別されました。
番号でグループを区別するのは分かりにくいということで、ES2018ではグループに名前を付けることができるようになりました。そのためには、(?<name> ... )
という記法を用います。name
というところにこのグループに付けたい名前を入れてください。
const str = 'foo bar';
const {
groups: {
left,
right,
},
} = str.match(/^(?<left>\w+)\s+(?<right>\w+)$/);
console.log(left); // 'foo'
console.log(right); // 'bar'
matchメソッドの場合、返り値は配列ですが、その配列にgroupsというプロパティが追加されています。これはオブジェクトであり、各グループにマッチした文字列が入っています。
また、グループにマッチした文字列は正規表現中で\1
のように参照することができました。名前付きグループの場合は\k<name>
という構文で可能になっています。
console.log(/^(?<part>\w+)\k<part>$/.test('foofoo')); // true
String.prototype.replaceメソッドで$1
などとしていたのも同様に$<name>
での参照が可能になっています。
RegExp Lookbehind Assertions
Proposal: https://github.com/tc39/proposal-regexp-lookbehind
これは、正規表現に後読み機能が追加されるというものです。
正規表現は従来から先読み機能がありましたが、その逆向きバージョンとなっています。構文は(?<= ...)
です。
次の例は、$
(ドル記号)に続く数字の列にマッチするという典型的な正規表現です。
const result = '123$456 $789 $ 000'.match(/(?<=\$)\d+/g);
console.log(result); // ['456', '789']
(?<=\$)
という部分で後読み機能を使用しています。これは、(?<=\$)
がある位置は$
が直前にあるような位置でなければならないという意味になります。そして、そのような位置の直後に\d+
、すなわち数字の列があればマッチします。ポイントは、上の例では123
や000
はこの正規表現にマッチしないということです。なぜなら、それらの場合は(?<=\$)
にあたる位置の直前に$
が無いからです。
後読み機能のいいところは、後読み部分はマッチした文字列に含まれない点です。後読み機能を使わずに上の例と同じことをしたい場合は、例えば次のように$
をマッチ文字列に含めたあとで除去する必要があります。
const result = '123$456 $789 $ 000'.match(/\$\d+/g)
.map(x=> x.slice(1));
console.log(result); // ['456', '789']
ちなみに、後読みには否定形もあります。それは(?<! ...)
です。
Unicode property escapes in regular expressions
Proposal: https://github.com/tc39/proposal-regexp-unicode-property-escapes
これは、Unicodeで文字に付与されているプロパティを正規表現で利用できるという機能です。構文は\p{PropertyName=PropertyValue}
です。
Unicodeでは、各文字にさまざまなプロパティが付与されています。一例として、General_Category
というプロパティは文字の分類を表します。例えば、Decimal_Number
は0〜9の数字を表す値です。General_CategoryがDecimal_Numberであるような文字にマッチする表現は\p{General_Category=Decimal_Number}
と書けます。
const re = /^\p{General_Category=Decimal_Number}+$/u;
console.log(re.test('123')); // true
console.log(re.test('123foo456')); // false
ここで特筆すべきは、Decimal_Numberというカテゴリが与えられているのは半角数字の0から9だけではないということです。例えば全角数字や黒板太字などもこのカテゴリに入ります。
const re = /^\p{General_Category=Decimal_Number}+$/u;
console.log(re.test('12345𝟞𝟕')); // true
なお、この\p{...}
は正規表現のu
フラグが有効になっていないと使えないので注意してください。
ちなみに、上の例の場合General_Categoryはgcという略称があり、Decimal_NumberはNdという略称があるので次のようにすることもできます。
const re = /^\p{gc=Nd}+$/u;
console.log(re.test('12345𝟞𝟕')); // true
また、General_Categoryはよく使われるので省略できます。
const re = /^\p{Nd}+$/u;
console.log(re.test('12345𝟞𝟕')); // true
また、Unicodeにはバイナリプロパティという種類のプロパティもあります。これはそのプロパティに属するか属さないかの2種類しかないプロパティです。この場合は\p{プロパティ名=値}
ではなく\p{プロパティ名}
とします。例えばLowercase
というプロパティは小文字を表すプロパティです。
const re = /^\p{Lowercase}+$/u;
console.log(re.test('foobar')); // true
console.log(re.test('Foobar')); // false
console.log(re.test('ελληνικά')); // true
この新機能は、Unicodeに存在する各種のプロパティを知っていなければ使えないので使用難易度が高いものの、今まで簡単に書けなかった正規表現を簡単に書くことができます。特に、Unicodeに収録されている様々な種類の文字に簡単に対応できるようになります。例えば最後の例に見えるように、\p{Lowercase}
はラテン文字だけでなくギリシャ文字などの小文字にも対応しており、[a-z]
とは異なることが分かります。
ブラウザ・処理系の対応
これらの新機能はいつから使えるのでしょうか。詳しいことはECMAScript 2016+ compatibility tableを参照してもらいたいのですが、
- Chromeはもう対応済(Chrome 64以上)
- Firefoxはまだ
- Edgeもまだ
- Safariもまだ(後読み以外はTPにはある)
- node.jsはフラグが必要(8.x以上)
という感じです。
まとめ
ES2018で追加された正規表現の新機能を紹介しました。
古くなったのは[\s\S]
や名前のないキャプチャグループとかそのへんです。