AST 変換 Tips
ClassNode 生成時は ClassHelper を使おう
コンストラクタや static なメソッドを呼び出す式に対応する AST を作ろうとすると、クラスを表す ClassNode オブジェクトが必要になりますが、このときにコンストラクタを呼び出すのは推奨されていないようです。代わりに、ClassHelper クラスの make メソッドを用いましょう。
例えば、String クラスを表す ClassHelper メソッドを用いるときには以下のようにします。
ClassNode type = ClassHelper.make(String)
与えられたクラス名から ClassNode オブジェクトを得たいときは、以下のようになります。
String typeName = "java.lang.String"
Class clazz = Class.forName(typeName)
ClassNode type = ClassHelper.make(clazz)
ちなみに、make メソッドは String を受け取るものと Class を受け取るものがありますが、String を受け取る方を用いるとエラーが発生することがあったので、Class を受け取る方を使ったほうが無難だと思います。
配列を表す ClassNode
ClassNode クラスの makeArray メソッドを使うと、そのクラスの配列を表す ClassNode オブジェクトが生成できます。便利です。
逆に、配列を表す ClassNode からその配列の成分のクラスを取得したい場合は、getComponentType メソッドが使えます。例えば、String[] を表す ClassNode に getComponentType メソッドを呼ぶと、String を表す ClassNode が返されます。
なお、多次元配列に対して getComponentType を呼ぶと次元が 1 つ落ちたものが返ってきます。例えば、String[][] を表す ClassNode に対しては String[] を表す ClassNode が返ってきます。一気に配列でないクラスを取得したい場合は、以下のようにすると良いでしょう。
ClassNode componentType = type
while (componentType.isArray()) {
componentType = componentType.getComponentType()
}
Variable の accessedVariable プロパティ
変数を表現する AST は Variable クラスで管理されます。ソースコード中に現れる変数は全て Variable オブジェクトになります。このとき、コード上の違う場所に書かれた変数は異なる Variable オブジェクトになります (lineNumber などが別々なので当然と言えば当然ですが)。一方、Variable クラスは accessedVariable というプロパティ (VariableExpression 型) をもっているのですが、これは同じ変数を表していれば同一のオブジェクトになります。
例えば、以下のようなコードを考えてみます。
if (true) {
int a = 2 // (1)
a += 3 // (2)
a = 7 // (3)
} else {
int a = 9 // (4)
a += 8 // (5)
}
「a」という名前の変数が 5 ヶ所現れていますが、これらは AST に変換されると Variable オブジェクトとしては別物です。ここで、(1), (2), (3) の a は同じ変数を表していますが、これとは別のスコープにある (4), (5) とは名前は同じでも別の変数です。したがって、各 Variable オブジェクトの accessedVariable プロパティを見てみると、(1), (2), (3) に対応するものは全て同一 (参照が同じ) の VariableExpression オブジェクトになっていて、(4), (5) に対応するものとは別の (参照が異なる) オブジェクトになっています。
変数の値を操作する AST を作る場合は、この辺にも注意する必要がありそうですね。
このシリーズ
- Groovy で AST 変換 I: AST 変換を作ってみよう
- Groovy で AST 変換 II: GroovyConsole の使い方
- Groovy で AST 変換 III: Tips など