Groovy AST 変換は予め規定された 9 のフェーズのいずれか実行される。
グローバル変換はどのフェーズでも適用される可能性がある。しかしローカル変換は意味解析フェーズよりも後でしか適用されない。 各フェーズを簡単に説明すると次のようになる。
- 初期化: ソースコードファイルを開いてコンパイルのための環境が整った
- 解釈: ソースコードを表現するトークン木を生成するための文法規則が使われた
- 変換: トークン木から抽象構文木 (AST) を生成する
- 意味解釈: 文法規則でチェックできない一貫性チェックや妥当性チェックを行う。あとクラスを解決する
- 正規化: AST の構築を完了する
- 命令セットの選択: java5 や pre java5 のような命令セットを選択する
- バイトコード生成 (メモリ): メモリ中にクラスのバイナリ表現を生成する
- 出力: メモリ中に生成したバイトコードをファイルシステムに出力する
- 終了処理: 後始末をする
一般的に後のフェーズのほうが利用できる型情報は多いと言われている。 あなたの実装した AST 変換の目的が AST を読むことだったら後のほうのフェーズで適用するほうが情報が多くていいだろう。 AST を操作することが目的だったら構文木が比較的疎である前のほうのフェーズで適用するほうが何かと都合がいいだろう。
ちょっとしたサンプルとして Groovy が static なプロパティをアグレッシブかつ statically に型付けするところをお見せしよう。
class Foo {
static doLog() { log.info("This won't even compile!") }
}
Foo.metaClass.static.log = [info:{ println it }] // Intended to enable the above code to work (but doesn't work)
/*
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, /Users/robert/dev/workspace/Groovy-WithLog/test/Bogus.groovy: 2: Apparent variable 'log' was found in a static scope but doesn't refer to a local variable, static field or class. Possible causes:
You attemped to reference a variable in the binding or an instance variable from a static context.
You mispelled a classname or statically imported field. Please check the spelling.
You attempted to use a method 'log' but left out brackets in a place not allowed by the grammar.
@ line 2, column 19.
static doLog() { log.info("This won't even compile!") }
^
1 error
*/
(案の定コンパイルエラーになった!)
AST 変換で static プロパティを足したければ上記のようにチェックされる前に割り込まないといけない。 具体的には変換フェーズに割り込めばよい (エラーになったのは意味解釈フェーズなのだ)。 前のほうのフェーズでは型は解決されていないだろうから、型の解決が大事だというなら手動で行わなければならないだろう (例えば特定のクラスのアノテーションをチェックするだとか) 。
それぞれのフェーズで生成される AST について詳しく知りたければ Groovy console の AST viewer を使うとそれぞれのフェーズでの出力を確認できる。