JavaScript Diary

コードフォーマッターを作成したい! [ 2003/04/23 ]

Eclipseを使ってると,[Ctrl+S]と同じ回数だけ,[Ctrl+Shift+F]を押していることに気づく.
・・・整形機能の虜になってしまっている ^^;

ふと,(JavaScriptで)JavaScriptのコードフォーマッターを作りたい衝動に駆られ,自分の後学のためにも挑戦してみたものの,これがなかなか難しい ^^;
思うように先に進まないので,全然途中だけど公開してみる.

ここでコードフォーマッターを作成するのに最低限必要な機能は以下のようになるかと思います.

1.ソースを読み込み,トークンに切り分ける機能.
2.トークンを読み出し,ソースの構造を解析する機能.ここで構文にエラーがないかなども調べる.
3.構造から,コードを整形する機能

実際はこの3つを同時進行で行うのですが,
このうち1が不具合含みながらも,かろうじて完成したので紹介します.
プログラムソースコードをトークンに分割

実行するとトークンを切り出してくれます.
ここでトークンとは"+"や"-",JavaScriptの予約語,識別子に加え,プログラム実行には関係のないコメントや空白なども含みます,
↑では分かりやすいように'■'をセパレータにして表示しています.
尚,構文エラーなどのチェックはしません.単純にトークンに分けるだけです.
但し,「コメント/*があるのに閉じてない」や「数値リテラルが不正(0yd0,1.0eff)」などのトークンに関するエラーは出ます.
クラス構成は以下.

Symbols : トークンを表すIDの変数をまとめたクラス.
KeywordScanner : JavaScriptの予約語(this, if, for...)を探すクラス.
Scanner Classが長くなりすぎたので分けただけ^^;
後々にはScanner Classにまとめる予定.
Scanner : ソースをトークンに切り分けるメインクラス.

Scannerクラスは構文エラーのチェックは行いません.
以下のようなコードでもきちんと(?)トークンに分けます.それが仕様です

var a = +-"text";

明らかにエラーですが,これは[var][a][=][+][-]["text"][;]という風に分けられます(空白部除く).

さて,Scannerクラスには不具合があって,それは正規表現リテラルに関するものです.これが辛い.現状ではScannerクラスは正規表現リテラルをトークンと解釈することが出来ません.正規表現リテラルの定義は以下のようになっています(http://jt.mozilla.gr.jp/より引用).

正規表現リテラルはすぐ後ろにスラッシュ (/) の続かないスラッシュで始まる (2つのスラッシュは1行コメントを表す)。JavaScript 1.5 のように、正規表現リテラルは除算 (/) や 除算代入 (/=) の各トークンと区別が付かない。/ や /= が構文的文法によって次のトークンとして妥当な場合はレクサはこれらを除算、除算代入として扱う。そうでなければ / や /= は正規表現リテラルの始まりとして扱われる
もう面倒臭いー.
正規表現リテラルを認識するためには,構文エラーもチェックしなくちゃいけないんです.Scannerクラスは構文エラーはチェックしないと決めてしまったので,正規表現リテラルが認識出来ない,でも,認識しないといけない.こんな葛藤が存在するんです.だって,「妥当な場合」ってのが,(今の段階では)よく分からない.

つうことでとりあえず,正規表現に関する不具合は無視して先に進んでます.

作成してて思ったのはfunctionリテラルの存在.これがJavaScriptの構文解析をかなり,複雑にしている.
for文1つ取っても,for (i;e;a)のようにi, e, aと3つのブロックに分けようと思った場合, JavaやC++では単純に「トークン';'」の位置を調べればよいのに対し,JavaScriptでは↓のようなトリッキーなコードもエラーなしで解析しなくてはならない.

for (var i = function(){
        var i;
        for (i = 0; true; i++) {
            ...
        };
        return i;
    }(); i < 5; i++) {
    ...
}

完成はまっだまだ先になりそうだ・・・.

完全に余談扱いですが,現状は以下.
コードフォーマッター 完成度2%
尚,コードは速度重視であることと,将来的にJava言語に移植したいのでJavaScript特有の書き方は出来るだけしていません.