EmEditor で Onigmo 正規表現エンジンを使おう

  • 13
    Like
  • 0
    Comment
More than 1 year has passed since last update.

EmEditor とは

EmEditor は Windows 用テキストエディター。

巨大ファイルがわりと軽く扱えたり,CSV ファイルが表形式で表示できたり,JavaScript でマクロが書けたり,テキスト比較ツール,行の重複削除やソートが組み込まれてたり,箱形選択・編集ができたりなど,汎用のテキストエディターの中でもプログラマーにとって嬉しい特長をいろいろ持っている。

©© の相互変換とか,パーセントエンコーディングや Base64 形式との相互変換の機能も地味に便利。

シンタックスハイライトや自動補完,スニピット入力などももちろんできる。

正規表現エンジンが選択可能に

今回の記事は,正規表現エンジンとして Onigmo(鬼雲)が使えるようになった,ということを伝えるのが目的だ。

Qiita には Onigmo の記事が無いようなので,Onigmo の特徴的な機能の紹介も兼ねている

バージョン 15.6 までは Boost.Regex

EmEditor バージョン 15.6 までの正規表現エンジンは Boost.Regex のみだった。

Boost.Regex は Boost というオープンソースライブラリー群の一部:

http://www.boost.org/doc/libs/1_59_0/libs/regex/doc/html/

C や C++ などでは定番の正規表現エンジンらしい。

バージョン 15.7 で Onigmo も

バージョン 15.7(2015年12月17日リリース)は正規表現エンジンとして Boost.Regex と共に Onigmo も搭載しており,選択できるようになった。

[ツール]→[カスタマイズ]→[検索]の「既定の正規表現エンジン」で既定のエンジンを選択する。

また,検索・置換を実行するときに,[検索]/「置換」ダイアログの「高度」ボタンを押して切り替えることもできる。

Onigmo とは

Onigmo は Ruby 1.9 に搭載された正規表現エンジン Oniguruma(鬼車)のフォーク。Ruby 2.0 以降に搭載されている。

Onigumo ではなく Onigmo なので注意(u は入らない)。

後で述べるが,漢字の文字クラスが \p{Han} と書けたり,「田中哲スペシャル」などというものも使える。機能・性能ともに優れたエンジンだ。

上でリンクしたリポジトリーには README.ja とか doc/RE.ja といった日本語のドキュメントもあるので機能や書式を調べるのにも困らない。

速度比較

EmEditor 15.7 ベータ版(beta 1)のリリースノート
https://jp.emeditor.com/forums/topic/emeditor-v15-7-0-beta-1-%E3%82%92%E5%85%AC%E9%96%8B%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F/

に,フィルター実行による簡単なベンチマークテストの結果が載っている。

Onigmo が速いテスト項目もあれば,Boost.Regex が速い項目もあるが,Boost.Regex が速いケースでも Onigmo は 1 割増し程度の実行時間で済んでいるが,Onigmo が速いケースでは Boost.Regex は 2~3 倍の時間がかかっている。このベンチマークの範囲内では,まあ「Onigmo に切り替えれば速くなる」と言ってもよさそうだ。

私も大きなファイルで置換をやってみたが,試した範囲では体感できるくらい Onigmo のほうが速かった。

検索機能

Boost.Regex も高機能だが,ここでは Onigmo の強いところを紹介しよう。

Unicode のプロパティーによる文字クラス

Onigmo にすれば,Unicode の文字のプロパティーの値で文字クラスが指定できるようになる。

例えば,〈すべての漢字〉を意味する文字クラスは \p{Han} と書ける。これが使えないと,

[\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{F900}-\x{FAFF}\x{20000}-\x{2A6DF}\x{2A700}-\x{2B73F}\x{2B740}-\x{2B81F}\x{2B820}-\x{2CEAF}\x{2F800}-\x{2FA1F}]

のようなゲンナリする正規表現になる(いちおう確かめたつもりだけど間違ってたらごめん)。

否定文字クラスは p を大文字にした \P{Han} でいい。これで漢字以外の全ての文字を表す(ただし,既定では改行を除く)。

同様に,〈すべてのラテン文字〉なら \p{Latin},〈すべてのハングル〉なら \p{Hangul} と書ける。

