正規表現のデバッグについてNo.38046
fzok4234 さん 20/03/12 15:46
 
連投につき大変失礼いたします。

さて、最近検索/置換や強調表示などで、思わぬ文字列にマッチしてしまったり、少
ないパターン文字列の指定にもかかわらず激重になったりすることがよくあります。

まず、検索結果や強調表示などがおかしいときにマッチした文字列の特定の部分がパ
ターン文字列のどの部分と対応しているかがすぐに分からず困ることがあります。こ
のためにマクロや.hilightファイルのデバッグに手間取ってしまいます。

Web上には
https://www.debuggex.com/
等の正規表現のデバッグや解析を行うサイトがありますが、どれもPerlなどのメジ
ャーな処理系に限定しているため、HmJre.dll独自のパターンの解析にはほとんど役
に立ちません。

また、強調表示など動作が重くなるのを防止するために、前もって正規表現の実行速
度を計測したいのですが、適切な方法が分からずに困っています。

今までは、検索や強調表示の対象文字列を数千行書いたテストファイルをそのつど作
成し、実際に検索や強調表示を行って終了までの時間を計っていたのですが、この方
法だと一々テストファイルを作成する手間に加えて、動作の軽い正規表現では一瞬で
処理が終わって計測できなかったり、逆に動作の重い正規表現ではいつまでたっても
処理が終わらず秀丸エディタをタスクマネージャから強制終了させたりと問題があり
ます。

そこで、HmJre.dll専用の正規表現の解析手段があれば大変助かるのですが、現在の
秀丸エディタでマクロなどを駆使して手軽に解析することは可能でしょうか。やりた
いこととしては以下のような感じです。


1. マッチした対象文字列の一部分からそれに対応しているパターンの一部分を抜き
出す。例えば、正規表現が

[a-z]+[0-9]+

で検索対象が

123abc456def789ghi

のとき、マッチした対象を

abc456
def789

と列挙し、さらに正規表現のどの部分がマッチ文字列のどの部分と対応しているかを

[a-z]+ => abc
[0-9]+ => 456
[a-z]+ => def
[0-9]+ => 789

という感じで列挙する。


2. 正規表現の1回の実行速度をナノ秒単位で計測する。検索、置換、強調表示といっ
た実行速度の異なる各シチュエーションを選択して計測を行う。正規表現の実行その
ものは完全にバックグラウンドで行い、実行に時間がかかっても秀丸エディタを強制
終了させることなく計測だけを終了できる。


これらをいざ自分でマクロを組んでやろうとするとかなり大掛かりな開発になりそう
です。もしすでにどなたかこのようなマクロを開発されいればご紹介よろしくお願い
します。



[ ]
RE:38046 正規表現のデバッグについてNo.38047
秀まるお2 さん 20/03/12 16:55
 
 テスト方法については僕的にはいいアイデアは無いです。すみません。普通に検索
を繰り返してテストしてもらうしか無さそうな気がします。

 速度については、一応、1行の長さが極端に長くない限りはそんな極端に遅くはな
らないはずだと思います。1行の長さが極端に長くなるとたしかに遅い場合はありま
すが、あまりにも時間がかかる場合は、実はHmJre.dllの中で時間測定をしていて、
一定時間を超えた場合は検索を中止するようにしています。

 一応、30秒固まってて、しかもマウスクリックがなされてれば検索を中断するよ
うにしています。

 極端に遅くならないようにするためには、とりあえず「*」や「+」の繰り返しをあ
んまり多く使わないようにしてもらうのがいいです。さらには一番遅くなりそうなの
は、その繰り返しパターンの入れ子のパターンなので、そういうのを使わないように
正規表現パターンを工夫してもらえれば大丈夫じゃないかと思います。また、この辺
の基本的な繰り返しパターンの指定についてはPerlも秀丸も同じなので、デバッグサ
イトでのテストもおおよそ可能だと思います。

[ ]
RE:38046 正規表現のデバッグについてNo.38048
でるもんたいいじま さん 20/03/12 18:10
 
でるもんた・いいじまです。

> Web上には
> https://www.debuggex.com/
> 等の正規表現のデバッグや解析を行うサイトがありますが、
> どれもPerlなどのメジャーな処理系に限定しているため、
> HmJre.dll独自のパターンの解析にはほとんど役に立ちません。

> そこで、HmJre.dll専用の正規表現の解析手段があれば大変助かるのですが、
> 現在の秀丸エディタでマクロなどを駆使して手軽に解析することは可能でしょうか。

まず最初に裏技からお示ししておくと、HmJre.dllはただのDLLですから、他の言語か
らも呼び出せます。関数の仕様は秀丸のフォルダにあるHmJre.chmをごらんください。

ただし、32bit版秀丸エディタからの関数の呼び出し規約はcdecl、一方でWin32APIは
stdcall規約ですから、VBAなどの一般的な言語でWin32API呼び出し用に用意されてい
る方法は使えません。C/C++言語なら実装可能ですが、下準備が少々面倒臭そうです。

一方で、64bit版ならWin64API自体が秀丸からのDLL呼び出しと同じcdecl規約になり
ましたので、普通の言語から普通に呼び出せます。ExcelのVBAで次のように記述して、
ワークシートから参照するのが簡単だと思います。

'32bit版と混同しないようファイル名に"64"をつけて、自前のフォルダにコピーして
使用
Private Declare PtrSafe Function FindRegular Lib "hmjre64.dll" _
    (ByVal regExp As String, ByVal targetString As String, _
        ByVal startPos As LongLong) As LongLong

Public Function RegExMatch(target As String, re As String) As LongLong
    RegExMatch = FindRegular(re, target, 0)
End Function

