🎄

ENCA 22日目: 正規表現の名前付きキャプチャグループでエスケープシーケンスを扱えるように

2024/12/22に公開

Unicode エスケープシーケンス色々

UTF-16 のサロゲートペアを含む 𝒜 について考えます。これは文字列リテラルとしては次の3つの表現をすることが出来ます。

  • "𝒜"
  • "\ud835\udc9c"
  • "\u{1d49c}"

変数宣言としては次の2つの表現をすることが出来ます。

  • let 𝒜;
  • let \u{1d49c};

正規表現リテラルでは u もしくは v フラグを使った Unicode 対応モードの場合3つの表現をすることが出来ますが、そうでない場合は後方互換性を考慮して "\u{1d49c}" の場合だけ展開されないようになっています(単に \ が無視される)。

console.log(/𝒜/u.test("𝒜")); // true
console.log(/\ud835\udc9c/u.test("𝒜")); // true
console.log(/\u{1d49c}/u.test("𝒜")); // true
console.log(/𝒜/.test("𝒜")); // true
console.log(/\ud835\udc9c/.test("𝒜")); // true
console.log(/\u{1d49c}/.test("u{1d49c}")); // true

なお現代において正規表現は Unicode 対応モードを使うことをおすすめします。ESLint の require-unicode-regexp でチェックすることが出来ます。

正規表現の名前付きキャプチャグループで Unicode エスケープシーケンスを扱えるように

ES2018 RegExp Named Capture Groups によって、正規表現のキャプチャに対して名前をつけられるようになりました。

const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const result = reg.exec("2015-01-02");
console.log(result.groups.year); // "2015"
console.log(result.groups.month); // "01"
console.log(result.groups.day); // "02"

なおこのキャプチャ名は変数名と同じ名前をつけられる事になっています。

/(?<0>.)/; // SyntaxError: 0 は変数名に出来ないためキャプチャ名にも出来ない

さてこのキャプチャ名に対して 𝒜 を使おうとすると以下のような挙動となっていました。

/(?<𝒜>.)/u; // V8 では動くが、JavaScriptCore では SyntaxError
/(?<\ud835\udc9c>.)/u; // OK
/(?<\u{1d49c}>.)/u; // OK
/(?<𝒜>.)/; // SyntaxError
/(?<\ud835\udc9c>.)/; // SyntaxError
/(?<\u{1d49c}>.)/; // SyntaxError

2020年3月にキャプチャ名における構文の扱い、そして Unicode 非対応モードであっても後方互換性の問題を起こさないことから全面的に許容していいのではないかと議論がなされ、いずれの場合も許容されるようになりました。

https://github.com/tc39/ecma262/pull/1869

/(?<𝒜>.)/u; // OK
/(?<\ud835\udc9c>.)/u; // OK
/(?<\u{1d49c}>.)/u; // OK
/(?<𝒜>.)/; // OK
/(?<\ud835\udc9c>.)/; // OK
/(?<\u{1d49c}>.)/; // OK

Discussion