nikkie-ftnextの日記

イベントレポートや読書メモを発信

JavaScriptの正規表現のコードに見た\pや\Pって何だ? Unicode文字クラスエスケープ!

はじめに

ほ!!! nikkieです

正規表現について、新しく知ったことがありました。
\p{L}ってなんだと思います?

目次

MDNより「Unicode 文字クラスエスケープ」

Unicode 文字クラスエスケープは文字クラスエスケープの一種で、Unicode プロパティで指定された一連の文字に一致します。

文字クラスエスケープとは、正規表現\d\w
これは見たことあります。

文字クラスエスケープは、文字の集合を表すエスケープシーケンスです。
文字クラスエスケープ: \d, \D, \w, \W, \s, \S - JavaScript | MDN

つまり\pや\Pは文字の集合を表すエスケープシーケンスというわけですね。

Unicode 文字クラスエスケープ」のドキュメントは次のように続きます。

これは Unicode 対応モードでのみ対応しています。

これはuフラグのことですね1
正規表現 - JavaScript | MDN

解説によると

すべての Unicode 文字には、それを記述する一連のプロパティがあります。

例えば、a という文字では、General_Category プロパティが Lowercase_Letter の値であり、Script プロパティが Latn の値です。

な、なんだってー! プロパティなんてものがあったのか

例えば、a は \p{Lowercase_Letter}(General_Category プロパティ名はオプション)と、\p{Script=Latn} によって一致させることができます。

\Pは\pの否定です

Unicode 文字クラスエスケープ」中の例

一般カテゴリー2\p{L}の例があります。
上で引いた「General_Category プロパティ名はオプション」を補足するような形になっています。

以下は正規表現リテラルとして同じ

  • /\p{L}/gu
  • /\p{General_Category=Letter}/gu(General_Categoryというプロパティ名を書いた)
  • /\p{Letter}/gu(General_Categoryを省略)

ブラウザ(Firefox)の開発ツールのコンソールで実行します。

const story = "It's the Cheshire Cat: now I shall have somebody to talk to.";
story.match(/\p{L}/gu);
Array(46) [ "I", "t", "s", "t", "h", "e", "C", "h", "e", "s", … ]

空白文字や記号を除いた文字にマッチしました!
story.match(/\p{General_Category=Letter}/gu);のように書き換えても同じ結果です。

Unicode Technical Standard #18 を参照

Unicode Technical Standard #18 Unicode Regular Expressions」に「General Category Property」があります(1.2.5)3
https://unicode.org/reports/tr18/#General_Category_Property

この表には、プロパティの値とその短縮名(Long formとAbb.)が記載されています。

  • Letter(短縮名はL)
  • Uppercase Letter(短縮名はLu)

大文字小文字やスペース、アンダースコアの有無など表記が揺れても、指す文字の集合としては同じとのことです。

any of the following should be equivalent: \p{Lu}, \p{lu}, \p{uppercase letter}, \p{Uppercase Letter}, \p{Uppercase_Letter}, and \p{uppercaseletter}

正規表現辞典』を紐解く

03-04-06 \p{...}、\P{...} Unicodeプロパティに基づく条件に合致する文字にマッチ

例が面白いなと思いました。

  • \p{Lu}(Uppercase Letter)は、例えば全角のGにもマッチ
  • \p{InHiragana}でひらがなにマッチ
"半角のG 全角のG".match(/\p{Lu}/gu);
Array [ "G", "G" ]

"半角のG 全角のG".match(/\p{InHiragana}/gu);

Uncaught SyntaxError: invalid property name in regular expression

でした。未サポートなんですかね?

Pythonではregex

標準ライブラリreの拡張。
Unicode codepoint propertiesもサポートしています!

>>> regex.findall(r"\p{Lu}", "半角のG 全角のG")
['G', 'G']
>>> regex.findall(r"\p{InHiragana}", "半角のG 全角のG")
['の', 'の']

Python 3.10.9、regex 2024.4.16で動かしています

終わりに

Unicode文字クラスエスケープを知りました。

  • \p{}\P{}{}中にUnicode文字のプロパティを指定できる
  • \dのように文字の集合を指定しているということ
    • 例えば\p{Lu}でUppercase Letterを指定。これは半角だけでなく全角などにもマッチする
  • JavaScriptではunicodeフラグを指定した正規表現リテラル

InHiraganaなど、Unicode文字クラスエスケープで指定するのは便利そうな気がします。

P.S. そもそもどこで\pを見かけた?

VS Codeの実装を覗いていて!4
https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1135

public static titleBoundary = new BackwardsCompatibleRegExp('(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}', 'gmu');

JavaScript正規表現リテラルには、本文で紹介した/\p{Lu}/guの記法の他に、RegExpオブジェクトを使う方法があります5
後者の方法ではバックスラッシュを使って特殊文字エスケープします。
なので、'\\p'となると理解しました。
ref: 正規表現 - JavaScript | MDN

"半角のG 全角のG".match(new RegExp("\\p{Lu}", "gu"));
Array [ "G", "G" ]

  1. 過去にある正規表現を理解するために書いた記事でもフラグに言及しました
  2. プロパティ名 General_Category の日本語訳と思われます
  3. この記事で参照しているMDNドキュメントの「例」の「一般カテゴリー」で示されていました
  4. この記事は登壇準備の一環です
  5. https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE%E3%81%AE%E4%BD%9C%E6%88%90