Compiler Pluginを作ってみた
なにげなくScalaのコンパイラのソースを眺めていたときに見つけたコード
// src/compiler/scala/tools/nsc/util/trace.scala object trace { def apply[T](msg: String)(value: T): T = { println(msg+value) value } // ... }
なんてことないけど気の効いたコードだ。このコードいいな、とつぶやいたら
という返事があった。おぉlazy val、その発想はなかった!
どうせなら全てのlazy valにStackTrace出力を埋め込むちょっとウザいCompiler Pluginを作ってみよう思い立った。
ところでCompiler Pluginってなによ?
Compiler Pluginへの入門はこの記事が参考になると思う。
http://blog.takeda-soft.jp/blog/show/399
簡単に言うとコンパイルの各フェイズの間でちょっかいを出すコードが書けてしまうすごい機能。
(「lispのマクロに対するOdersky先生の回答だ」みたいなこと言ってた人もいたような気がする)
上の記事ではゼロ除算をコンパイル時に検出するPluginが解説されていたが、Compiler Pluginではコンパイル時に抽象構文木(AST)そのものをいじってしまうこともできる。
そういうPluginの例としてはNonnull-check generatorが紹介されていた。
作ってみた
とりあえず
lazy val hoge = new Hoge()
みたいなのを
lazy val hoge = { Thread.currentThread.getStackTrace foreach println; new Hoge()}
に変換するだけの簡単なCommpilerPluginを作ってみた。
唯一めんどくさそうなのが付加するコードをASTにするところだが、これは[twitter:@xuwei_k]さんが紹介してくれたMkTreeを使えば簡単に得られる。
余談
ここまで「Compiler Pluginって簡単に作れるよ」的な説明をしてきたけど、実際はここに辿りつくまでに大変時間がかかった。というのも何を間違えたかCompiler Pluginを挿し込む場所が、上のコードではparserフェイズの後になっているが、当初typerフェイズの後にしていたからだ。
せっかつくったのでそのコードも載せてみる。
先に挙げたコードとまったく同じ事をしているわけだが、やたら長い。
そしてAST作るのだけでも面倒なのに、さらに各Treeに手で型付けするという苦行(笑。
よっぽどのことがない限り
val runsAfter = List[String]("parser")
をおすすめします。