Unicode の character property については,英語の Wikipedia を参照のこと(日本語版が無いのが残念)。

https://en.wikipedia.org/wiki/Unicode_character_property

一般カテゴリー(general category)とかスクリプト(script)とか,プロパティーにもいろんな種類がある。

Onigmo でどんなプロパティーが使えるのかは,Onigmo のリポジトリーの UnicodeProps.txt を参照されたい。

ちなみに Hiragana, Katakana, Han, Latin, Greek, Cyrillic については Shfit JIS など非 Unicode 系文字コードのテキストでも使える。

文字コード指定で BMP 外の文字も

Boost.Regex は \x{ } の中身は 4 桁までぽい。これでは基本多言語面(BMP: Basic Multilingual Plane)の文字しか指定できない。ということは「𡈽」も絵文字もダメだ。\x{29E15} はエラーになる。

Onigmo は 6 桁までちゃんと書けるので,Unicode のすべての文字が指定できる。

文字クラスの積演算

文字クラスというのは要するに文字の集合なわけだが,その集合の積演算(集合の共通部分を取る)ができる。

例えば,非 ASCII のラテン文字を検索したいとする。ラテン文字のクラスは \p{Latin} だ。一方,〈ASCII 以外の文字〉は \P{ASCII} と書ける(P は大文字)。そこで,〈非 ASCII のラテン文字〉は

[\p{Latin}&&\P{ASCII}]

と書ける。この && は文字クラスの積演算を表している。

同様に,ASCII のうち,英数字以外の文字なら

[\p{ASCII}&&[^0-9a-zA-Z]]

という具合だ。

部分式呼び出し

「インスタンスメソッド」だの「ジャン=ジャック・ルソー」だのというカタカナ語を拾う正規表現はこう書ける:

\p{Katakana}[\p{Katakana}ー]*([・==]\p{Katakana}[\p{Katakana}ー]*)*

けっこうウザイ。よく見たら

\p{Katakana}[\p{Katakana}ー]*

が 2 回出てくるじゃないか。プログラマーなら「DRY じゃねーよ」と思うことだろう。1 回で済ませたいよね。Onigmo なら

(\p{Katakana}[\p{Katakana}ー]*)([・==]\g<1>)*