A1  B1  C1  
aaaa a+  =RegExMatch(A1,B1)

ちなみに、C1に =FindRegular(B1,A1,0) と書いたら、-2が返ってきました。もしか
して、こういうDLL呼び出しをするとbyvalなりLongLongなりの宣言が無視されるので
しょうか…。

VBAではなくC#から関数を呼ぶ場合は、

class HmJre {
[DllImport("hmjre64.dll", CharSet=CharSet.Ansi)]
extern static long FindRegular(string regExp, string target, long startPos);
...

};

のようにクラスを定義すればよさそうです。

☆ ☆ ☆

> また、強調表示など動作が重くなるのを防止するために、
> 前もって正規表現の実行速度を計測したいのですが、
> 適切な方法が分からずに困っています。

これもVBAやC#から呼び出すのがいいと思います。1回だけ計測してナノ秒単位の値を
得ても各種オーバーヘッドで山ほど誤差が出ますので、正規表現のコンパイルとマッ
チングを100万〜1億回くらい繰り返して、その途中で所定の時間(たとえば10秒)が
経過したらそこで打ち切り、とすると計測精度が上がります。

☆ ☆ ☆

> 1. マッチした対象文字列の一部分からそれに対応しているパターンの
> 一部分を抜き出す。例えば、正規表現が
> [a-z]+[0-9]+
> で検索対象が
> 123abc456def789ghi
> のとき、マッチした対象を
> abc456
> def789
> と列挙し、

ここまではHmJre.dllの GetLastMatch****() 関数で簡単に実装できます。

さらに正規表現のどの部分がマッチ文字列のどの部分と対応しているかを
> [a-z]+ => abc
> [0-9]+ => 456
> [a-z]+ => def
> [0-9]+ => 789
> という感じで列挙する。

これには「長い正規表現を部分部分に分解する作業」そのものが必要になります。す
なわち、自力でゼロから正規表現エンジンを作れる技能が必要になります。

なので、どうしてもデバッガなしではデバッグができないのであれば、「HmJre.dll
独自のパターン」を一切使わず、メジャーな処理系と共通の文法だけで正規表現を書
くしかないでしょう。

☆ ☆ ☆

というかそもそも、正規表現にしてもプログラミング全般にしても、普通ならまず小
さなもので動作確認をして、徐々に大きくしながら動作テストとデバッグログ出力を
繰り返していき、最終的にパッケージにするのがスジです。
パーツテストなしでいきなり巨大なものを一気に組み上げて、その状態からぶっつけ
本番でデバッガに投入、という手法は一般論として、個別のパーツ作りすらおぼつか
ないレベルの人が採用していい開発手順ではありません。

[ ]
RE:38047 正規表現のデバッグについてNo.38051
fzok4234 さん 20/03/12 21:14
 

> 極端に遅くならないようにするためには、とりあえず「*」や「+」の繰り返しを
>あんまり多く使わないようにしてもらうのがいいです。さらには一番遅くなりそう
>なのは、その繰り返しパターンの入れ子のパターンなので、そういうのを使わない
>ように正規表現パターンを工夫してもらえれば大丈夫じゃないかと思います。

回答ありがとうございます。

確かに、今まで数百文字の複雑怪奇な正規表現の動作が重かったのでマッチの正確さ
を妥協して数十行の短い一見してシンプルな正規表現に変えたら、
かえって激重になってしまったという不思議な現象に遭遇していました。

今後はなるべく*や+の入れ子が深くなっていないか注意してみようと思います。


[ ]
RE:38048 正規表現のデバッグについてNo.38052
fzok4234 さん 20/03/12 21:20
 
回答ありがとうございます。

HmJre.dllって実は.Net環境からP/Invokeできたのですね。C#ならTaskオブジェクト
とasync/awaitでバックグラウンドでテストしていつでも中断できるアプリを簡単に
自作できそうです。


[ ]
RE:38051 正規表現のデバッグについてNo.38054
でるもんたいいじま さん 20/03/13 08:58
 
でるもんた・いいじまです。

秀まるおさん:
> 極端に遅くならないようにするためには、とりあえず「*」や「+」の
> 繰り返しをあんまり多く使わないようにしてもらうのがいいです。
> さらには一番遅くなりそうなのは、その繰り返しパターンの入れ子の
> パターンなので、そういうのを使わないように正規表現パターンを
> 工夫してもらえれば大丈夫じゃないかと思います。

fzok4234さん:
> 確かに、今まで数百文字の複雑怪奇な正規表現の動作が重かったので
> マッチの正確さを妥協して数十行の短い一見してシンプルな正規表現に変えたら、
> かえって激重になってしまったという不思議な現象に遭遇していました。

おそらくこれは「組み合わせ爆発」だと思います。

私自身も正規表現エンジンの現物のコードを読んだことがないのですが、*や+を二重
に入れ子にした場合には検索対象文字列の長さの2乗に比例して重くなるはずです。3
重の入れ子なら長さの3乗、4重の入れ子なら長さの4乗のはずです。一般論として3乗
のアルゴリズムは極力避けるべきだというのは、プログラマなら当然ご存じのはずで
す。

それと、コードの重さを決めるのは、コードをテキストで表現したときの長さではあ
りません。極端な話、こんなコード

int a[256];
for (i=0 ; i<256 ; i++ ) a[i} = f(i);



a[0] = f(0);
a[1] = f(1);

a[255] = f(255);

と展開して、さらにf()もインライン展開してしまえば何倍も速くなるはずです。

むしろ、コードをグラフ構造に落としたときの経路の深さが実行速度には決定的に影
響するはずです。fzok4234さんがやってしまったことはまさに、展開してあったはず
のコードをforで縮約してしまったことと同等のはずです。

[ ]