最近JavaScriptばかり書いていたためか無性にJVMと戯れたくなったので、KEMURIの実行環境を作成しました。
単なるインタプリタとJavaのclassファイルを生成するコンパイラがあります。インタプリタは以前実装したことがあるのですが、コンパイラは今回が初めてです。
コンパイラ実装に使用したのはお馴染みのASMです。ASMはJRubyをはじめとした様々なJVM言語で使われているので、こうした用途はもともと得意とする領域でしょう。バージョン4を使いたかったのですが、まだMavenセントラルリポジトリに上がっていないため3.3.1で妥協しました。Java7の新機能を使いたいわけではないですし。
最初はKEMURIがスタックマシンであることを利用して「KEMURIのスタック=JVMのオペランドスタック」ができないかと思ったのですが、出力時のループでVerifyError(inconsistent stack height)が出てしまってダメでした。JVMは条件分岐の経路によってオペランドスタックの高さが変わることを許さないのですよね。素直にDequeを作っています。
コンパイラと言ってもやっていることは単純で、例えばHelloWorldをコンパイルすると以下のようなmain()を含むクラスを吐き出します。このクラスをjava Hello
のように実行すれば、期待通りの結果が出力されるというわけです。この単純さは構文解析が無いことに起因するのでしょうか、それとも単に機能の少なさでしょうか。
public static void main(String[] args) { Deque stack = new ArrayDeque(); hello(stack); print(stack); }
ちなみに、久々にGOTOを使いました。ちょうなつかしい。Windowsバッチとかではまだまだ現役ですが、他の言語ではほとんど見なくなりましたよね。そのうちサブルーチンがいかに素晴らしいか若手に語って聞かせる日が来るんでしょうか。いや、もう来ているのか……?