「前のエントリで書き忘れてた - 最速チュパカブラ研究会」にて、Narcissus で使われている正規表現が参考になるという話が出ています。
/^"(?:\\.|[^"])*"|^'(?:[^']|\\.)*'/
/^\/((?:\\.|[^\/])+)\/([gimy]*)/
/^\/(?:\*(?:.|\n)*?\*\/|\/.*)/
一流の人が書いたものを使いましょう
というのに異を唱えるつもりはありませんが、そのままコピー & ペーストしていては意味がありません。ここはやはり一文字一文字心をこめて写経しましょう……ではなく、どうしてその書き方でうまくいくのかをきちんと考えた上で使いましょう。
上記の文字列リテラルを表す正規表現から、一重引用符でくくられた文字列にマッチする部分だけを抜き出すと '(?:[^']|\\.)*'
となります。途中 \\.
とあるのはエスケープシーケンスを許容するためですね。ではエスケープシーケンスを含む文字列 'It\'s sunny.' が、この正規表現パターンとどうマッチしていくのか見ていきましょう。
'
とマッチする。[^']
とマッチする。[^']
とマッチする。[^']
とマッチする。[^']
とのマッチに失敗する。\\.
とのマッチに失敗する。'
とマッチする。マッチは成功しましたが、得られた文字列は 'It\' だけです。エスケープシーケンスを適切に処理できていません。
実はこの正規表現、文字列リテラルにマッチするという目的からすると間違っています。すなわち、Narcissus のバグというわけです。エスケープシーケンスを正しく処理するためには、グループ内の選択の順番を入れ替えて '(?:\\.|[^'])*'
としなくてはいけません。
これで大丈夫かと思いきや、まだ落とし穴があります。'It\'s sunny. という終端のない文字列とマッチする場合を考えて見ましょう。この場合、最終的には文字 ' がパターン '
と、文字 I、t、\ がパターン [^']
と、文字 ' がパターン '
とマッチしてマッチが終了します。不正な形式の文字列に対してもマッチ成功となってしまうのです。
しかし、この問題は Narcissus では表面化しません。Narcissus ではマッチした文字列から文字列値を取得するために eval 関数を使っているからです。'It\' という文字列を eval 関数で解析すれば、当然終端のない文字列としてエラーが出ます。そして 'It\' のような文字列がマッチするのはそもそも入力にエラーが含まれていたときだけです。エラーを含む入力があったときにエラーになるという、ごく当然の結果になるのです。
なお、不正な文字列に対してはマッチが失敗してほしいというときは、パターンを '(?:\\.|[^'\\])*'
とする必要があります。さらに、一般的な入力にはエスケープシーケンスでない文字のほうが多く含まれると考えられるので、これは '(?:[^'\\]|\\.)*'
と選択の順番を入れ替えたほうが実行速度が上がるような気がします。しかし、なぜか SpiderMonkey では前者に対して後者のほうが約 60% も遅く、JScript でも両者の実行時間はほぼ同じです。
JavaScript 1.5 までは /[/]/
という入力は /[/
セコメントをする