正規表現の部分式を宣言しただけでsearchdNo.09735
fzok4234 さん 22/02/06 20:15
 
毎度お世話になっております。


さて、マクロの searchdown 系文や finddown 系文で正規表現を使って検索を行うと
き、その正規表現の
パターン中に、例えば

 (?<heavyPattern>aaaaaaaa){0}

とか、hmonig.dll のアトミックグループを使った

 (?>(?:)|(?:a\A(?<heavyPattern>aaaaaaaa)))

というような、専ら長さ 0 文字のアンカーとして機能して式中の (?<heavyPattern>
aaaaaaaa) の部分が
絶対に実行されないようにした式を入れたとき、その aaaaaaaa の部分の式が動作の
重い内容だと全体の
検索速度がなぜか重くなってしまいます。

このため、hmonig.dll や最近の HmJre.dll に備わっている部分式呼び出し機能で呼
び出して使用するための
式を、前もって名前を付けて定義だけを行う「宣言」行為に支障をきたしております。


この「式の宣言」の具体的手順の例は次のようなものです。まず、正規表現 DLL をH
mJreSelect.dll にして
.HmJreSelect ファイルの内容を以下のようにします。

 [DllSelector]
 (?#hmonig)=hmonig.dll
 
 [ReplaceRegExp]
 (?#a)=(?>(?:)|(?:a\A(?<a>\d++)))
 (?#b)=(?>(?:)|(?:a\A(?<b>\((?<bItem>(?>\g<a>|\g<b>))(?:,\g<bItem>)*+\))))
 (?#c)=(?>(?:)|(?:a\A(?<c>(?<cItem>(?>\g<a>|\g<b>))(?:[\+\-\*/]\g<c>)*+)))

その上で次のようなマクロ

 setcompatiblemode 0x00000200 ;
 searchdown2 @"(?#hmonig)(?u)(?#a)(?#b)(?#c)\g<c>" , casesense , regular ;
 endmacro ;

を実行して、開いているファイル上の

 1+2+(3)*(4,5)-(6,(7,(8,9),((10,11),12)))/13

という文字列を選択させる、といった使用法です。ここで、定義した (?#a) 、(?#b)
 、(?#c) は後から
別のマクロや検索ダイアログ、強調表示などで再利用するため、.HmJreSelect ファ
イルに残しておく
必要があります。


問題なのは、上記の例のように名前を付けての「定義」の段階では実行されないはず
の (?<a> ... ) や
(?<b> ... ) といった部分に、入れ子にした * とか部分式呼び出しの再帰とかの動
作の重い式を使用した場合、
たとえマクロで

 searchdown2 @"(?#hmonig)(?u)(?#a)(?#b)(?#c)foobar" , casesense , regular ;

として \g<a> 、\g<b> 、\g<c> を全く呼び出さなくても検索の動作が重くなってし
まうことです。式定義の

 (?>(?:)|(?:a\A(?<a> ... )))

は、論理和記号 | の左辺がマッチしたら右辺の式は評価されない「アトミックグ
ループ」のはずです。つまり、
左辺の (?:) は長さ 0 の空文字列にマッチするため開いているファイル上のカーソ
ルがどの位置にあっても
必ず左辺にマッチして、右辺の式は決して実行されないはずです。しかも、念には念
を入れて右辺の先頭には
a\A すなわち有効な 1 文字の右にファイル先頭のアンカーという絶対にマッチしな
いパターンまで入れています。

そのため、部分式呼び出し用の式宣言の (?<a> ... ) の部分の「実行」時の速度が
どんなに遅くても、実際に
\g<a> を呼び出しさえしなければ理論上は速度に影響を及ぼさないはずです。


しかし、実際には式宣言の (?<a> ... ) の内容次第では、空のファイルを開いた状態で

 searchdown2 @"(?#hmonig)(?u)(?#a)(?:)" , casesense , regular ;

としたり、予め

 setsearch @"(?#hmonig)(?u)(?#a)(?:)" , 0x00000002 | 0x00000010 ;

で設定の上で

 finddown2 ;

とした場合、1 回の動作に 200 [ms] 以上かかることもあります。


この問題のボトルネックがどこにあるのかを当方で掴むことができず、正しい「部分
式宣言」の仕方が分からず
大変困っています。どうか解決策のご教授よろしくお願い申し上げます。



[ ]
RE:09735 正規表現の部分式を宣言しただけNo.09736
秀丸担当 さん 22/02/07 10:00
 
同じ問題かわからないですが、似たような状況を再現できました。
単にゼロ文字にヒットするように、
(?#hmonig)()
で検索してから、下候補コマンドの連続で遅い場合がありました。
HmJreで、
()
の検索の場合は遅くはないようでした。

下候補を実行するだけで、画面内に見えている行の検索文字列の強調を更新する動作
があり、一行が長い行が見えていると、(?#hmonig)()で遅かったです。
下候補で検索文字列の強調を更新するのは無駄がありました。
検索条件に変化が無い場合は無駄に更新しないように改善していきます。
とりあえず検索ダイアログから検索する場合は、検索文字列の強調をOFFにすると回
避できると思います。
searchdownとfinddown2の場合はそのままでは影響ないはずですが、searchdownのhil
ightのオプションやhilightfound文があると影響あるかもしれません。

検索すること自体の、(?#hmonig)()と()で違いがあるのは、そういうhmonigの特性な
のだと思います。

新規作成状態で、(?>(?:)|(?:a\A(?<a> ... )))の右側が複雑な場合に遅いというの
は再現できませんでした。
そういうことが起きるとしたら、これもhmonigの特性という気がしますが、再現でき
ずわからないです。

[ ]
RE:09736 正規表現の部分式を宣言しただけNo.09737
fzok4234 さん 22/02/07 16:02
 
一応、当方で次のような極端な例では再現できています。繰り返しの *+ の入れ子や
部分式呼び出しの
再帰を多用したものですが、finddown2 と searchdown2 との両方で、1 回の実行に
なんと 2 秒も
かかってしまっています。


再現方法は、まず必ず浮動小数点数版の秀丸エディタを用意の上で、HmJreSelect.dl
l と hmonig.dll を
インストールして、「動作環境」の「環境」で正規表現 DLL を HmJreSelect.dll に
しておきます。

次に、以下に掲載の .HmJreSelect ファイルを HmJreSelect.dll と同じディレクト
リに置きます。ただし、
以下に掲載した内容は見やすくするために、[ReplaceRegExp] セクション以下の 9
行での正規表現式の
宣言では、式の途中に改行やインデントのスペースが挿入されています。実際に使用
する際にはこの改行文字と
スペースを削除する必要があります。

そして、「新規作成」コマンドなどで空のファイルを開いた状態で以下に掲載のマク
ロ Benchmark.mac を
実行します。2 回の input ボックスで SearchPattern と RunningSeconds の値を聞
いてきますが、そのまま
OK します。

約 6 分後に全ての結果報告がアウトプット枠に出力されます。


この結果を見ると、検索パターンが

 (?#hmonig)(?#maxlines:32)(?#fulllinematch)(?u)(?:)
 (?#hmonig)(?#maxlines:32)(?#fulllinematch)(?u)(?#cslexeme)(?:)
 (?#hmonig)(?#maxlines:32)(?#fulllinematch)(?u)(?#cslexeme)(?#cskeyword)(?:)
  ...
 (?#hmonig)(?#maxlines:32)(?#fulllinematch)(?u)(?#cslexeme)(?#cskeyword)(?#
csid)(?#csop)(?#cstypename)(?#csnumeric)(?#csstring)(?#csspecialtoken)(?:)

と変化する間は finddown2 と searchdown2 の実行時間は、ほぼパターンの文字数に
比例するように緩やかに
増加していき、

 (?#hmonig)(?#maxlines:32)(?#fulllinematch)(?u)(?#cslexeme)(?#cskeyword)(?#
csid)(?#csop)(?#cstypename)(?#csnumeric)(?#csstring)(?#csspecialtoken)(?:)

の時点では 1.2 [ms] 程度となっています。ところが、問題の *+ の入れ子や部分式
呼び出しの再帰を多用した
(?#csstatementsymbol) を追加して

 (?#hmonig)(?#maxlines:32)(?#fulllinematch)(?u)(?#cslexeme)(?#cskeyword)(?#
csid)(?#csop)(?#cstypename)(?#csnumeric)(?#csstring)(?#csspecialtoken)(?#css
tatementsymbol)(?:)

になったとたん、実行時間が 2 秒と前回の 2000 倍弱まで跳ね上がってしまいました。


なお、開いているファイルに適当な文字列を入力した状態で、SearchPattern の inp
ut ボックスで
\g<statementSymbol> とかの入力文字列にマッチする定義済みの部分式を指定しても、
結果はほとんど
変わりませんでした。

おそらく、finddown2 や searchdown2 の内部で最初に呼び出される Jre2Compile()
での正規表現式の
初期化で時間がとられているような感じがします。


実行環境 :

 ・CPU : Intel Core i7-8700K 3.7Ghz 6 コア 12 スレッド
 ・RAM : 32GB
 ・OS : Windows 10 Pro 1909 x64
 ・アンチウイルス : カスペルスキー インターネット セキュリティ
 ・秀丸エディタ : 9.12β6 Float x64
 ・HmJre.dll : 5.28
 ・hmonig.dll : 0.8.7.0
 ・HmJreSelect.dll : 0.0.1.0

.HmJreSelect ファイル :
----------------------------------------------------------------------------
--------------------
[DllSelector]
(?#hmonig)=hmonig.dll

[ReplaceRegExp]
(?#cslexeme)=(?>(?:)|(?:@\A(?>
        (?<newLineChar>[\x{000A}\x{000D}])
    |   (?<lineBodyChar>[^\x{000A}\x{000D}])
    |   (?<char>(?>\g<newLineChar>|\g<lineBodyChar>))
    |   (?<spaceChar>[\p{Zs}\t\v\f])
    |   (?<wordHeadChar>[_\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}])
    |   (?<wordChar>(?>\g<wordHeadChar>|[\p{Mn}\p{Mc}\p{Pc}\p{Nd}\p{Cf}]))
    |   (?<nonWordChar>[!"\$%&'\(\)=\^~\|@\[\{;\+:\*\]\},<\.>/\?\-])
    |   (?<noPreLexemeChar>(?>(?:(?<=[/])[/\*])|[#]))
    |   (?<noPostLexemeChar>(?>(?:[\*](?=[/]))))
    |   (?<newLine>(?>(?:\x{000D}\x{000A})|\g<newLineChar>))
    |   (?<space>(?<!\g<spaceChar>)(?:\g<spaceChar>++))
    |   (?<spaceOrEmpty>(?<!\g<spaceChar>)(?:\g<spaceChar>*+))
    |   (?<lineCommentBegin>(?<![/\*])//)
    |   (?<lineComment>\g<lineCommentBegin>\g<lineBodyChar>*+(?>\g<newLine>|
\z))
    |   (?<blockCommentBegin>(?<![/])/\*)
    |   (?<blockCommentEnd>\*/)
    |   (?<blockComment>\g<blockCommentBegin>(?>(?:(?:)(?![\*])\g<char>)|(?:
[\*](?![/])))*+\g<blockCommentEnd>)
    |   (?<directive>(?<=[\x{000A}\x{000D}]|\A)\g<spaceOrEmpty>#\g<lineBodyC
har>++(?>\g<newLine>|\z))
    |   (?<separaterLexeme>(?>\g<lineComment>|\g<blockComment>|\g<directive>
|\g<space>|\g<newLine>))
    |   (?<separater>(?<!\g<noPreLexemeChar>)(?:\g<separaterLexeme>++)(?!\g<
noPostLexemeChar>))
    |   (?<separaterOrEmpty>(?<!\g<noPreLexemeChar>)(?:\g<separaterLexeme>*
+)(?!\g<noPostLexemeChar>))
    |   (?<separaterBegin>(?<!\g<noPreLexemeChar>)(?>\g<newLineChar>|\g<spac
eChar>|\g<lineCommentBegin>|\g<blockCommentBegin>|\z))
    |   (?<separaterEnd>(?>\g<newLineChar>|\g<spaceChar>|\g<blockCommentEnd>
|\A)(?!\g<noPostLexemeChar>))
    |   (?<wordEscape>(?>(?:\\u\h{4})|(?:\\U\h{8})))
    |   (?<wordHeadClitic>(?>\g<wordHeadChar>|\g<wordEscape>))
    |   (?<wordClitic>(?>\g<wordChar>|\g<wordEscape>))
    |   (?<word>(?<!\g<noPreLexemeChar>)(?:\g<wordHeadClitic>\g<wordClitic>*
+)(?!\g<noPostLexemeChar>))
    |   (?<nonWordClitic>(?>(?:(?:)(?![/\*])\g<nonWordChar>)|(?:(?<![/\*])/
(?![/\*]))|(?:(?<![/])\*(?![/]))))
    |   (?<nonWord>(?<!\g<noPreLexemeChar>)(?:\g<nonWordClitic>++)(?!\g<noPo
stLexemeChar>))
    |   (?<preWordClitic>(?>\g<nonWordClitic>|\g<separaterEnd>))
    |   (?<postWordClitic>(?>\g<nonWordClitic>|\g<separaterBegin>))
    |   (?<preNonWordClitic>(?>\g<wordClitic>|\g<preWordClitic>))
    |   (?<postNonWordClitic>(?>\g<wordHeadClitic>|\d|\g<postWordClitic>))
    |   (?<postSeparater>(?>\g<wordHeadClitic>|\d|\g<nonWordClitic>|\z))
)))

(?#cskeyword)=(?>(?:)|(?:@\A(?<keyword>(?>
        yield
    |   with
    |   while
    |   where
    |   when
    |   volatile
    |   virtual
    |   value
    |   using
    |   unsafe
    |   unchecked
    |   typeof
    |   try
    |   true
    |   throw
    |   this
    |   switch
    |   struct
    |   static
    |   stackalloc
    |   sizeof
    |   set
    |   select
    |   sealed
    |   return
    |   remove
    |   ref
    |   record
    |   readonly
    |   public
    |   protected
    |   private
    |   partial
    |   params
    |   override
    |   out
    |   orderby
    |   or
    |   operator
    |   on
    |   null
    |   not
    |   new
    |   namespace
    |   nameof
    |   lock
    |   let
    |   join
    |   is
    |   into
    |   internal
    |   interface
    |   init
    |   in
    |   implicit
    |   if
    |   group
    |   goto
    |   global
    |   get
    |   from
    |   foreach
    |   for
    |   fixed
    |   finally
    |   false
    |   extern
    |   explicit
    |   event
    |   equals
    |   enum
    |   else
    |   do
    |   descending
    |   delegate
    |   default
    |   continue
    |   const
    |   class
    |   checked
    |   catch
    |   case
    |   by
    |   break
    |   base
    |   await
    |   async
    |   ascending
    |   as
    |   and
    |   alias
    |   add
    |   abstract
    |   __refvalue
    |   __reftype
    |   __makeref
    |   __arglist
    |   _
))))

(?#csid)=(?>(?:)|(?:@\A(?<id>(?>(?:(?<!\g<noPreLexemeChar>)@)|(?:(?:)(?!\g<k
eyword>\g<postWordClitic>)))\g<word>)))

(?#csop)=(?>(?:)|(?:@\A(?>
        (?<incrementOp>(?>\+\+|--))
    |   (?<plusMinusBaseOp>(?>
                (?:(?<![\+])\+(?![\+]))
            |   (?:(?<![\-])-(?![\->]))
        ))
    |   (?<plusMinusAssignOp>\g<plusMinusBaseOp>=)
    |   (?<plusMinusOp>\g<plusMinusBaseOp>(?!=))
    |   (?<asteriskBaseOp>(?<![/])\*(?![/]))
    |   (?<asteriskAssignOp>\g<asteriskBaseOp>=)
    |   (?<asteriskOp>\g<asteriskBaseOp>(?!=))
    |   (?<divisionBaseOp>(?>
                (?:(?<![/\*])/(?![/\*]))
            |   %
        ))
    |   (?<divisionAssignOp>\g<divisionBaseOp>=)
    |   (?<divisionOp>\g<divisionBaseOp>(?!=))
    |   (?<booleanBinaryOp>(?>&&|(?:\|\|)))
    |   (?<andBaseOp>(?<![&])&(?![&]))
    |   (?<andAssignOp>\g<andBaseOp>=)
    |   (?<andOp>\g<andBaseOp>(?!=))
    |   (?<pipeBaseOp>(?<![\|])\|(?![\|]))
    |   (?<pipeAssignOp>\g<pipeBaseOp>=)
    |   (?<pipeOp>\g<pipeBaseOp>(?!=))
    |   (?<hatBaseOp>\^)
    |   (?<hatAssignOp>\g<hatBaseOp>=)
    |   (?<hatOp>\g<hatBaseOp>(?!=))
    |   (?<shiftBaseOp>(?><<|>>))
    |   (?<shiftAssignOp>\g<shiftBaseOp>=)
    |   (?<shiftOp>\g<shiftBaseOp>(?!=))
    |   (?<relationalOp>(?>
                (?<equalOp>[=!]=)
            |   (?<lessGreaterEqualOp>[<>]=)
            |   (?<lessOp>(?<![<])<(?![<=]))
            |   (?<greaterOp>(?<![>=\-])>(?![>=]))
        ))
    |   (?<lambdaOp>=>)
    |   (?<bangOp>[!](?!=))
    |   (?<tildeOp>~)
    |   (?<rangeOp>\.\.)
    |   (?<nullCoalescingBaseOp>\?\?)
    |   (?<nullCoalescingAssignOp>\g<nullCoalescingBaseOp>=)
    |   (?<nullCoalescingOp>\g<nullCoalescingBaseOp>(?!=))
    |   (?<memberRefOp>(?>->|\?\.))
    |   (?<dotOp>(?<![\.\?])\.(?![\.]))
    |   (?<nullConditionalIndexerBeginOp>\?\[)
    |   (?<indexerBeginOp>(?<![\?])\[)
    |   (?<indexerEndOp>\])
    |   (?<invokeBeginOp>\()
    |   (?<invokeEndOp>\))
    |   (?<genericInvokeBeginOp>(?<![<])<(?![<=]))
    |   (?<genericInvokeEndOp>(?<![=\-])>(?![>=]))
    |   (?<blockBeginOp>\{)
    |   (?<blockEndOp>\})
    |   (?<semicolonOp>;)
    |   (?<commaOp>,)
    |   (?<ifOp>(?<![\?])\?(?![\?=\.\[]))
    |   (?<elseOp>(?<![:]):(?![:]))
    |   (?<assignOp>(?<![\+\-\*/%&\|\^=!<>]|<<|>>|\?\?)=(?![=>]))
)))

(?#cstypename)=(?>(?:)|(?:@\A(?<typeName>(?<!\g<noPreLexemeChar>)(?:
    (?:
            (?:
                (?<normalTypeNameSegment>
                    (?>(?:global(?=\g<postWordClitic>))|(?:\g<id>(?=\g<postW
ordClitic>)))
                    (?:\g<separaterOrEmpty>\g<genericInvokeBeginOp>(?:
                            (?:(?:\g<separaterOrEmpty>\g<commaOp>)*+)
                        |   (?:\g<separaterOrEmpty>\g<typeName>(?:\g<separat
erOrEmpty>\g<commaOp>\g<separaterOrEmpty>\g<typeName>)*+)
                    )\g<separaterOrEmpty>\g<genericInvokeEndOp>)?+
                )
                (?:\g<separaterOrEmpty>(?>\g<dotOp>|(?:::\g<separaterOrEmpty
>))\g<normalTypeNameSegment>)*
            )
        |   (?:\(
                \g<separaterOrEmpty>
                (?<tupleTypeNameSegment>\g<typeName>\g<separater>\g<id>)
                (?:\g<separaterOrEmpty>\g<commaOp>\g<separaterOrEmpty>\g<tup
leTypeNameSegment>)++
            \g<separaterOrEmpty>\))
    )
    (?:\g<separaterOrEmpty>(?>
            \g<asteriskOp>
        |   \?
        |   (?:\[(?:\g<separaterOrEmpty>\g<commaOp>)*+\g<separaterOrEmpty>
\])))*+
)(?!\g<noPostLexemeChar>))))

(?#csnumeric)=(?>(?:)|(?:@\A(?<numeric>(?<!\g<noPreLexemeChar>)(?>
        (?:
            (?>
                    (?:0[Xx](?>\h|(?<numericDelim>(?<!_)_(?!_)))++(?:(?<=\h)
(?:)))
                |   (?:0[Bb](?>[01]|\g<numericDelim>)++(?:(?<=[01])(?:)))
                |   (?:
                        (?<decimal>(?:(?:)(?=\d))(?>\d|\g<numericDelim>)++
(?:(?<=\d)(?:)))
                        (?!(?>[Xx]|[Bb]|(?:\.\d)|[Ee]|(?<floatSuffix>(?>[Ff]
|[Dd]|[Mm]))))
                    )
            )
            (?>(?:[Uu][Ll])|(?:[Ll][Uu])|[Uu]|[Ll])?+
        )
    |   (?>
                (?:\g<decimal>(?>(?:(?<expSuffix>[Ee][\+\-]?+\g<decimal>)\g<
floatSuffix>?+)|\g<floatSuffix>))
            |   (?:\g<decimal>?+\g<dotOp>\g<decimal>\g<expSuffix>?+\g<floatS
uffix>?+)
        )
)(?!\g<noPostLexemeChar>))))

(?#csstring)=(?>(?:)|(?:@\A(?>
        (?<escape>\\(?>[\\0abfnrtv]|(?:u\h{4})|(?:U\h{8})|(?:x\h{1,}++)))
    |   (?<commonInterpolationEscape>(?>\{\{|\}\}))
    |   (?<commonInterpolationArgBegin>(?<!(?<!\{)\{)\{(?!\{))
    |   (?<commonInterpolationArgEnd>(?<!(?<!\})\})\}(?!\}(?!\})))
    |   (?<verbatimEscape>"")
    |   (?<stringChar>(?:)(?![\\"])\g<lineBodyChar>)
    |   (?<stringEscape>(?>\g<escape>|\\"))
    |   (?<stringBody>(?>\g<stringChar>|\g<stringEscape>)*+)
    |   (?<stringBegin>(?<!\g<noPreLexemeChar>)(?:(?<![@\$])"))
    |   (?<stringEnd>(?:"(?!"))(?!\g<noPostLexemeChar>))
    |   (?<string>\g<stringBegin>\g<stringBody>\g<stringEnd>)
    |   (?<interpolationChar>(?:)(?![\{\}])\g<stringChar>)
    |   (?<interpolationEscape>(?>\g<stringEscape>|\g<commonInterpolationEsc
ape>))
    |   (?<interpolationClitic>(?>\g<interpolationChar>|\g<interpolationEsca
pe>))
    |   (?<interpolationFormat>\g<interpolationClitic>++(?=\g<commonInterpol
ationArgEnd>))
    |   (?<interpolationBegin>(?<!\g<noPreLexemeChar>)(?:(?<![@])\$"))
    |   (?<interpolationEnd>\g<stringEnd>)
    |   (?<interpolationBody>\g<interpolationClitic>*+(?=(?>\g<commonInterpo
lationArgBegin>|\g<interpolationEnd>)))
    |   (?<verbatimStringChar>(?:)(?!["])\g<char>)
    |   (?<verbatimStringEscape>\g<verbatimEscape>)
    |   (?<verbatimStringBody>(?>\g<verbatimStringChar>|\g<verbatimStringEsc
ape>)*+)
    |   (?<verbatimStringBegin>(?<!\g<noPreLexemeChar>)(?:(?<![\$])@"))
    |   (?<verbatimStringEnd>\g<stringEnd>)
    |   (?<verbatimString>\g<verbatimStringBegin>\g<verbatimStringBody>\g<ve
rbatimStringEnd>)
    |   (?<verbatimInterpolationChar>(?:)(?![\{\}])\g<verbatimStringChar>)
    |   (?<verbatimInterpolationEscape>(?>\g<verbatimStringEscape>|\g<common
InterpolationEscape>))
    |   (?<verbatimInterpolationClitic>(?>\g<verbatimInterpolationChar>|\g<v
erbatimInterpolationEscape>))
    |   (?<verbatimInterpolationFormat>\g<verbatimInterpolationClitic>++(?=\
g<commonInterpolationArgEnd>))
    |   (?<verbatimInterpolationBegin>(?<!\g<noPreLexemeChar>)(?:(?>\$@|@\$)"))
    |   (?<verbatimInterpolationEnd>\g<stringEnd>)
    |   (?<verbatimInterpolationBody>\g<verbatimInterpolationClitic>*+(?=(?>
\g<commonInterpolationArgBegin>|\g<verbatimInterpolationEnd>)))
    |   (?<charLiteral>(?<!\g<noPreLexemeChar>)(?:'(?>(?:(?:)(?![\\'])\g<lin
eBodyChar>)|(?>\g<escape>|\\'))')(?!\g<noPostLexemeChar>))
)))

(?#csspecialtoken)=(?>(?:)|(?:@\A(?>
        (?<modifier>(?>
                public
            |   protected
            |   private
            |   internal
            |   new
            |   abstract
            |   virtual
            |   override
            |   sealed
            |   static
            |   const
            |   readonly
            |   using
            |   volatile
            |   async
            |   await
            |   fixed
            |   unsafe
            |   extern
            |   partial
            |   this
            |   params
            |   in
            |   out
            |   ref
        ))
    |   (?<modifiers>(?<!\g<noPreLexemeChar>)(?:\g<modifier>(?:\g<separater>
\g<modifier>)*+(?=\g<postWordClitic>))(?!\g<noPostLexemeChar>))
    |   (?<attributeModifier>(?>
                assembly
            |   module
            |   type
            |   field
            |   method
            |   event
            |   property
            |   param
            |   return
        ))
    |   (?<whereType>(?>
                (?:(?<!\g<noPreLexemeChar>)(?>
                        (?:(?>
                                struct
                            |   class
                            |   unmanaged
                            |   notnull
                            |   default
                        )(?=\g<postWordClitic>))
                    |   (?:new\g<separaterOrEmpty>\g<invokeBeginOp>\g<separa
terOrEmpty>\g<invokeEndOp>(?=\g<postNonWordClitic>))
                )(?!\g<noPostLexemeChar>))
            |   (?:\g<typeName>(?=\g<postWordClitic>))
        ))
    |   (?<freeArrayInitializer>\g<indexerBeginOp>(?:\g<separaterOrEmpty>\g<
commaOp>)*+\g<separaterOrEmpty>\g<indexerEndOp>)
    |   (?<freeArrayInitializers>(?<!\g<noPreLexemeChar>)(?:
            \g<freeArrayInitializer>(?:\g<separaterOrEmpty>\g<freeArrayIniti
alizer>)*+
        (?=\g<postNonWordClitic>))(?!\g<noPostLexemeChar>))
)))

(?#csstatementsymbol)=(?>(?:)|(?:@\A(?<statementSymbol>(?<!\g<noPreLexemeCha
r>)(?>
        (?<emptyStatementSymbol>\g<semicolonOp>(?=\g<postNonWordClitic>))
    |   (?<blockStatementSymbol>\g<blockBeginOp>(?:\g<separaterOrEmpty>
            \g<statementSymbol>
        )*+\g<separaterOrEmpty>\g<blockEndOp>(?=\g<postNonWordClitic>))
    |   (?:
            (?<expressionSymbol>(?<!\g<noPreLexemeChar>)
                (?<expressionSegmentSymbol>(?>
                        (?:(?>
                                \g<incrementOp>
                            |   \g<plusMinusAssignOp>
                            |   \g<plusMinusOp>
                            |   \g<asteriskAssignOp>
                            |   \g<asteriskOp>
                            |   \g<divisionAssignOp>
                            |   \g<divisionOp>
                            |   \g<booleanBinaryOp>
                            |   \g<andAssignOp>
                            |   \g<andOp>
                            |   \g<pipeAssignOp>
                            |   \g<pipeOp>
                            |   \g<hatAssignOp>
                            |   \g<hatOp>
                            |   \g<shiftAssignOp>
                            |   \g<shiftOp>
                            |   \g<equalOp>
                            |   \g<lessGreaterEqualOp>
                            |   \g<bangOp>
                            |   \g<tildeOp>
                            |   \g<rangeOp>
                            |   \g<nullCoalescingAssignOp>
                            |   \g<nullCoalescingOp>
                            |   \g<memberRefOp>
                            |   \g<assignOp>
                        )(?=\g<postNonWordClitic>))
                    |   (?:(?>
                                with
                            |   where
                            |   when
                            |   value
                            |   unchecked
                            |   typeof
                            |   true
                            |   throw
                            |   switch
                            |   stackalloc
                            |   sizeof
                            |   select
                            |   or
                            |   on
                            |   null
                            |   not
                            |   nameof
                            |   let
                            |   join
                            |   is
                            |   into
                            |   group
                            |   from
                            |   false
                            |   equals
                            |   descending
                            |   default
                            |   checked
                            |   by
                            |   base
                            |   ascending
                            |   as
                            |   and
                            |   __refvalue
                            |   __reftype
                            |   __makeref
                            |   __arglist
                        )(?=\g<postWordClitic>))
                    |   (?:\g<typeName>(?=\g<postWordClitic>))
                    |   (?:\g<numeric>(?=\g<postWordClitic>))
                    |   (?:(?:\g<dotOp>(?!\d))(?=\g<postNonWordClitic>))
                    |   (?:(?>
                                \g<charLiteral>
                            |   \g<string>
                            |   \g<verbatimString>
                        )(?=\g<postNonWordClitic>))
                    |   (?:\g<interpolationBegin>(?:\g<interpolationBody>\g<
commonInterpolationArgBegin>\g<separaterOrEmpty>
                            (?<commonInterpolationArgSymbol>
                                \g<expressionSymbol>(?:\g<separaterOrEmpty>\
g<commaOp>\g<separaterOrEmpty>\g<expressionSymbol>)?+
                            )\g<separaterOrEmpty>(?:\g<elseOp>\g<separaterOr
Empty>\g<interpolationFormat>)?+
                        \g<commonInterpolationArgEnd>)*+\g<interpolationBody
>\g<interpolationEnd>(?=\g<postNonWordClitic>))
                    |   (?:\g<verbatimInterpolationBegin>(?:\g<verbatimInter
polationBody>\g<commonInterpolationArgBegin>\g<separaterOrEmpty>
                            \g<commonInterpolationArgSymbol>\g<separaterOrEm
pty>(?:\g<elseOp>\g<separaterOrEmpty>\g<verbatimInterpolationFormat>)?+
                        \g<commonInterpolationArgEnd>)*+\g<verbatimInterpola
tionBody>\g<verbatimInterpolationEnd>(?=\g<postNonWordClitic>))
                    |   (?:\g<ifOp>\g<separaterOrEmpty>\g<expressionSymbol>\
g<separaterOrEmpty>\g<elseOp>(?=\g<postNonWordClitic>))
                    |   (?:(?:orderby(?=\g<postWordClitic>))\g<separaterOrEm
pty>
                            \g<expressionSymbol>(?:\g<separaterOrEmpty>\g<co
mmaOp>\g<separaterOrEmpty>\g<expressionSymbol>)*+
                        \g<separaterOrEmpty>(?>from|select|group)(?=\g<postW
ordClitic>))
                    |   (?<propertyPatternSymbol>(?<!\g<noPreLexemeChar>)(?:
\g<blockBeginOp>(?:\g<separaterOrEmpty>
                            (?<propertyPatternSegmentSymbol>
                                \g<id>(?:\g<separaterOrEmpty>\g<dotOp>\g<sep
araterOrEmpty>\g<id>)*+
                                \g<separaterOrEmpty>\g<elseOp>\g<separaterOr
Empty>\g<expressionSymbol>
                            )\g<separaterOrEmpty>\g<commaOp>\g<separaterOrEm
pty>\g<propertyPatternSegmentSymbol>
                        )?+\g<separaterOrEmpty>\g<blockEndOp>(?=\g<postNonWo
rdClitic>))(?!\g<noPostLexemeChar>))
                    |   (?<initializerSymbol>(?<!\g<noPreLexemeChar>)(?:\g<b
lockBeginOp>
                            (?:\g<separaterOrEmpty>\g<expressionSymbol>\g<se
paraterOrEmpty>\g<commaOp>)*+
                            (?:\g<separaterOrEmpty>\g<expressionSymbol>)?+
                        \g<separaterOrEmpty>\g<blockEndOp>(?=\g<postNonWordC
litic>))(?!\g<noPostLexemeChar>))
                    |   (?<invokeSymbol>(?<!\g<noPreLexemeChar>)(?:\g<invoke
BeginOp>(?:\g<separaterOrEmpty>
                            (?<argBodySymbol>
                                (?<argSegmentSymbol>
                                    (?:\g<id>\g<separaterOrEmpty>\g<elseOp>\
g<separaterOrEmpty>)?+\g<expressionSymbol>
                                )(?:\g<separaterOrEmpty>\g<commaOp>\g<separa
terOrEmpty>\g<argSegmentSymbol>)*+
                            )
                        )?+\g<separaterOrEmpty>\g<invokeEndOp>(?=\g<postNonW
ordClitic>))(?!\g<noPostLexemeChar>))
                    |   (?<genericInvokeSymbol>(?<!\g<noPreLexemeChar>)(?:\g
<genericInvokeBeginOp>\g<separaterOrEmpty>
                            \g<typeName>(?:\g<separaterOrEmpty>\g<commaOp>\g
<separaterOrEmpty>\g<typeName>)*+
                        \g<separaterOrEmpty>\g<genericInvokeEndOp>(?=\g<post
NonWordClitic>))(?!\g<noPostLexemeChar>))
                    |   (?:(?:)(?!\g<genericInvokeSymbol>)\g<lessOp>(?=\g<po
stNonWordClitic>))
                    |   (?:\g<greaterOp>(?=\g<postNonWordClitic>))
                    |   \g<freeArrayInitializers>
                    |   (?:
                            (?<definitionHeaderSymbol>(?<!\g<noPreLexemeChar
>)(?>
                                    (?:
                                        (?<attributesSymbol>(?<!\g<noPreLexe
meChar>)(?:(?:)(?=\g<indexerBeginOp>\g<postNonWordClitic>)
                                            (?<attributeSymbol>\g<indexerBeg
inOp>
                                                (?:\g<separaterOrEmpty>\g<at
tributeModifier>\g<separaterOrEmpty>\g<elseOp>)?+
                                            \g<separaterOrEmpty>
                                                (?<attributeSegmentSymbol>
                                                    \g<typeName>(?:\g<separa
terOrEmpty>\g<invokeSymbol>)?+
                                                )(?:\g<separaterOrEmpty>\g<c
ommaOp>\g<separaterOrEmpty>\g<attributeSegmentSymbol>)*+
                                            \g<separaterOrEmpty>\g<indexerEn
dOp>)(?:\g<separaterOrEmpty>\g<attributeSymbol>)*+
                                        (?=\g<postNonWordClitic>))(?!\g<noPo
stLexemeChar>))(?:\g<separaterOrEmpty>\g<modifiers>)?+
                                    )
                                |   \g<modifiers>
                            )(?!\g<noPostLexemeChar>))\g<separaterOrEmpty>(?>
                                    (?<discardSymbol>_(?=\g<postWordClitic>))
                                |   (?<varExpressionSymbol>(?:\g<typeName>(?
=\g<postWordClitic>))\g<separaterOrEmpty>
                                        (?<varSymbol>(?>
                                                (?<varNameSymbol>(?>\g<disca
rdSymbol>|(?:\g<id>(?=\g<postWordClitic>))))
                                            |   (?:\g<invokeBeginOp>\g<separ
aterOrEmpty>
                                                    \g<varNameSymbol>(?:\g<s
eparaterOrEmpty>\g<commaOp>\g<separaterOrEmpty>\g<varNameSymbol>)++
                                                \g<separaterOrEmpty>\g<invok
eEndOp>(?=\g<postNonWordClitic>))
                                        )(?:\g<separaterOrEmpty>\g<assignOp>
\g<separaterOrEmpty>\g<expressionSymbol>)?+)
                                    )
                                |   (?<delegateExpressionSymbol>delegate\g<s
eparaterOrEmpty>
                                        (?<paramSymbol>(?<!\g<noPreLexemeCha
r>)(?:\g<invokeBeginOp>(?:\g<separaterOrEmpty>
                                            (?<paramBodySymbol>
                                                (?<paramSegmentSymbol>(?>
                                                        (?:(?:\g<definitionH
eaderSymbol>\g<separaterOrEmpty>)?+(?>
                                                                (?:\g<typeNa
me>\g<separater>(?>
                                                                        \g<d
iscardSymbol>
                                                                    |   (?<p
aramVarSymbol>
                                                                           
(?:\g<id>(?=\g<postWordClitic>))
                                                                           
(?:\g<separaterOrEmpty>\g<assignOp>\g<separaterOrEmpty>\g<expressionSymbol>)?+
                                                                        )
                                                                ))
                                                            |   \g<discardSy
mbol>
                                                        ))
                                                    |   \g<paramVarSymbol>
                                                    |   (?:__arglist(?=\g<po
stWordClitic>))
                                                ))(?:\g<separaterOrEmpty>\g<
commaOp>\g<separaterOrEmpty>\g<paramSegmentSymbol>)*+
                                            )
                                        )?+\g<separaterOrEmpty>\g<invokeEndO
p>(?=\g<postNonWordClitic>))(?!\g<noPostLexemeChar>))
                                    \g<separaterOrEmpty>\g<blockStatementSym
bol>)
                                |   (?<lambdaExpressionSymbol>
                                        (?>
                                                (?:\g<typeName>\g<separaterO
rEmpty>\g<paramSymbol>)
                                            |   (?:\g<id>(?=\g<postWordCliti
c>))
                                            |   \g<paramSymbol>
                                        )\g<separaterOrEmpty>
                                        (?>
                                                (?<expressionBodiedLambdaSym
bol>(?<!\g<noPreLexemeChar>)(?:
                                                    \g<lambdaOp>\g<separater
OrEmpty>(?:)(?!\g<blockBeginOp>)\g<expressionSymbol>
                                                )(?!\g<noPostLexemeChar>))
                                            |   (?:\g<lambdaOp>\g<separaterO
rEmpty>\g<blockStatementSymbol>)
                                        )
                                    )
                            )
                        )
                    |   (?<indexerSymbol>(?<!\g<noPreLexemeChar>)(?:(?>\g<nu
llConditionalIndexerBeginOp>|\g<indexerBeginOp>)(?:\g<separaterOrEmpty>
                            \g<argBodySymbol>
                        )?+\g<separaterOrEmpty>\g<indexerEndOp>(?=\g<postNon
WordClitic>))(?!\g<noPostLexemeChar>))
                    |   (?:(?>
                                this
                            |   ref
                            |   out
                            |   new
                            |   in
                            |   await
                        )(?=\g<postWordClitic>))
                    |   \g<discardSymbol>
                    |   \g<varExpressionSymbol>
                    |   \g<delegateExpressionSymbol>
                    |   \g<lambdaExpressionSymbol>
                ))
            (?:\g<separaterOrEmpty>\g<expressionSegmentSymbol>)*+(?!\g<noPos
tLexemeChar>))
        \g<separaterOrEmpty>\g<emptyStatementSymbol>)
    |   (?:(?:(?:yield\g<separater>)?+return(?=\g<postWordClitic>))\g<separa
terOrEmpty>\g<expressionSymbol>\g<separaterOrEmpty>\g<emptyStatementSymbol>)
    |   (?:(?>
                return
            |   (?:(?:yield\g<separater>)?+break)
            |   continue
        )\g<separaterOrEmpty>\g<emptyStatementSymbol>)
    |   (?:(?:goto(?=\g<postWordClitic>))\g<separaterOrEmpty>
            (?<labelSymbol>(?>
                    (?:(?:case(?=\g<postWordClitic>))\g<separaterOrEmpty>\g<
expressionSymbol>)
                |   (?:default(?=\g<postWordClitic>))
                |   (?:\g<id>(?=\g<postWordClitic>))
            ))
        \g<separaterOrEmpty>\g<emptyStatementSymbol>)
    |   (?:(?:(?>
                else
            |   try
            |   finally
            |   do
        )(?=\g<postWordClitic>))\g<separaterOrEmpty>\g<statementSymbol>)
    |   (?:(?>
                checked
            |   unchecked
            |   unsafe
        )\g<separaterOrEmpty>\g<blockStatementSymbol>)
    |   (?:(?>
                if
            |   switch
            |   while
            |   lock
        )\g<separaterOrEmpty>\g<invokeSymbol>\g<separaterOrEmpty>\g<statemen
tSymbol>)
    |   (?:
            (?:(?>
                    (?:(?:await\g<separater>)?+using)
                |   fixed
            )(?=\g<postWordClitic>))\g<separaterOrEmpty>(?:(?:)(?!\g<typeNam
e>\g<postWordClitic>))\g<invokeBeginOp>\g<separaterOrEmpty>(?>
                    (?<varDefinitionSymbol>
                        (?:\g<definitionHeaderSymbol>\g<separaterOrEmpty>)?+
(?:\g<typeName>(?=\g<postWordClitic>))\g<separaterOrEmpty>
                        \g<varSymbol>(?:\g<separaterOrEmpty>\g<commaOp>\g<se
paraterOrEmpty>\g<varSymbol>)*+
                    )
                |   \g<expressionSymbol>
            )\g<separaterOrEmpty>\g<invokeEndOp>\g<separaterOrEmpty>\g<state
mentSymbol>
        )
    |   (?:
            (?:catch(?=\g<postWordClitic>))
            (?:\g<separaterOrEmpty>
                (?:\g<invokeBeginOp>\g<separaterOrEmpty>(?>
                        \g<varDefinitionSymbol>
                    |   (?:\g<typeName>(?=\g<postWordClitic>))
                )\g<separaterOrEmpty>\g<invokeEndOp>(?=\g<postNonWordClitic>))
                (?:\g<separaterOrEmpty>when\g<separaterOrEmpty>\g<invokeSymb
ol>)?+
            )?+
            \g<separaterOrEmpty>\g<statementSymbol>
        )
    |   (?:(?:await\g<separater>)?+foreach\g<separaterOrEmpty>(?:\g<invokeBe
ginOp>\g<separaterOrEmpty>
            \g<varDefinitionSymbol>\g<separaterOrEmpty>(?:in(?=\g<postWordCl
itic>))\g<separaterOrEmpty>\g<expressionSymbol>
        \g<separaterOrEmpty>\g<invokeEndOp>(?=\g<postNonWordClitic>))\g<sepa
raterOrEmpty>\g<statementSymbol>)
    |   (?:for\g<separaterOrEmpty>\g<invokeBeginOp>
            (?:(?:\g<separaterOrEmpty>
                (?<forSegmentSymbol>(?>\g<varDefinitionSymbol>|\g<expression
Symbol>))
            )?+\g<separaterOrEmpty>\g<semicolonOp>){2}
            (?:\g<separaterOrEmpty>\g<forSegmentSymbol>)?+
        \g<separaterOrEmpty>\g<invokeEndOp>\g<separaterOrEmpty>\g<statementS
ymbol>)
    |   (?:\g<labelSymbol>\g<separaterOrEmpty>\g<elseOp>\g<separaterOrEmpty>
\g<statementSymbol>)
    |   (?:\g<varDefinitionSymbol>\g<separaterOrEmpty>\g<emptyStatementSymbol>)
    |   (?:
            (?:\g<definitionHeaderSymbol>\g<separaterOrEmpty>)?+
            (?:\g<typeName>\g<separater>\g<id>(?=\g<postWordClitic>))(?:\g<s
eparaterOrEmpty>
                (?<genericParamSymbol>(?<!\g<noPreLexemeChar>)(?:\g<genericI
nvokeBeginOp>\g<separaterOrEmpty>
                    (?<genericParamSegmentSymbol>
                        (?:\g<definitionHeaderSymbol>\g<separaterOrEmpty>)?+
\g<id>
                    )(?:\g<separaterOrEmpty>\g<commaOp>\g<separaterOrEmpty>\
g<genericParamSegmentSymbol>)*+
                \g<separaterOrEmpty>\g<genericInvokeEndOp>(?=\g<postNonWordC
litic>))(?!\g<noPostLexemeChar>))
            )?+\g<separaterOrEmpty>\g<paramSymbol>(?:\g<separaterOrEmpty>
                (?<wheresSymbol>(?<!\g<noPreLexemeChar>)(?:(?:)(?=where\g<po
stWordClitic>)
                    (?<whereSymbol>where\g<separater>\g<id>\g<separaterOrEmp
ty>\g<elseOp>\g<separaterOrEmpty>
                        \g<whereType>(?:\g<separaterOrEmpty>\g<commaOp>\g<se
paraterOrEmpty>\g<whereType>)*+
                    )(?:\g<separaterOrEmpty>\g<whereSymbol>)*+
                )(?!\g<noPostLexemeChar>))
            )?+\g<separaterOrEmpty>
            (?<functionBodySymbol>(?<!\g<noPreLexemeChar>)(?>
                    \g<emptyStatementSymbol>
                |   (?:\g<expressionBodiedLambdaSymbol>\g<separaterOrEmpty>\
g<emptyStatementSymbol>)
                |   \g<blockStatementSymbol>
            )(?!\g<noPostLexemeChar>))
        )
)(?!\g<noPostLexemeChar>))))

----------------------------------------------------------------------------
--------------------


Benchmark.mac マクロ :
----------------------------------------------------------------------------
--------------------
debuginfo 2 ;
#defaultCompatibleMode = setcompatiblemode(
        0x00000003
    |   0x0000000C
    |   0x00000200
    |   0x00020000
    |   0x00080000
    |   0x00400000
    |   0x00800000
    |   0x04000000
    |   0x08000000
) ;
#defaultTopY = screentopy ; #defaultLeftX = screenleftx ; disabledraw ; disa
bleinvert ;
#defaultColumn = column ; #defaultLineNo = lineno ;

setfloatmode 1 ;
#HideMath = loaddll( hidemarudir + @"\HideMath.dll" ) ;
setfloatmode 0 ;

$SearchPattern = input( @"SearchPattern" ,@"(?:)"  ) ;
#RunningSeconds = val( input( @"RunningSeconds" , @"10" ) ) ;
#RunningMilliseconds = #RunningSeconds * 1000 ;

#TestNamesCount = 4 ;
$TestNames[ 0 ] = @"SetSearch" ;
$TestNames[ 1 ] = @"FindDown2" ;
$TestNames[ 2 ] = @"SetSearchFindDown2" ;
$TestNames[ 3 ] = @"SearchDown2" ;

$Pattern = @"" ;

// 比較のための空テスト。
call WriteReportTitle @"Empty" ;
call Benchmark @"Empty" ;
call WriteReportItem @"Empty" , ##return ;
call WriteDebug @"" ;

#includeNamesCount = 9 ;
$includeNames[ 0 ] = @"cslexeme" ;
$includeNames[ 1 ] = @"cskeyword" ;
$includeNames[ 2 ] = @"csid" ;
$includeNames[ 3 ] = @"csop" ;
$includeNames[ 4 ] = @"cstypename" ;
$includeNames[ 5 ] = @"csnumeric" ;
$includeNames[ 6 ] = @"csstring" ;
$includeNames[ 7 ] = @"csspecialtoken" ;
$includeNames[ 8 ] = @"csstatementsymbol" ;

$dummyPattern = @"" ;
#dummyPatternCount = 0 ;
while ( true ) {
    $lastIncludeName = @"" ;
    if ( #dummyPatternCount > 0 ) {
        $lastIncludeName = $includeNames[ #dummyPatternCount - 1 ] ;
    }
   
    call TestPattern
            @"LastIncludeName = " + $lastIncludeName
        ,   $dummyPattern
    ;
   
    if ( #dummyPatternCount >= #includeNamesCount ) {
        break ;
    }
   
    $dummyPattern = (
            $dummyPattern
        +   @"(?#"
        +   $includeNames[ #dummyPatternCount ]
        +   @")"
    ) ;
    #dummyPatternCount = #dummyPatternCount + 1 ;
}

freedll #HideMath ;

moveto2 #defaultColumn , #defaultLineNo ;
enableinvert ; enabledraw #defaultTopY , #defaultLeftX ;
setcompatiblemode #defaultCompatibleMode ;
endmacro ;

BuildTestTitle :
    return @"" ;

TestPattern :
    $$testTitle = $$1 ;
    $$dummyPattern = $$2 ;
   
    call WriteReportTitle $$testTitle ;
   
    $Pattern = (
            @"(?#hmonig)(?#maxlines:32)(?#fulllinematch)(?u)"
        +   $$dummyPattern
        +   $SearchPattern
    ) ;
    call TestRegexDll ;
   
    call WriteDebug @"" ;
   
    return ;

TestRegexDll :
    ##testNamesIndex = 0 ;
    while ( ##testNamesIndex < #TestNamesCount ) {
        call Benchmark $TestNames[ ##testNamesIndex ] ;
        call WriteReportItem
                $TestNames[ ##testNamesIndex ]
            ,   ##return
        ;
       
        ##testNamesIndex = ##testNamesIndex + 1 ;
    }
   
    return ;

// 比較のための空テスト。
Empty_Begin : return ;
Empty_Process : return ;
Empty_End : return ;

// setsearch での設定のみの繰り返し。
SetSearch_Begin :
    moveto2 0 , 1 ;
    return ;
SetSearch_Process :
    setsearch $Pattern , 0x00000002 | 0x00000010 ;
    return ;
SetSearch_End : return ;

// 最初に setsearch で設定して以後 finddown2 での検索を繰り返す。
FindDown2_Begin :
    setsearch $Pattern , 0x00000002 | 0x00000010 ;
    return ;
FindDown2_Process :
    moveto2 0 , 1 ;
    finddown2 ;
    return ;
FindDown2_End : return ;

// finddown2 での検索前にその都度 setsearch で設定する。
SetSearchFindDown2_Begin : return ;
SetSearchFindDown2_Process :
    setsearch $Pattern , 0x00000002 | 0x00000010 ;
    moveto2 0 , 1 ;
    finddown2 ;
    return ;
SetSearchFindDown2_End : return ;

// searchdown2 で設定と検索を一度に行う。
SearchDown2_Begin : return ;
SearchDown2_Process :
    moveto2 0 , 1 ;
    searchdown2 $Pattern , casesense , regular ;
    return ;
SearchDown2_End : return ;

Benchmark :
    $$testName = $$1 ;
   
    call $$testName + @"_Begin" ;
   
    ##count = 0 ;
    ##startTime = tickcount ;
    while ( ( tickcount - ##startTime ) < #RunningMilliseconds ) {
        call $$testName + @"_Process" ;
        ##count = ##count + 1 ;
    }
   
    call $$testName + @"_End" ;
   
    return ##count ;

WriteReportTitle :
    call WriteDebug @"[ " + $$1 + @" ]" ;
    return ;

WriteReportItem :
    $$testName = $$1 ;
    ##count = ##2 ;
   
    setfloatmode 1 ;
    $$milliseconds = dllfuncstr(
            #HideMath
        ,   @"Format"
        ,   @"%.3lf"
        ,   #RunningMilliseconds / ##count
    ) ;
    setfloatmode 0 ;
   
    call WriteDebug
            $$testName
        +   @" = "
        +   $$milliseconds
        +   @" [ms]"
    ;
   
    return ;

WriteDebug :
    debuginfo $$1 + "\U0000000A" ;
    return $$1 ;

----------------------------------------------------------------------------
--------------------


結果報告 :
----------------------------------------------------------------------------
--------------------
[ Empty ]
Empty = 0.012 [ms]

[ LastIncludeName =  ]
SetSearch = 0.015 [ms]
FindDown2 = 0.187 [ms]
SetSearchFindDown2 = 0.191 [ms]
SearchDown2 = 0.189 [ms]

[ LastIncludeName = cslexeme ]
SetSearch = 0.015 [ms]
FindDown2 = 0.532 [ms]
SetSearchFindDown2 = 0.537 [ms]
SearchDown2 = 0.535 [ms]

[ LastIncludeName = cskeyword ]
SetSearch = 0.015 [ms]
FindDown2 = 0.618 [ms]
SetSearchFindDown2 = 0.620 [ms]
SearchDown2 = 0.619 [ms]

[ LastIncludeName = csid ]
SetSearch = 0.016 [ms]
FindDown2 = 0.627 [ms]
SetSearchFindDown2 = 0.633 [ms]
SearchDown2 = 0.627 [ms]

[ LastIncludeName = csop ]
SetSearch = 0.016 [ms]
FindDown2 = 0.783 [ms]
SetSearchFindDown2 = 0.777 [ms]
SearchDown2 = 0.778 [ms]

[ LastIncludeName = cstypename ]
SetSearch = 0.016 [ms]
FindDown2 = 0.939 [ms]
SetSearchFindDown2 = 0.936 [ms]
SearchDown2 = 0.938 [ms]

[ LastIncludeName = csnumeric ]
SetSearch = 0.016 [ms]
FindDown2 = 1.004 [ms]
SetSearchFindDown2 = 1.010 [ms]
SearchDown2 = 0.996 [ms]

[ LastIncludeName = csstring ]
SetSearch = 0.016 [ms]
FindDown2 = 1.123 [ms]
SetSearchFindDown2 = 1.128 [ms]
SearchDown2 = 1.117 [ms]

[ LastIncludeName = csspecialtoken ]
SetSearch = 0.017 [ms]
FindDown2 = 1.248 [ms]
SetSearchFindDown2 = 1.249 [ms]
SearchDown2 = 1.244 [ms]

[ LastIncludeName = csstatementsymbol ]
SetSearch = 0.017 [ms]
FindDown2 = 2000.000 [ms]
SetSearchFindDown2 = 2000.000 [ms]
SearchDown2 = 2000.000 [ms]

----------------------------------------------------------------------------
--------------------



[ ]
RE:09737 正規表現の部分式を宣言しただけNo.09738
秀丸担当 さん 22/02/07 18:38
 
ものすごく複雑で驚いていますが、同じ条件で試してみたところでは、同じように再
現するようでした。
言われている通り、Jre2Compileに時間がかかっている様子のようです。

(?#hmonig)(?u)(?#cslexeme)(?#cskeyword)(?#csid)(?#csop)(?#cstypename)(?#csnu
meric)(?#csstring)(?#csspecialtoken)(?#csstatementsymbol)(?:)

を展開して、19221文字の正規表現パターンをhmonig.dllでJre2Compile処理している
ときに時間がかかっているようです。

[ ]
RE:09738 正規表現の部分式を宣言しただけNo.09739
秀丸担当 さん 22/02/08 14:21
 
ちなみに最終的に強調表示になることが目的だと思いますが、強調表示の場合は、Jr
e2Compileはキャッシュされ、強調表示の結果自体もキャッシュされ、コンパイルと
マッチの2段階のキャッシュがあります。

普通の検索の場合は強調表示と違って、頻繁にコンパイルもマッチも呼びだされやす
いようになっていました。
改善の余地はありますが、リスクもあるので、慎重に改善を検討しようと思います。

現状でやるとしたら、検索ダイアログで「すべて検索 - 色付け」にすると、コンパ
イルは一度だけになります。
マッチも一度だけにはなりますが、ファイル全体に対して行うので、場合によっては
逆に遅くなるかもしれません。
下候補の代わりに「次の結果」コマンドを使うと移動できます。

[ ]
RE:09738 正規表現の部分式を宣言しただけNo.09740
fzok4234 さん 22/02/08 15:07
 
> 言われている通り、Jre2Compileに時間がかかっている様子のようです。


正規表現の初期化・コンパイルのオーバーヘッドが想像以上に大きいことは理解でき
ました。
しかし、現状だと searchdown や finddown 、colormarkerallfound などの実行の度に

 Jre2Compile() でのコンパイル
      ↓
 Jre2GetMatchInfo() などでの検索
      ↓
 Jre2Close() で解放

を繰り返すため、たとえ今回提示したような極端な例ではなくても短時間に大量の
searchdown 、finddown 、colormarkerallfound などの実行を行うときに Jre2Compi
le() の
オーバーヘッドが無視できないほど大きくなってマクロが重くなってしまうことは明
白です。


このため、マクロの最初の段階でこれから使用する正規表現をまとめてコンパイルし
てその
インスタンスを保持しておいて searchdown などでは Jre2GetMatchInfo() などの検
索関数のみを
実行し、マクロの終了時に正規表現インスタンスをまとめて Jre2Close() で解放す
るか、あるいは
keepdll や keepobject と同じように正規表現インスタンスをマクロ終了後も保持し
て、同じ
プロセスで次回のマクロ実行に繰り越せるような仕組みを新設したほうがよいと思い
ます。


具体的な提案として、以下のような方法を思いつきました。まず、

 createregular $regexName , $pattern [ , $regexDllFullPath ] ;
  正規表現をコンパイルしてそのインスタンスを保持する。
   $regexName          : 作成した正規表現インスタンスを識別する名前。
   $pattern            : 正規表現のパターン。
   $regexDllFullPath   : 使用する正規表現 DLL を明示的に指定する。省略時
は「動作環境」の
                         「環境」ページで指定されたものが適用される。

 releaseregular $regexName ;
  正規表現インスタンスを明示的に開放する。
   $regexName          : 解放する正規表現インスタンスの識別名。

 keepregular $regexName , #mode ;
  マクロ終了後も正規表現インスタンスを解放しないようにするかを支持する。
   $regexName          : 正規表現インスタンスの識別名。
   #mode               : 0   : マクロ終了後に自動開放する ( 既定値 ) 。
                         1   : マクロ終了後も自動開放しない。$regexName
は後から再利用や
                               releaseregular が可能。

というような文を新設の上、このコンパイル済み正規表現インスタンスの使用に関し
ては、searchdown 系文では

 searchdown $regexName [ , word ] [ , casesense | nocasesense ] , createdre
gular [ , fuzzy ] [ , inselect ] [ , inselect2 ] [ , linknext ] [ , hilight
| nohilight ] [ , incolormarker ] ;
 searchdown2 $regexName [ , word ] [ , casesense | nocasesense ] , createdr
egular [ , fuzzy ] [ , inselect ] [ , inselect2 ] [ , linknext ] [ , hilight
 | nohilight ] [ , incolormarker ] ;
 searchup $regexName [ , word ] [ , casesense | nocasesense ] , createdregu
lar [ , fuzzy ] [ , inselect ] [ , inselect2 ] [ , linknext ] [ , hilight |
nohilight ] [ , incolormarker ] ;
 searchup2  $regexName [ , word ] [ , casesense | nocasesense ] , createdre
gular [ , fuzzy ] [ , inselect ] [ , inselect2 ] [ , linknext ] [ , hilight
| nohilight ] [ , incolormarker ] ;

というように検索文字列の代わりに正規表現インスタンスの識別名を使用して regul
ar | noregular の代わりに
新設した createdregular というキーワードを使用するようにします。replacedown
系文も以下のように同様とします。

 replacedown $regexName , 置換文字列 [ , word ]  [ ,casesense | nocasesense
 ] , createdregular [ , fuzzy ] [ , inselect ] [ , inselect2 ] [ , linknext
 ] [ , ask ] [ , hilight | nohilight ] [ , incolormarker ] ;
 replaceup $regexName , 置換文字列 [ , word ]  [ ,casesense | nocasesense ]
 , createdregular [ , fuzzy ] [ , inselect ] [ , inselect2 ] [ , linknext ]
[ , ask ] [ , hilight | nohilight ] [ , incolormarker ] ;
 replaceall $regexName , 置換文字列 [ , word ]  [ ,casesense | nocasesense
 ] , createdregular [ , fuzzy ] [ , inselect ] [ , inselect2 ] [ , linknext
 ] [ , ask ] [ , hilight | nohilight ] [ , incolormarker ] ;
 replaceallfast $regexName , 置換文字列 [ , word ]  [ ,casesense | nocasese
nse ] , createdregular [ , fuzzy ] [ , inselect ] [ , inselect2 ] [ , linkne
xt ] [ , ask ] [ , hilight | nohilight ] [ , incolormarker ] ;
 replaceallquick $regexName , 置換文字列 [ , word ]  [ ,casesense | nocases
ense ] , createdregular [ , fuzzy ] [ , inselect ] [ , inselect2 ] [ , linkn
ext ] [ , ask ] [ , hilight | nohilight ] [ , incolormarker ] ;

同様に grep 関係も、

 grep $regexName , 検索するファイル , カレントフォルダ [ , word ] [ , cases
ense | nocasesense ] , createdregular [ , fuzzy ] [ , subdir ] [ , icon ] [ ,
 filelist ] [ , fullpath ] [ , hilight | nohilight ] ;
 localgrep $regexName [ , word ] [ , casesense | nocasesense ] , createdreg
ular [ , fuzzy ] [ , icon ] [ , hilight | nohilight ] ;
 grepreplace $regexName , 置換文字列 , 検索するファイル , カレントフォルダ
[ , word ] [ , casesense | nocasesense ] , createdregular [ , fuzzy ] [ , su
bdir ] [ , icon ] [ , filelist ] [ , fullpath ] [ , hilight | nohilight ] [ ,
 backup ] [ , preview ] ;

という感じにします。searchdialog などの GUI ダイアログの表示関係への対応は後
回しでも構いません。
setsearch 文では

 setsearch $regexName , 0x80000010 | 他のフラグ1 , 0x00000010 | 他のフラグ2 ;

というように、フラグ 2 に新たに 0x00000010 というコンパイル済み正規表現イン
スタンスの使用を
指示するフラグを新設します。

これにより、searchdown 、replacedown 、grep などの実行や setsearch 実行後の
finddown 、colormarkerallfound などでは
内部で Jre2GetMatchInfo() のみを実行する形で大幅に高速化を図るということにな
ります。


いずれにせよ現状では、「予め複数の正規表現をまとめてコンパイルして searchdow
n などではその
コンパイル済みの正規表現インスタンスを呼び出す」ということができないようなの
で、何らかの対策を
お願いしたいところです。

どうかよろしくお願いします。



[ ]
RE:09739 正規表現の部分式を宣言しただけNo.09741
fzok4234 さん 22/02/08 15:41
 
どうやら行き違いになったみたいですね。


> ちなみに最終的に強調表示になることが目的だと思いますが、強調表示の場合は、
>Jre2Compileは
> キャッシュされ、強調表示の結果自体もキャッシュされ、コンパイルとマッチの2
>段階の
> キャッシュがあります。


一応当方では、通常の強調表示や複数行コメントの機能ではなく、マクロからのカ
ラーマーカーでの
強調表示の代用を検討しています。その理由は言うまでもなく

 ・通常の強調表示は複数行に対応していない。
 ・複数行コメントは重なりがあると後からの強調表示が拒否される。
 ・そもそも入れ子になったプログラム要素の文脈判断は単純なパターンマッチでは
できない。

ということですが、実際にマクロでカラーマーカーでの強調表示を行うためには、フ
ァイルの先頭から

 ・変数名などの識別子や演算子、括弧やデリミターなど約物類といった有効なトー
クンを
  1 個ずつ読み取る。
 ・スペースや改行文字、コメントといった無効なセパレーターを読み飛ばす。

といった処理を行い、同時に読み取ったトークンの種類を判別する作業を行う必要が
あります。

その過程で、トークンやセパレーターにマッチする多数の正規表現パターンを文字列
変数として用意した上で、
その正規表現で searchdown2 とかを何度も実行してトークンやセパレーターを読み
進めなければなりません。

そこで問題になるのが 1 回 1 回の searchdown2 などの実行時間です。現状では se
archdown2 などを実行するたびに
一々 Jre2Compile() するため、searchdown2 自体の実行時間が長くなってしまいま
す。ここに、先ほど行き違いで
提案したようなマクロ用の事前 Jre2Compile() 機能があればかなり助かることにな
ります。


どうか対策のご検討よろしくお願いいたします。



[ ]
RE:09740 正規表現の部分式を宣言しただけNo.09742
秀丸担当 さん 22/02/08 17:33
 
強調表示の定義への指定ではなく、その都度の検索の場合は、強調表示のコンパイル
のキャッシュは効果ないです。
複数の正規表現コンパイルの使いまわしもできたらいいと思います。
やるとしたら、ご提案の方法か、あるいは一度使われたパターンを自動的にキャッシ
ュするというのでもいいかもしれません。
ご意見参考にさせていただきます。

[ ]
RE:09738 正規表現の部分式を宣言しただけNo.09743
でるもんたいいじま さん 22/02/08 20:55
 
でるもんた・いいじまです。
書いてある間にいろいろ行き違いになっていました。

秀丸担当さん:
> ものすごく複雑で驚いていますが、

同意です。

ただ、今までのやり取りを眺めている限り、そもそもfzok4234さんがやろうとしてい
ること自体が無理筋のように思えます。fzok4234さんがいま求めている機能は、「フ
ルスペックのコンパイラに実装されている5段階の機能(プリプロセッサ、パーサ、
コードジェネレータ、アセンブラ、リンカ)のうち、パーサの機能を一ユーザである
fzok4234さんが丸ごと再発明したい」「方法は正規表現に限定したい」「秀丸の正規
表現の性能に現状不満であり、高性能なものをサイトー企画さんが無償で提供してほ
しい」という、かなり強引な要求です。

☆ ☆ ☆

以下順番に。そんなの言われなくても知ってるよ、という部分も多々あるかと思いま
すが、「それイミフー!」と言われるよりはマシですのでお付き合いください。

> 言われている通り、Jre2Compileに時間がかかっている様子のようです。
> (?#hmonig)(?u)(?#cslexeme)(?#cskeyword)(?#csid)(?#csop)(?#cstypename)
> (?#csnumeric)(?#csstring)(?#csspecialtoken)(?#csstatementsymbol)(?:)
> を展開して、19221文字の正規表現パターンを

こういう複雑な文法パターン(正規表現に限らずBNFでもYACCでも)のコンパイルに
時間がかかるのは、そもそも当然のことです。むしろ、「今の高性能なPCで、これだ
けのパターンがわずか2.0秒でコンパイルできる」ということのほうに技術革新の速
さを感じざるを得ません。

もし仮に、いまベンチマークを行っているコード一式(各種DLLも秀丸も)を32bitバ
イナリで用意して、それを「購入時はWindows95か98が入っており、延命に延命を重
ねて、今でもWindows98のまま工場機械等の制御に使っている」というクラスの環境
に持っていけば、メモリスワップが発生しない前提でも、このパターンのコンパイル
だけで数分以上かかるはずです。

じゃあ実際のコンパイラなり、複雑なコードの文法チェックに対応しているエディタ
なり(たとえばVSCodeあたり?)では何をしているのかというと、コンパイラ自体・
エディタ自体をビルドする時点で、上記のような構文を記述したYACCソースを予めす
べてC言語のコードへとコンパイルして、当然ながらC言語ですのでそのままCコンパ
イラでバイナリコードへとコンパイルして、そうやってできたバイナリコードを製品
として出荷しています。つまり、EXE/DLLファイルができあがる時点ではもはや、パ
ターンのコンパイルをこれから行うということはないのです。

それに、仮にご提案のように正規表現の事前コンパイルができたとして、それをどこ
かに永久保存することができなければ意味がありません。今のfzok4234さんの構想が
仮に実現したとして、コンパイル結果を丸ごとファイルにエクスポート・インポート
する機能を追加しなければ、WindowsUpdate等でマシンを再起動した時点で、あるい
は再起動しなくてもログオフしてログインし直した時点でデータは失われます。そこ
から数分ないし数十分待つのが現実的ですか?あるいは、対象となるC#の言語仕様に
変更がない限り何回コンパイルしても結果が一切変動しないにもかかわらず、ログイ
ンのたびにコンパイルすることに意味はありますか?

☆ ☆ ☆

では次に、「複雑な正規表現を予めコンパイルしておいて、以後はそれを保存したオ
ブジェクトを使い回す」というアプローチについて。hidesoft.4:09740でfzok4234さ
んが提案されているcreateregular等の構文ですね。

これが最近の多くの言語で実用化されていることは当然私も知っています。たとえば
JavaScriptなら、
var re = new RegExp("(重いパターン)*");
としておけばコンパイルはこの1回だけですし、Google Chromeなどの最近のブラウザ
ではしかるべく記述しておけばこういうコンパイル結果をコードキャッシュに覚えて
おいてくれます。これは何も「最近のJavaScriptで導入された」という話ではなくて、
90年代前半にJavaScriptが誕生した時点から存在する、割と歴史ある文法です。

ところがそもそも秀丸マクロは言語仕様自体として、C#やJavaScriptの複雑な仕様に
は遠く及ばず、オブジェクト管理に関しては「ほとんど何の機能もない」という状況
です。たとえば変数に代入可能な値からして「文字列」と「数値」のみで、オブジェ
クトを収納することはできません。

※createobjectやloaddllは文面だけを見たところオブジェクトを格納しているよう
に見えますが、実際にはオブジェクトそのものはマクロから見えない場所に格納され
ていて、変数に格納しているのはそのインデックスです。下記のテストコードを参照。

#a = loaddll("hmjre.dll");
message sprintf("loaddll('hmjre.dll') -> %d\n", #a);

#b = loaddll("hmonig.dll");
message sprintf("loaddll('hmonig.dll') -> %d\n", #b);

当然ですが、最初のloaddllが通った後で dllfuncstr(1, "ReplaceRegular", ....)
のように書いてもあっさり動くはずです。(マクロエンジンが意図的にこの記述をエ
ラーにしていなければ、ですが。)

さらに言うと、ガベージコレクションもイマドキの水準には程遠いものですし、秀丸
マクロに関しては「プロセスのリサイクル」「複数のプロセス間でのデータ共有」と
いう機構があるので、「いつになればオブジェクトの寿命が尽きる(=メモリを解放
してよい)のか」という議論も必要です。ちょっとユーザがコードを誤っただけで、
メモリリークが際限なく蓄積していくリスクがあります。

「プロセスのリサイクル」「複数のプロセス間でのデータ共有」の影響として、「各
スレッドごとに名前空間を分離できない」という問題点もあります。これはどう解決
しますか?

ここまで見てきた時点でもう、fzok4234さんのお考えを丸々そのまま鵜呑みにして実
装するのは二重三重に無理筋、と分かります。

☆ ☆ ☆

長くなりますので一旦このあたりで切ります。

[ ]
RE:09743 正規表現の部分式を宣言しただけNo.09744
fzok4234 さん 22/02/08 22:33
 
> じゃあ実際のコンパイラなり、複雑なコードの文法チェックに対応しているエディ
>タなり(たとえばVSCodeあたり?)
> では何をしているのかというと、コンパイラ自体・エディタ自体をビルドする時点
>で、

実は VSCode はそれ単体でコンパイルを行っているわけではなく、Language Server
Protocol ( LSP ) を用いて各言語の
LSP サーバーと通信を行う LSP クライアントに過ぎないようです。

LSP とはプログラミング言語の構文解析のロジックを「あらゆるテキストエディタ /
 IDE」に提供するための標準化された
仕組みであり、実際の構文解析は LSP サーバーというテキストエディタとは分離さ
れたプログラムが行い、
テキストエディタ側は自前で構文解析を行わずに実装した LSP クライアントで構文
解析の指示を LSP サーバー に送って、
これから返ってきた応答に基づいて強調表示なりコード補完なりを行うと行うという
ものです。

LSP サーバー自体は https://microsoft.github.io/language-server-protocol/implementors/servers/
 にて様々な言語の
ものが無償配布されています。各テキストエディタは LSP クライアントを実装する
ことでこれらの LSP サーバーを
用いて様々な言語の構文解析に基づく機能が使用可能になるわけです。

LSP が「あらゆるテキストエディタ / IDE」を対象にしたものである以上、秀丸エデ
ィタも LSP クライアントの実装に
向けて動き出すのが理想なのですが、現時点では全くその兆しが無いため、当面は
ユーザーが自力でマクロを駆使して
構文解析を行わなければならないというのが実情です。しかし、その自力での構文解
析すら妨げる問題が次々と見つかって
いるのが現在の構図です


> それに、仮にご提案のように正規表現の事前コンパイルができたとして、それをど
>こかに永久保存することが
> できなければ意味がありません。今のfzok4234さんの構想が仮に実現したとして、
>コンパイル結果を丸ごと
> ファイルにエクスポート・インポートする機能を追加しなければ、WindowsUpdate
>等でマシンを再起動した時点で、
> あるいは再起動しなくてもログオフしてログインし直した時点でデータは失われま
>す。そこから数分ないし
> 数十分待つのが現実的ですか?あるいは、対象となるC#の言語仕様に変更がない限
>り何回コンパイルしても
> 結果が一切変動しないにもかかわらず、ログインのたびにコンパイルすることに意
>味はありますか?

一応、想定している 1 個のソースファイルは最大で 5000 行程度、トークンの数が
2 万個程度の比較的小さなものです。
ファイルを開くときに自動起動マクロの「ファイルを開いた直後」で呼び出し、その
際に 3 〜 4 秒程正規表現の
コンパイルで待たされることは覚悟しています。

最初に時間をかけてコンパイルしてそれを keepregular で保持してその後は自動起
動マクロの「編集後タイマー」で
コンパイル済みの状態で検索 / カラーマーカーを行うか、それすら待ち時間がスト
レスに感じたら通常の「マクロ登録」
でキーボードショートカットで手動で呼び出す、という運用を想定しています。


> さらに言うと、ガベージコレクションもイマドキの水準には程遠いものですし、秀
>丸マクロに関しては
> 「プロセスのリサイクル」「複数のプロセス間でのデータ共有」という機構がある
>ので、
> 「いつになればオブジェクトの寿命が尽きる(=メモリを解放してよい)のか」と
>いう議論も必要です。
> ちょっとユーザがコードを誤っただけで、メモリリークが際限なく蓄積していくリ
>スクがあります。

これに関しては

 ・原則として正規表現インスタンスはマクロが終了した時点で全て自動で破棄する。
次回のマクロ実行まで繰り越すには
  明示的に keepregular $regexName , 1 ; としなければならない。
 ・秀丸エディタのプロセスを跨いでは正規表現インスタンスは共有はできない。lo
addll と同様に nexthidemaru などで
  別の秀丸エディタに切り替わったときは改めて createregular しなおす必要が
ある。
 ・もちろん秀丸エディタを閉じるときはそのウィンドウ / タブの正規表現インス
タンスは自動で破棄する。

でよいと思います。


いずれにせよ、秀丸エディタ側が本格的に LSP クライアント実装に動き出すか、あ
るいは誰かが秀丸エディタ向けの
LSP クライアント DLL を作るかすれば、ユーザーサイドの苦労は大幅に軽減される
はずです。それが実現するまでは
この「戦い」は継続することになります。



[ ]
RE:09742 正規表現の部分式を宣言しただけNo.09745
でるもんたいいじま さん 22/02/08 23:28
 
でもんた・いいじまです。

秀丸担当さん:
> 複数の正規表現コンパイルの使いまわしもできたらいいと思います。
> やるとしたら、ご提案の方法か、あるいは一度使われたパターンを
> 自動的にキャッシュするというのでもいいかもしれません。
> ご意見参考にさせていただきます。

☆ ☆ ☆

そうですね…自動的にキャッシュする方法だと、特定のユーザが一体何通りの正規表
現を使うのか、そのデータ量(ソースコードとコンパイル後のそれぞれ)はどのくら
いになるのか、の見積もりが必要だと思います。

このあたりはfzok4234さんのほうでデータを提示していただくのが早いと思います。

ちょっとこちらではJRE32.dll互換APIの資料が全く見つからず(hmjre.chm に記載の
リンクを踏んでも、サーバの契約が切れているためエラーメッセージが返ってきます。
archive.orgを当たってみても表紙ページ以外の具体的なデータは存在せず)、コン
パイル後のデータサイズをどうやって取得すればいいのかは全く分からないのですが、
ソースコードのトータルサイズはすぐに提示できるはずです。

あるいは、サイトー企画さん側さえよろしければ、fzok4234さんの手元にある大量の
正規表現パターンをまとめて圧縮したものをメールで送付していただいて、残存バグ
の吐き出しも兼ねてサイトー企画さんのほうでベンチマークをしていただく、という
方法もあるかと思います。

それを踏まえて、キャッシュの最大サイズ(と、そもそもキャッシュ機能を使うかど
うか)を上級者向け設定で指定できるようにするのが順当な流れかなと思います。

☆ ☆ ☆

それともう一つ、「部分式を予め定義しておく」という機能は、実は一般のユーザー
さんにも需要があるのではと思います。fzok4234さんの使い方はかなり極端ですが
(その点についての批判はまた後日)、ちょっと込み入った正規表現で検索・置換を
しようとすると、現状ではマクロファイルに保存しておくしかありません。

一応、秀丸には「キー操作を記録して、そのままマクロ形式で保存する機能」があり
ますが、そういう細かいマクロも数が多くなると管理が面倒ですし、一旦記録した内
容を丸ごと録り直しではなく部分的に修正したいと思ったら一定のプログラミングス
キルが必要ですし、そうすると「"..." の内側での \ の扱い」や逆に「@"..." の内
側での " の扱い」でハマる人が続出します。

というわけでほぼほぼ別個のご提案になるのですが、hidemaru.exeでも別バイナリで
もいいのでたとえばGUIで下記のようなテーブルを表示して、各ユーザが使いたい部
分式を登録しておけるようにする、というのはどうでしょうか。

<table>
<tr><th>対象<th>ラベル<th>内容<th>メモ</tr>
<tr><td>検索<td>h2k<td>[ぁ-ん]+<td>全角ひらがなをカタカナに</tr>
<tr><td>置換<td>h2k<td>\(0,ToZenkakuKata)<td>カタカナに変換<tr>
<tr><td>検索<td>dakuten<td>[ガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペ
ポ]+<td>濁点・半濁点つきの全角カタカナ<tr>
<tr><td>検索<td>nofile1<td>[\x00-\x1F\x7F]+<td>ファイル名に使えない文字@コ
ントロールコード<tr>
<tr><td>検索<td>nofile2<td>["\*/:<>\?\\]+<td>ファイル名に使えない文字Aシェ
ル文字<tr>
<tr><td>置換<td>nofile1<td>_<td>下線1個に置換<tr>
<tr><td>置換<td>nofile2<td>\(0,ToZenkaku)<td>全角に変換<tr>
</table>

この例の場合、普通に置換ダイアログを出して
  検索文字列 :\g<nofile2>
  置換先文字列:\g<nofile2> ←ここの書き方をどう簡略化するかは要検討
のように置換すれば上記の「ファイル名に使えない文字A」を丸ごと置換できる、と
いった具合です。

どうでしょうか。重いパターンは登録時点でのコンパイルも可能にして、このテーブ
ルの情報一式(コンパイルした場合はそのバイナリも)を一つのファイルに保存して
おく、ということにすれば、少数のヘビーユーザーさん以外にも広く恩恵がいきわた
るのではと思います。

保存先と保存方法に関しては、下記のようになると思います。
@レジストリは「一つのサブキーで合計64KB」という制限があるので避けたい。
Aとすると、AppDataにファイルを作るのが順当。
Bデータ形式は、単純なベタ書きで十分な性能が出るならそれがベストだが、もし重
くなるようならSQLite等を使うことも要検討。

☆ ☆ ☆

P.S.
上の提案に関連して簡単なテストをしてみたところ、仕様と言っていいのかどうか分
からない挙動がありましたので、別投稿で質問させていただきます。

[ ]
RE:09744 正規表現の部分式を宣言しただけNo.09746
でるもんたいいじま さん 22/02/09 05:30
 
でるもんた・いいじまです。

> > じゃあ実際のコンパイラなり、複雑なコードの文法チェックに
> > 対応しているエディタなり(たとえばVSCodeあたり?)では
> > 何をしているのかというと、コンパイラ自体・エディタ自体を
> > ビルドする時点で、
>
> 実は VSCode はそれ単体でコンパイルを行っているわけではなく、
> Language Server Protocol ( LSP ) を用いて各言語の
> LSP サーバーと通信を行う LSP クライアントに過ぎないようです。

あ゛ー、思い出しました。そうです、Language Server Protocolです。以前にもどな
たかと議論したことがありました。(その時もfzok4234さんでしたっけ?)

で、私のほうから「機密扱いのコードが漏洩する懸念はないのか」と質問したところ、
「LANの内側に社内専用のLSPサーバーを立てればよい」と教えていただいたのでした。

☆ ☆ ☆

> LSP サーバー自体は
> https://microsoft.github.io/language-server-protocol/implementors/servers/
> にて様々な言語のものが無償配布されています。
> 各テキストエディタは LSP クライアントを実装することでこれらの LSP サーバーを
> 用いて様々な言語の構文解析に基づく機能が使用可能になるわけです。

少しだけ覗いてみました。とりあえずclangの公式LSPサーバエンジンのファイル構成
を眺めてみましたが、本体はC++で実装、ビルド時に必要な各種テキストデータの生
成にはPythonを併用、という構成のようですね。

☆ ☆ ☆

> LSP が「あらゆるテキストエディタ / IDE」を対象にしたものである以上、
> 秀丸エディタも LSP クライアントの実装に向けて動き出すのが
> 理想なのですが、現時点では全くその兆しが無いため、

ここまでは何とか理解できます。でも、MSのサイトに載っているLSPサーバは約140件、
クライアントは約40件しかありません。しかも、クライアントのうち約10件がMicros
oftの提供、他にはvim用の実装が多数、ということで、そもそも対応しているクライ
アントのほうが圧倒的少数と言っていいと思います。

そして、少なくともC#に関しては

> 当面はユーザーが自力でマクロを駆使して
> 構文解析を行わなければならない

というのは無理筋です。そもそも「C#の文法全体」が「正規表現だけで原理的に記述
可能なデータ」になっているかどうかすら怪しいですし、hidesoft.4:09737 の例を
見てもたとえば
  (?<newLineChar>[\x{000A}\x{000D}])
  (?<lineBodyChar>[^\x{000A}\x{000D}])
なんていう部分は、普通のパーサーならC言語で生のポインタを使って実装するはず
です。その2つ下の
  (?<spaceChar>[\p{Zs}\t\v\f])
なんてのも普通は
  if ( (9<=c && c<=13) || (0x2000<=c && c<=0x200C) || ListOfSpaceChars.ifE
xist(c) )
みたいにハードコードするはずです。こういう「多重ループの一番内側で繰り返し繰
り返し行われる単純な処理」はCやアセンブラで徹底的に高速化するのが定石です。
それを正規表現で書いている時点で既に、竹槍でB29に立ち向かっているようなもの
です。

ちなみに、私がプログラムを書くときには、基本的なチェックだけをエディタにやら
せて、それ以上のことは原則、コンパイラに投げています。ここでいう「基本的なチ
ェック」とは、たとえば次のようなものです。
@言語の予約語や標準ライブラリ関数などと、自分が書いた変数・関数名が競合して
いないか
A基本的な構文は合っているか(たとえば、引用符や括弧類はきちんと整合している
か)
Bキーワードのスペルミスをしていないか(正しいスペルなら正しく強調表示される
が、間違っていたら思い通りの色にならない)
C特別な意味を持つ文字列(たとえばWindowsなら "kernel32.dll" を使うでしょう
し、Perlなら use Digest::SHA "sha1_hex"; のような書き方があります)もスペル
ミスをしていないか
D自分自身によくありがちなミスをしていないか、具体的には私の場合は
 ・コロンとセミコロン、コンマとピリオドの取り違え
 ・某言語では正規表現マッチを str ~= "ab+c" のように書くが、これをPerlの癖
で str =~ "ab+c" と書いていないか
 ・文末のセミコロンを不適切な位置に打っていないか(慣れていない言語の場合)
といったもの

このあたりまで自動化しておけば、あとはもうデバッガの出番です。

<余談>
というか、LSPなしでは解読できないような複雑な書き方はそもそも避けるべき、と
いうのが私の持論です。たとえば、C言語のこういう紛らわしい例。
  char * const a;
  char const * b;
片方は「変数の値の書き換え不可」で、もう片方は「ポインタが示す先の書き換え不
可」です。さてどっちがどっちだ、と。こういうのは
  typedef char const CCHAR;
  typedef char *PCHAR;
としてから
  PCHAR const a;
  CCHAR *b;
とすれば紛れません。元々の紛らわしいコードのままでも、変数を直接いじれば当然
コンパイルエラーになりますが、たとえば scanf("%20s", b); なんていうミスは環
境次第で容易に見過ごされます。

私自身に関して言えば、満足な道具がほとんど手に入らなかった時代からずっとプロ
グラミングをやっているので、「足らぬ足らぬは工夫が足らぬ」が板についてるんで
すね。秀まるおさんや担当さんも同様のバックグラウンドをお持ちのはずで、だから
「どうしてもLSPがほしい!」という熱量が出てこないのだと思います。
</余談>

もちろん、ソースの総量がGB単位になるような巨大プロジェクトの現場なら単語補完
やヒエラルキー解析なども役に立つのでしょうが、幸か不幸か私はそういう現場を徹
底的に避けています(笑)

☆ ☆ ☆

> いずれにせよ、秀丸エディタ側が本格的に LSP クライアント実装に
> 動き出すか、あるいは誰かが秀丸エディタ向けの LSP クライアント
> DLL を作るかすれば、ユーザーサイドの苦労は大幅に軽減されるはずです。

結論からいうと、どうしても「秀丸上でLSP」をねだるのであれば、後者(DLLを作
る)のほうが現実的だと思います。動作が安定するまでは頻繁に修正・再コンパイル
や実験を繰り返すことになるでしょうし、秀丸本体のソースコードは非公開なので、
第三者が実装するなら必然的に別バイナリになりますし。

ただ、fzok4234さんは以前に「自分にはネイティブコードが書けない」と仰っていま
したけど、逆にネイティブコードを主戦場にしている人はLSPのメリットをなかなか
実感できないです。たとえば今の私自身はC言語よりもHTML+CSSやPerlを書く機会の
ほうが圧倒的に多いのですが、実際に色々なトラブルに直面してきて、その原因が
「機械で検出可能な構文ミス・設計ミス」だったことはほとんどありません。それな
のにLSPクライアント実装のハードルはめっぽう高いわけで、少なくとも私にとって
は費用対効果は無限小です。

結局のところ、「秀丸でファイルを編集して、そのファイルをVSCodeで開いてLSPに
通して、問題が見つかれば修正して、また秀丸に戻ってくる」という使い方が当面の
落としどころのように思えますが、いかがでしょうか?

[ ]
RE:09745 正規表現の部分式を宣言しただけNo.09747
秀丸担当 さん 22/02/09 09:58
 
キャッシュは、自動でキャッシュするか、自動でキャッシュを指示する一文でやって
みようとしています。
キャッシュするサイズは、個数は決めることはできても、どれだけメモリを使うかは
本体からは知ることはできないので、メモリ使用量の上限を決めるのはできなさそう
です。
常時キャッシュもできなくないと思いますが、際限なく増える可能性もあるので、マ
クロ内で把握できるぶんにしたほうがいいかもしれません。


正規表現の使いまわしについては、秀丸エディタの基本機能には含まれないのですが、
HmJreSelect.dllというものがあります。(fzokさんしか使っていない気がしますが)
https://hide.maruo.co.jp/lib/macro/hmjreselect010.html
これを使うと、よく使うパターンを定義しておいて簡略表記ができます。
もし使う場合、なんでも自分流にできてしまうので、ある程度正規表現の文法に則し
た書き方にしたほうがいいです。
正規表現DLLを通してのことで、置換文字列のほうはできないです。
ちなみに置換で変換モジュールを使う操作を簡便にするには、「すべて検索 - 複数
選択」で複数選択してから、[編集]→[変換]→[...]で一括して変換する方法もあり
ます。

[ ]
RE:09745 正規表現の部分式を宣言しただけNo.09748
秀まるお2 さん 22/02/09 10:21
 
> ちょっとこちらではJRE32.dll互換APIの資料が全く見つからず

 JRE32.dll用のドキュメントはどこにもリンクが無いですが、一応、以下のURLから
ダウンロードできます。

    https://hide.maruo.co.jp/software/bin3/jredoc.lzh

 LZH形式になってるのでセキュリティソフトが警告を出すかもしれませんが、その
辺うまく対処すれば解凍できると思います。

 あと、HmJre.dllで独自に追加したAPI類があって、それについてはJreFuzzy.hって
ファイルに記述してあります。それは、最新版を今アップロードしました。

    https://hide.maruo.co.jp/software/bin3/jrefuzzy_h_v529.zip

 です。

 基本的に自分用の物なのでいいかげんで、質問されても返事できないこともありま
す。

 よろしくお願いします。

[ ]
RE:09746 正規表現の部分式を宣言しただけNo.09749
秀丸担当 さん 22/02/09 10:27
 
LSPはできたらいいとずっと思ってはいますが、本体としてはやっていないです。
もし誰かする人がいるとして、とりあえず基本的な機能としてあるべきものを押さえ
ておくといいと思っていますが、updatecountとdisabledraw2(仮)があるといいので
はないかと思います。

updatecountは要望がありそうかと思っていて、無かったのですが、非同期処理では
たぶん必要になることはあるのでV8.98の時点で追加しています。
disabledraw2(仮)は、disabledrawとは違って純粋に描画しないだけの文があったら
いいです。従来からあるdisabledrawは、内部的にウィンドウサイズを0にするという
手法のため、そのあたりを効率的にできたらいいです。

従来からcolormarker文でも色付けはできますが、fzok4234さんの要望で最近はコメ
ントや文字列等の意味のある強調と同等にもできます。
何らかの外部処理があるとして、最近はそれを反映できる状態になったので、自由に
強調表示するとしたらそっち方面でやっていくのがいいのではないかと思います。
あと内部的にスペルチェック用に波線の指定もあるのですが、colormarker文ででき
てもいいと思います。

正規表現でどんどんやっていっているのというのは、無理があるというか、テスト用
のサンプルでは本来ならできない文字数になってしまっています。
HmJreSelectとhmonig.dllの組み合わせでできてしまっている状態で、検索文字列の
上限(4095文字)と、HmJre.dllの上限も調べてみたら16KBで、19221文字は本当ならど
ちらも超えてしまっています。

[ ]
RE:09746 正規表現の部分式を宣言しただけNo.09750
fzok4234 さん 22/02/09 13:55
 
> 結論からいうと、どうしても「秀丸上でLSP」をねだるのであれば、後者(DLLを作
>る)のほうが
> 現実的だと思います。動作が安定するまでは頻繁に修正・再コンパイルや実験を繰
>り返すことに
> なるでしょうし、秀丸本体のソースコードは非公開なので、第三者が実装するなら
>必然的に
> 別バイナリになりますし。
>
> ただ、fzok4234さんは以前に「自分にはネイティブコードが書けない」と仰ってい
>ましたけど、
> 逆にネイティブコードを主戦場にしている人はLSPのメリットをなかなか実感でき
>ないです。
> たとえば今の私自身はC言語よりもHTML+CSSやPerlを書く機会のほうが圧倒的に多
>いのですが、
> 実際に色々なトラブルに直面してきて、その原因が「機械で検出可能な構文ミス・
>設計ミス」だったことは
> ほとんどありません。それなのにLSPクライアント実装のハードルはめっぽう高い
>わけで、少なくとも
> 私にとっては費用対効果は無限小です。
>
> 結局のところ、「秀丸でファイルを編集して、そのファイルをVSCodeで開いてLSP
>に通して、問題が
> 見つかれば修正して、また秀丸に戻ってくる」という使い方が当面の落としどころ
>のように思えますが、
> いかがでしょうか?

少し調べたところ、ohtorii 氏が秀丸エディタ用の LSP クライアント開発に挑戦さ
れているようです。

https://github.com/ohtorii/hidemaru_lsp_client

こちらの推移を見守りつつ、当面は 秀丸エディタ <=> VSCode <=> コンパイラー を
行き来する体制か、
あるいは件の構文解析マクロの自作かで対処することになる見通しです。



[ ]
RE:09747 正規表現の部分式を宣言しただけNo.09751
fzok4234 さん 22/02/09 14:57
 
> キャッシュは、自動でキャッシュするか、自動でキャッシュを指示する一文でやっ
>てみようと
> しています。
> キャッシュするサイズは、個数は決めることはできても、どれだけメモリを使うか
>は本体からは
> 知ることはできないので、メモリ使用量の上限を決めるのはできなさそうです。
> 常時キャッシュもできなくないと思いますが、際限なく増える可能性もあるので、
>マクロ内で
> 把握できるぶんにしたほうがいいかもしれません。

一応、当方で把握しているキャッシュすべき正規表現パターンの数は、多くて 200
個ぐらいです。
このため、キャッシュする正規表現パターンの数の最大値を多く見積もって 1024 個
までとして、
「動作環境」の「パフォーマンス」ページの「詳細」の「上限の設定」で自由に変更
できるように
するとよいと思います。

また、自動キャッシュは

 0. マクロが終了したら直ちに自動開放するキャッシュ。
 1. マクロが終了しても解放せず、同じ秀丸エディタで再度マクロを実行時にも使
用可能なキャッシュ。

の 2 系統 ( それぞれ最大 1024 個 ) を用意して、マクロの 1 回の実行だけキャッ
シュするのか、
それとも自動起動マクロなどで同じ秀丸エディタで繰り返し使用するためにマクロ終
了後も保持するのかを
選択できるようにした方がよいでしょう。

具体的には、

 enableregularcache #mode ;
  #mode  : 上記の 2 系統のキャッシュを選択。
             0  マクロが終了したら直ちに自動開放する。
             1  マクロが終了しても解放せず保持する。

と、

 disableregularcache ;

で囲った間の searchdown や finddown の実行時だけキャッシュして、ユーザーがキ
ャッシュしたくない
正規表現までキャッシュしてしまうのを防ぐようにするとともに、

 releaseregularcache #mode ;

での明示的なキャッシュの破棄や、

 #maxRegularCacheCount = getmaxinfo( 8 ) ;

でキャッシュの最大個数を取得可能にする、といった構文を用意した方がよいと思わ
れます。



[ ]
RE:09751 正規表現の部分式を宣言しただけNo.09752
秀丸担当 さん 22/02/09 17:26
 
だいたい似たような感じで、setregularcache文を追加してみています。
また仕様とか変えてしまうかもしれないですが、保持するように指示したのを削除で
きないので、setregularcache 0;とかで消せるようにもしておこうと思います。

個数上限は普段の検索使いでも常時にキャッシュするとしたら、上限が必要と思いま
すが、一応マクロが把握できる範囲内という想定で、上限はそれほど重要ではないか
もしれません。
でも事故があるかもしれないので、上限も決めておきます。

[ ]
RE:09752 正規表現の部分式を宣言しただけNo.09753
fzok4234 さん 22/02/09 17:48
 
素早い対応ありがとうございます。


9.12β8 にて setregularcache のテストを行いましたが、

 setregularcache #mode ;

の構文は正しく動作しているようなのですが、

 setregularcache $pattern , #mode ;

の構文が機能していないようです。

最初に提示した .HmJreSelect の内容を少しダイエットして searchdown2 や finddo
wn2 の実行時間が
320 [ms] 程度になるようにしたものでテストしましたが、

 setregularcache 2 ;

を追加した場合は 0.2 [ms] まで短縮できたものの、

 setregularcache $pattern , 1 ;

では 320 [ms] 程度と何もしない場合と変わりませんでした。


ベンチマーク用マクロ :
----------------------------------------------------------------------------
--------------------
debuginfo 2 ;
#defaultCompatibleMode = setcompatiblemode(
        0x00000003
    |   0x0000000C
    |   0x00000200
    |   0x00020000
    |   0x00080000
    |   0x00400000
    |   0x00800000
    |   0x04000000
    |   0x08000000
) ;
#defaultTopY = screentopy ; #defaultLeftX = screenleftx ; disabledraw ; disa
bleinvert ;
#defaultColumn = column ; #defaultLineNo = lineno ;

setfloatmode 1 ;
#HideMath = loaddll( hidemarudir + @"\HideMath.dll" ) ;
setfloatmode 0 ;

$SearchPattern = input( @"SearchPattern" ,@"(?:)"  ) ;
#RunningSeconds = val( input( @"RunningSeconds" , @"10" ) ) ;
#RunningMilliseconds = #RunningSeconds * 1000 ;
#Mode = val( input( @"Mode" , @"0" ) ) ;

#TestNamesCount = 4 ;
$TestNames[ 0 ] = @"SetSearch" ;
$TestNames[ 1 ] = @"FindDown2" ;
$TestNames[ 2 ] = @"SetSearchFindDown2" ;
$TestNames[ 3 ] = @"SearchDown2" ;

$Pattern = @"" ;
$isPatternCachedKey = @"isPatternCached" ;
if ( getstaticvariable( $isPatternCachedKey , 0 ) == @"" ) {
    setstaticvariable $isPatternCachedKey , str( false ) , 0 ;
}

#includeNamesCount = 9 ;
$includeNames[ 0 ] = @"cslexeme" ;
$includeNames[ 1 ] = @"cskeyword" ;
$includeNames[ 2 ] = @"csid" ;
$includeNames[ 3 ] = @"csop" ;
$includeNames[ 4 ] = @"cstypename" ;
$includeNames[ 5 ] = @"csnumeric" ;
$includeNames[ 6 ] = @"csstring" ;
$includeNames[ 7 ] = @"csspecialtoken" ;
$includeNames[ 8 ] = @"csstatementsymbol" ;

#includeNamesIndex = 0 ;
$dummyPattern = @"" ;
while ( #includeNamesIndex < #includeNamesCount ) {
    $dummyPattern = (
            $dummyPattern
        +   @"(?#"
        +   $includeNames[ #includeNamesIndex ]
        +   @")"
    ) ;
   
    #includeNamesIndex = #includeNamesIndex + 1 ;
}

call TestPattern
        @"SearchPattern = " + $SearchPattern + @" , Mode = " + str( #Mode )
    ,   $dummyPattern
;

freedll #HideMath ;

moveto2 #defaultColumn , #defaultLineNo ;
enableinvert ; enabledraw #defaultTopY , #defaultLeftX ;
setcompatiblemode #defaultCompatibleMode ;
endmacro ;

TestPattern :
    $$testTitle = $$1 ;
    $$dummyPattern = $$2 ;
   
    call WriteReportTitle $$testTitle ;
   
    $Pattern = (
            @"(?#hmonig)(?#maxlines:32)(?#fulllinematch)(?u)"
        +   $$dummyPattern
        +   $SearchPattern
    ) ;
   
    if ( #Mode == 1 ) {
        setregularcache 2 ;
    } else if ( #Mode == 2 ) {
        if ( ! val( getstaticvariable( $isPatternCachedKey , 0 ) ) ) {
            setregularcache $Pattern , 1 ;
            setstaticvariable $isPatternCachedKey , str( true ) , 0 ;
        }
    } else {
        // 何もしない。
    }
   
    call TestRegexDll ;
   
    call WriteDebug @"" ;
   
    return ;

TestRegexDll :
    ##testNamesIndex = 0 ;
    while ( ##testNamesIndex < #TestNamesCount ) {
        call Benchmark $TestNames[ ##testNamesIndex ] ;
        call WriteReportItem
                $TestNames[ ##testNamesIndex ]
            ,   ##return
        ;
       
        ##testNamesIndex = ##testNamesIndex + 1 ;
    }
   
    return ;

// 比較のための空テスト。
Empty_Begin : return ;
Empty_Process : return ;
Empty_End : return ;

// setsearch での設定のみの繰り返し。
SetSearch_Begin :
    moveto2 0 , 1 ;
    return ;
SetSearch_Process :
    setsearch $Pattern , 0x00000002 | 0x00000010 ;
    return ;
SetSearch_End : return ;

// 最初に setsearch で設定して以後 finddown2 での検索を繰り返す。
FindDown2_Begin :
    setsearch $Pattern , 0x00000002 | 0x00000010 ;
    return ;
FindDown2_Process :
    moveto2 0 , 1 ;
    finddown2 ;
    return ;
FindDown2_End : return ;

// finddown2 での検索前にその都度 setsearch で設定する。
SetSearchFindDown2_Begin : return ;
SetSearchFindDown2_Process :
    setsearch $Pattern , 0x00000002 | 0x00000010 ;
    moveto2 0 , 1 ;
    finddown2 ;
    return ;
SetSearchFindDown2_End : return ;

// searchdown2 で設定と検索を一度に行う。
SearchDown2_Begin : return ;
SearchDown2_Process :
    moveto2 0 , 1 ;
    searchdown2 $Pattern , casesense , regular ;
    return ;
SearchDown2_End : return ;

Benchmark :
    $$testName = $$1 ;
   
    call $$testName + @"_Begin" ;
   
    ##count = 0 ;
    ##startTime = tickcount ;
    while ( ( tickcount - ##startTime ) < #RunningMilliseconds ) {
        call $$testName + @"_Process" ;
        ##count = ##count + 1 ;
    }
   
    call $$testName + @"_End" ;
   
    return ##count ;

WriteReportTitle :
    call WriteDebug @"[ " + $$1 + @" ]" ;
    return ;

WriteReportItem :
    $$testName = $$1 ;
    ##count = ##2 ;
   
    setfloatmode 1 ;
    $$milliseconds = dllfuncstr(
            #HideMath
        ,   @"Format"
        ,   @"%.3lf"
        ,   #RunningMilliseconds / ##count
    ) ;
    setfloatmode 0 ;
   
    call WriteDebug
            $$testName
        +   @" = "
        +   $$milliseconds
        +   @" [ms]"
    ;
   
    return ;

WriteDebug :
    debuginfo $$1 + "\U0000000A" ;
    return $$1 ;

----------------------------------------------------------------------------
--------------------


結果報告 :
----------------------------------------------------------------------------
--------------------
// 何もしないとき
[ SearchPattern = (?:) , Mode = 0 ]
SetSearch = 0.017 [ms]
FindDown2 = 322.581 [ms]
SetSearchFindDown2 = 322.581 [ms]
SearchDown2 = 322.581 [ms]

// setregularcache 2 ; 使用時
[ SearchPattern = (?:) , Mode = 1 ]
SetSearch = 0.017 [ms]
FindDown2 = 0.200 [ms]
SetSearchFindDown2 = 0.200 [ms]
SearchDown2 = 0.199 [ms]

// setregularcache $Pattern , 1 ; 使用時
[ SearchPattern = (?:) , Mode = 2 ]
SetSearch = 0.017 [ms]
FindDown2 = 322.581 [ms]
SetSearchFindDown2 = 322.581 [ms]
SearchDown2 = 322.581 [ms]

----------------------------------------------------------------------------
--------------------



[ ]
RE:09753 正規表現の部分式を宣言しただけNo.09754
秀丸担当 さん 22/02/10 09:08
 
早速のご確認ありがとうございます。
確かにそうなることが確認できました。
現状でキャッシュを使うようにするには、文字列指定の場合であっても、あらかじめ
setregularcache 1;//もしくは2;
のように最初に0以外にして有効としておく必要がありました。
仕様が定まりきっていないところもあるので、この指定が無くても文字列指定単体で
もできるようにしていこうと思います。

あと(?#maxlines:32)(?#fulllinematch)は秀丸エディタによって取り除かれ正規表現
DLLに渡るときには無いので、そもそもキャッシュされても効果はありませんでした。
除去する具合を自動で一致させるようにします。
他には実際の検索で大文字/小文字を区別しないときにも、秀丸エディタによって小
文字に変換されたものが正規表現DLLに渡るので、そこも一致させておく必要があり
ます。
わかりやすい重い正規表現があれば判別可能ですが、重くないと判別できないので、
無駄になる可能性もあって、文字列指定はちょっと扱いが面倒かもしれません。
できるだけ気にせずできるようにしていきます。

[ ]
RE:09754 正規表現の部分式を宣言しただけNo.09755
fzok4234 さん 22/02/10 09:19
 
改善の検討ありがとうございます。


[ ]