と,こう書ける。`

この \g<番号> というのは 部分式呼び出し と呼ばれるものだ。

後方参照みたいに〈後方(左側)で捕獲(capture)された文字列を参照する〉ってのとは違うぞ。文字列じゃなくて部分パターンを呼び出してるんだ。

\g< > の中に入れる番号は,何番目に出てくる ( ) かを表している。

名前付きキャプチャーも使えるので,番号でなく名前による部分式呼び出しも可能だ。さきほどの正規表現は

(?<kata>\p{Katakana}[\p{Katakana}ー]*)([・==]\g<kata>)*

とも書ける。見ての通り ( ) の代わりに (?<名前> ) とし,\g<番号> の代わりに \g<名前> とする。

ちなみに,名前付きキャプチャーの場合,後方参照は \k<名前> とする。

名前付きキャプチャーと名前なしキャプチャーは同時には使えないことに注意しよう。

部分式呼び出しは田中哲(たなかあきら)さんが 2002 年に ruby-dev メーリングリストの [ruby-dev:16732] sharing sub-regexp において,Oniguruma に対する要望の形で提案されたもので,俗に「田中哲スペシャル」と呼ばれている。

応用:回文

次の正規表現で回文を見つけることができる。

((.)(?:\g<1>|.?)\k<2+0>)

試しに

たけやぶやけたらキツツキがトマトをつつきパパがミルクにクルミを入れた

に対して検索してみてくれ。

せっかくなのでこの正規表現を解読してみよう。

3番目の ( ) を伏せ字(xxxx)にすると

((.)xxxx\k<2+0>)

という構造になっている。

\k<2+0> は後方参照であって,2番目の捕獲文字列(つまり (.) の部分で捕獲される文字列)を参照している。

つまり,\2 とだいたい同じだ。そして,+0 の無い \k<2> なら \2 と全く同じである。+0 が何なのかはあとで述べる。

結局,伏せ字にしておいた部分の両端に〈同じ文字〉が存在することを意味している。

さて,さきほど伏せ字にしておいた

(?:\g<1>|.?)

の部分を検討する。これは \g<1>.? の選択の形になっている。

\g<1> は一つ目の ( ) で囲まれた部分パターンの呼び出しだ。部分と言っても,今の場合は正規表現全体である。この正規表現は回文のパターンを表しているはずだから,\g<1> は〈回文〉ということに他ならない。

結局,この検索パターンの全体は〈二つの同じ文字の間に〈0 ないし 1 個の文字〉もしくは回文が挟まったもの〉という意味になる。

だから,「パパ」(同じ文字の間に 0 個の文字)も「トマト」(同じ文字の間に 1 個の文字)も「キツツキ」(同じ文字の間に回文「ツツ」)にもマッチするわけだ。

さて,さきほど宿題にしておいた +0 の意味に移ろう。

回文の正規表現を再掲する。

((.)(?:\g<1>|.?)\k<2+0>)

捕獲の ( ) は,二つしかない。

「トマト」を拾うとき,内側の ( ) は先頭の「ト」を捕獲する。そして (?:\g<1>|.?) は「マ」にマッチし,\k<2+0> は捕獲した「ト」を参照する。

この例だと \k<2+0> の部分は単に \k<2> とか \2 でも問題無い。

しかし,「キツツキ」を拾うときはどうだろう。
内側の ( ) は先頭の「キ」を捕獲する。しかし,\g<1> の部分では,内側の ( ) は「ツ」を捕獲し,外側の ( ) は「ツツ」を捕獲することになる。部分式呼び出しが入れ子になっているので,どの階層(レベル)の ( ) かによって後方参照の値が違うのだ。いまの場合,単に \k<2> とか \2 としたのでは,最後に捕獲した「ツ」を参照してしまうことになる。

そこで,同じ階層の 2 番目の ( ) の後方参照という意味で \k<2+0> と書く。+0 の代わりに +1 とか -1 と書けば一つ深い階層,浅い階層になる。

なお,この回文の正規表現は実はもう少し簡素化できる。\g<0> でパターン全体の呼び出しになる。だから

(.)(?:\g<0>|.?)\k<1+0>

これでーいいのだー。

※部分式呼び出しの項を書くにあたって,「正規表現」に無限のパワーを与える"田中哲スペシャル" を全面的に参考にさせていただきました。回文の正規表現もここに掲載されていたものを元にしています。
※なお,同サイトには,田中哲スペシャルについて「よい子の皆さんは使わないで下さいね」とあるのですが,プログラマーがよい子なんかになってもしょうがないので大いに使いましょう。

Ruby の Onigmo との違い

Rubyist の方は \x{3400} のような記述を見て「ん? 文字コード指定って \u{3400} の形じゃないの?」と思ったかもしれない。

そう,よくは分からないけど,Ruby の正規表現は Onigmo の仕様(doc/RE.ja)に書いてあるのとわずかに違うところがある。EmEditor は Onigmo の仕様書どおりだ。

\x{ } 以外に違いがあるかどうかは知らない。

オプション

Onigmo には動作オプションがある。

代表的なものは ONIG_OPTION_ASCII_RANGE で,これが ON だと \w\d\s などが ASCII 限定になる。つまり,ON なら \d[0-9] と同じだ。OFF なら全角の「123」やアラビア文字の「١٢٣」やラオ文字の「໑໒໓」にもマッチする。

EmEditor は Ruby と同じく ONIG_OPTION_ASCII_RANGE が ON になっている。

EmEditor も Ruby もこのオプションを変更することはできない。
ただ,正規表現中で動作モードを変えることはできる。たとえば

\s

は全角空白にマッチしないが,これを (?u: ) で囲んだ

(?u:\s)

は全角空白にもマッチする。
囲んだところだけ動作が変えられるのだ。
あるいは

(?u)\s

のように書いてもいい。(?u) 以降の動作が変わる。

詳しくは doc/RE.ja の「7. 拡張式集合」を参照されたい。

ひとこと

Onigmo を積んだ EmEditor は金棒に鬼