JavaScriptに色を付けるの1

さて、JavaScriptをネタに色を付けるのだが。

  1. コメントや文字列や正規表現など、複数の単語を包含し得る領域をまず最初に探し出して切り分ける。
  2. 残った部分に対して、予約語を探し出して切り分ける。
  3. 探し出したそれぞれに、その種別に応じた色を付けて再結合する。

昨日の夜に考えたこの方針は今も変わらないのだが、初段のコメント等々を切り出すのがちょっと面倒なんだよな。

まずは初段の対象となる各要素の定義だが、だいたいこんな感じ。

コメント

「/*」 から 「*/」 まで。 改行を含むことができる。

「//」 から行末まで。 定義から当然だが、改行を含まない。

文字列

「"」 で挟まれた領域。 ただしエスケープされた 「"」 は終端ではない。 実は改行を含むことができる。

「'」 で挟まれた領域。 ただしエスケープされた 「’」 は終端ではない。 実は改行を含むことができる。

「`」 で挟まれた領域。 ただしエスケープされた 「`」 は終端ではない。 普通に改行を含むことができる。

正規表現

「/」 で挟まれた領域。 ただしエスケープされた 「/」 は終端ではない。 空はない。 改行を含まない。

これだと連続する割り算も正規表現と認識してしまうのだが、その辺は後で考えることにする。

毎回 「コメントや文字列や正規表現」 というのが面倒なので、ここではこいつらをまとめてブロック要素と呼ぶことにして、問題は、ブロック要素の種類がいっぱいあること。 そしてそれぞれのブロック要素の中に、他のブロック要素と判断しうる形式の文字列があり得ること。

予約語の判定で楽をするためにブロック要素を先に取り出そうとしているのだが、そのブロック要素も、取り出す場所のコンテキストってものを考える必要があるのだった。 コメントの中の 「"」 は文字列の開始じゃないとか、そういう判断。

結局、頭から順に字句解析するしかないのか。 それは面倒だなぁ…。 なんてしばらく悩んで、、

  1. ブロック要素の全てで検索してみる。
  2. 最初に見つかるまでが最も短いものを正解として採用する。

とすれば良いことに気が付いた。

このやり方だと、

  1. 先頭から最初のブロック要素の前までは予約語などの検索対象とする。
  2. 見つけた最初のブロック要素は色付けのspanで囲む。
  3. 最初のブロック要素の後ろは、同じロジックの適用対象とする。

と、処理を綺麗に書けそうな気がするし。

ということで、まずはブロック要素だけを取り出して色を付ける処理を作ってみた。

const blockDefs = [ [ /^(.*?)(\/\*.*?\*\/)(.*)$/s , "comment" ], [ /^(.*?)(\/\/.*?)(\n.*)$/s , "comment" ], [ /^([^"]*?)("(?:\\.|[^"\\])*")(.*)$/s , "string" ], [ /^([^']*?)('(?:\\.|[^'\\])*')(.*)$/s , "string" ], [ /^([^`]*?)(`(?:\\.|[^`\\])*`)(.*)$/s , "string" ], [ /^([^\/]*?)(\/[^\/\*](?:\\.|[^\/\\])*\/)(.*)$/s , "regexp" ], ]; function createFinder( reg, cls ) { return text => { const matched = text.match( reg ); return matched ? { pre : matched[1], got : `<span class="${cls}">${matched[2]}</span>`, pst : matched[3], } : { pre : text, got : "", pst : "", } } } const blockFinders = blockDefs.map( d => createFinder( ...d ) ); const findBlock = text => blockFinders.map( f => f( text ) ) .reduce( ( r0, r1 ) => r0.pre.length < r1.pre.length ? r0 : r1 ); const stepParse = ( text, acc ) => { const r = findBlock( text ); if ( !r.got ) { return acc + r.pre; } return stepParse( r.pst, acc + r.pre + r.got ); } const setColor = () => { const elm = document.getElementById( "smpl01" ); elm.innerHTML = stepParse( elm.innerHTML, "" ); document.getElementById( "btn01" ).disabled = true; }

上のボタンをクリックすると、上記処理を上記ソースコード表示に適用して色を付ける。 まあ、コメントは一文字もない訳だが。

JavaScriptの記述が楽だからES2018仕様も使っているが、そのために今の時点では Safari でしか動かない。 最終的には手元のデバイス全てでちゃんと動くようにするのでIE11への対応もするつもりなのだが、一度楽を覚えると、面倒な世界には戻りたくないよなぁ。 この際だから敗北を認めて、トランスパイル環境を導入するか。 或いは別の敗北を認めて、IE11を切り捨てるか。

しかし、Safariはどうしちゃったんだろうね。 かつてはIEと並ぶ駄目な子だったのに、まさかSafariだけが最新実装で動く日が来るとは。

今日はここまで。 明日は予約語の色付けができるようにする。