あなたの知っている正規表現はもう古い！　正規表現の新常識（ES2018編）

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)にマッチしないからです。

```javascript
console.log(/foo.bar/.test(`foo
bar`)); // false
```

ES2018で新しく導入された`s`フラグが付いている正規表現は、`.`が真に全ての文字にマッチするようになります。

```javascript
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などのメソッドで取得できます。

```js
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`というところにこのグループに付けたい名前を入れてください。

```js
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>`という構文で可能になっています。

```js
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

これは、正規表現に**後読み**機能が追加されるというものです。

正規表現は従来から先読み機能がありましたが、その逆向きバージョンとなっています。構文は`(?<= ...)`です。

次の例は、`$`（ドル記号）に続く数字の列にマッチするという典型的な正規表現です。

```js
const result = '123$456 $789 $ 000'.match(/(?<=\$)\d+/g);

console.log(result); // ['456', '789']
```

`(?<=\$)`という部分で後読み機能を使用しています。これは、`(?<=\$)`がある位置は`$`が直前にあるような位置でなければならないという意味になります。そして、そのような位置の直後に`\d+`、すなわち数字の列があればマッチします。ポイントは、上の例では`123`や`000`はこの正規表現にマッチしないということです。なぜなら、それらの場合は`(?<=\$)`にあたる位置の直前に`$`が無いからです。

後読み機能のいいところは、後読み部分はマッチした文字列に含まれない点です。後読み機能を使わずに上の例と同じことをしたい場合は、例えば次のように`$`をマッチ文字列に含めたあとで除去する必要があります。

```js

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}`と書けます。

```js
const re = /^\p{General_Category=Decimal_Number}+$/u;

console.log(re.test('123')); // true
console.log(re.test('123foo456')); // false
```

ここで特筆すべきは、Decimal_Numberというカテゴリが与えられているのは半角数字の0から9だけではないということです。例えば全角数字や黒板太字などもこのカテゴリに入ります。

```js
const re = /^\p{General_Category=Decimal_Number}+$/u;

console.log(re.test('１２3４5𝟞𝟕')); // true
```

なお、この`\p{...}`は正規表現の`u`フラグが有効になっていないと使えないので注意してください。

ちなみに、上の例の場合General_Categoryはgcという略称があり、Decimal_NumberはNdという略称があるので次のようにすることもできます。

```js
const re = /^\p{gc=Nd}+$/u;

console.log(re.test('１２3４5𝟞𝟕')); // true
```

また、General_Categoryはよく使われるので省略できます。

```js
const re = /^\p{Nd}+$/u;

console.log(re.test('１２3４5𝟞𝟕')); // true
```

また、Unicodeにはバイナリプロパティという種類のプロパティもあります。これはそのプロパティに属するか属さないかの2種類しかないプロパティです。この場合は`\p{プロパティ名=値}`ではなく`\p{プロパティ名}`とします。例えば`Lowercase`というプロパティは小文字を表すプロパティです。

```js
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](http://kangax.github.io/compat-table/es2016plus/)を参照してもらいたいのですが、

- Chromeはもう対応済（Chrome 64以上）
- Firefoxはまだ
- Edgeもまだ
- Safariもまだ（後読み以外はTPにはある）
- node.jsはフラグが必要（8.x以上）

という感じです。

# まとめ
ES2018で追加された正規表現の新機能を紹介しました。

古くなったのは`[\s\S]`や名前のないキャプチャグループとかそのへんです。