Groovy で AST 変換 II: GroovyConsole の使い方

## 初めに
[前回の記事](http://qiita.com/Ziphil/items/680b63b2d31aa952e1c2)で、二項演算子 `/` をメソッド `intdiv` に変換する AST 変換を作成しました。このとき、以下のようなコードを書いたのですが、完成品だけ見せられてもなぜそのようなコードになったのかが分からないので、このコードが産まれるまでの経緯をまとめておこうと思います。

```groovy
public Expression transform(Expression expression) {
  if (expression != null) {
    // AST が二項演算子の呼び出しである場合
    if (expression instanceof BinaryExpression) {
      // 二項演算子を表すトークンを取得
      Token operation = expression.getOperation()
      // 二項演算子の名称が「/」である場合
      if (operation.getText() == "/") {
        // 左側の AST を取得
        Expression left = expression.getLeftExpression()
        // 右側の AST を取得
        Expression right = expression.getRightExpression()
        // 左側の AST に対して再帰的に変換を行う
        Expression nextReceiver = transform(left)
        // 右側の AST だけからなる引数リストを作成して再帰的に変換を行う
        Expression nextArguments = transform(new ArgumentListExpression(right))
        // intdiv メソッド呼び出しの AST を作成する
        Expression nextExpression = new MethodCallExpression(nextReceiver, "intdiv", nextArguments)
        return nextExpression
      }
    }
    return expression.transformExpression(this)
  } else {
    return null
  }
}
```

## GroovyConsole の利用

### 基本的な使い方

AST 変換を実装するには、変換前の AST と変換後の AST がどのような構造をしているかを把握する必要があります。GroovyConsole を使うことで、Groovy コードのどの部分がどのような AST になるかを調べることができます。

GroovyConsole は、Windows であれば、Groovy がインストールされているディレクトリ内の bin/groovysh.bat をクリックすることで起動することができます。起動すると、上下に 2 つに分かれたウィンドウが表示されます。上がテキスト入力画面で、下が実行結果が表示される画面です。Groovy コードを入力してメニューから Script → Run (もしくは Ctrl＋R キー) でコードを実行できます。

さて、GroovyConsole には AST ブラウザが内蔵されていて、Groovy コードがどのような AST で表現されるかを調べることができます。メニューから Script → Inspect Ast (もしくは Ctrl＋T キー) で AST ブラウザを開くことができます。

### 二項演算子 `/` の AST の調査

さて、AST 変換 `@Intdiv` の実装には、`7 / 2` のような `/` の呼び出しがどのような AST で表現されるかを調べる必要があります。これを GroovyConsole で調べてみましょう。

GroovyConsole を開き、コード入力画面に以下のコードを入力してください。

```groovy
class Test {

  public static void main(String... args) {
    7 / 2
  }
  
}
```

その後、AST ブラウザを開いてください。

この AST 変換は、変換の 8 つの段階のうち Semantic Analysis という段階で行うよう設定した (`@GroovyASTTransformation` で設定した) ので、AST ブラウザの「At end of Phase」の部分を「Semantic Analysis」にします。ツリービューに「ClassNode - Test」という要素が表示されるので、それを辿って `7 / 2` に当たる部分の AST を見てみます。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/151402/0d13079f-37a7-3f2a-d716-24aee6c4428a.png)

ツリービューの右側に選択したオブジェクトのプロパティ情報が表示されます。ここの「class」という項目を見ることで、二項演算子の呼び出しは `BinaryExpression` クラスで表現されることがわかります。「operatrion」という項には「("/" at 4:7: "/")」と表示されていて、おそらくこれが何の二項演算子であるか (`+` なのか `/` なのか) の情報なのでしょう。また、「leftExpression」という項を見ると 「ConstantExpression[7]」と表示されており、どうやら二項演算子の左側に書かれた式がここに格納されているらしいことが分かります。同様に、「rightExpression」は右側に書かれた式であることが分かります。

さて、これで必要な情報が揃いました。まず、`transform` メソッドに渡された AST が `BinaryExpression` クラスのオブジェクトかどうか判定すれば良いわけです。もしそうであったら、`operation` プロパティにアクセスすることで、何の演算子なのかの情報が得られます[^1]。ただ、`operation` プロパティは `Token` オブジェクトになっていて、このクラスの扱いがよく分からないので、[`Token` のGroovyDoc](http://docs.groovy-lang.org/latest/html/gapi/org/codehaus/groovy/syntax/Token.html) を見ることにしましょう。すると、`getText` というメソッドがあることが分かるので、これを用いて演算子が `/` であるか判定することにします。

演算子が `/` であると判定された場合は、`intdiv` メソッドの呼び出しのために `/` の左右に書かれた式を取得しておく必要があります。それは `leftExpression` プロパティと `rightExpression` プロパティでしたね。

ということで、ここまでで以下のコードが書けました。

```groovy
public Expression transform(Expression expression) {
  if (expression != null) {
    // AST が二項演算子の呼び出しである場合
    if (expression instanceof BinaryExpression) {
      // 二項演算子を表すトークンを取得
      Token operation = expression.getOperation()
      // 二項演算子の名称が「/」である場合
      if (operation.getText() == "/") {
        // 左側の AST を取得
        Expression left = expression.getLeftExpression()
        // 右側の AST を取得
        Expression right = expression.getRightExpression()
        
        // intdiv メソッドの呼び出しに対応する AST の生成をここに書く

      }
    }
    return expression.transformExpression(this)
  } else {
    return null
  }
}
```

### `intdiv` メソッドの呼び出しの AST の調査

さて、次は変換後の `intdiv` メソッドの呼び出しがどのような AST で表現されるか調べます。

GroovyConsole の入力画面に以下のコードを入力してください。その後、AST ブラウザの右上にある「Reflesh」ボタンをクリックするか F5 キーを押すと、表示が更新されます。

```groovy
class Test {

  public static void main(String... args) {
    7.intdiv(2)
  }
  
}
```

`7.intdiv(2)` の AST を見てみましょう。

![2.png](https://qiita-image-store.s3.amazonaws.com/0/151402/8663f17d-803b-eaec-6659-bc868383b333.png)

「class」の項目を見ると、メソッド呼び出しを表現するクラスが `MethodCallExpression` であることが分かります。そこで [`MethodCallExpression` の Groovydoc](http://docs.groovy-lang.org/latest/html/gapi/org/codehaus/groovy/ast/expr/MethodCallExpression.html) を見てみると、コンストラクタが 2 種類あることが確認できます。作りたい AST のメソッドの名前は「intdiv」で固定なので、1 番目の `MethodCallExpression(Expression, String, Expression)` を使うことにします[^2]。したがって、以下のようにすれば良さそうです。

```groovy
public Expression transform(Expression expression) {
  if (expression != null) {
    // AST が二項演算子の呼び出しである場合
    if (expression instanceof BinaryExpression) {
      // 二項演算子を表すトークンを取得
      Token operation = expression.getOperation()
      // 二項演算子の名称が「/」である場合
      if (operation.getText() == "/") {
        // 左側の AST を取得
        Expression left = expression.getLeftExpression()
        // 右側の AST を取得
        Expression right = expression.getRightExpression()
        // intdiv メソッド呼び出しの AST を作成する
        Expression nextExpression = new MethodCallExpression(left, "intdiv", right)
        return nextExpression
      }
    }
    return expression.transformExpression(this)
  } else {
    return null
  }
}
```

これでも動くのですが、Groovy が生成する AST では、メソッド呼び出しの引数は全て `ArgumentListExpression` オブジェクトになっているので、それに倣ってここでも `ArgumentListExpression` オブジェクトにしてコンストラクタに渡そうと思います。[`ArgumentListExpression` のGroovydoc](http://docs.groovy-lang.org/latest/html/gapi/org/codehaus/groovy/ast/expr/ArgumentListExpression.html) を見るとコンストラクタがたくさんありますが、その中で `Expression` オブジェクトを 1 つだけとるものがちょうど良さそうです。これを使ってみます。

```groovy
public Expression transform(Expression expression) {
  if (expression != null) {
    // AST が二項演算子の呼び出しである場合
    if (expression instanceof BinaryExpression) {
      // 二項演算子を表すトークンを取得
      Token operation = expression.getOperation()
      // 二項演算子の名称が「/」である場合
      if (operation.getText() == "/") {
        // 左側の AST を取得
        Expression left = expression.getLeftExpression()
        // 右側の AST を取得
        Expression right = expression.getRightExpression()
        // 右側の AST だけからなる引数リストを作成する
        Expression nextArguments = new ArgumentListExpression(right)  // ← ここ!
        // intdiv メソッド呼び出しの AST を作成する
        Expression nextExpression = new MethodCallExpression(left, "intdiv", nextArguments)
        return nextExpression
      }
    }
    return expression.transformExpression(this)
  } else {
    return null
  }
}
```

これで一通り完成しましたね。

### `transform` の再帰呼び出しはしっかりと

実は、上のコードのままではうまく行きません。なぜかというと、このままでは `left` や `nextArguments` に対して `transform` メソッドが呼び出されないので、例えば `(7 / 2) / (5 / 3)` のようなネストした部分があると、一番外側しか `intdiv` に変換されません。そこで、しっかり `transform` を呼ぶようにしましょう。

```groovy
public Expression transform(Expression expression) {
  if (expression != null) {
    // AST が二項演算子の呼び出しである場合
    if (expression instanceof BinaryExpression) {
      // 二項演算子を表すトークンを取得
      Token operation = expression.getOperation()
      // 二項演算子の名称が「/」である場合
      if (operation.getText() == "/") {
        // 左側の AST を取得
        Expression left = expression.getLeftExpression()
        // 右側の AST を取得
        Expression right = expression.getRightExpression()
        // 左側の AST に対して再帰的に変換を行う
        Expression nextReceiver = transform(left)  // ← ここ!
        // 右側の AST だけからなる引数リストを作成して再帰的に変換を行う
        Expression nextArguments = transform(new ArgumentListExpression(right))  // ← ここ!
        // intdiv メソッド呼び出しの AST を作成する
        Expression nextExpression = new MethodCallExpression(nextReceiver, "intdiv", nextArguments)
        return nextExpression
      }
    }
    return expression.transformExpression(this)
  } else {
    return null
  }
}
```

これで完成です。

自前で新しく AST を生成したら `transform` を呼ぶのを忘れないでください。

### 作った AST 変換を試してみる

GroovyConsole のメニューから Script → Add Jars to Classpath や Script → Add Directory to Classpath を選択することで、クラスパスに jar ファイルやディレクトリを追加することができます。これを用いて、作成した AST 変換を GroovyConsole 上で試すことができます。

GroovyConsole の AST ブラウザは、AST の構造を表示するだけではなく、変換された AST を通常の Groovy コードの形でも表示してくれます。これによって、意図した通りに変換が行われているかを一目で確認することができます。

例えば、今回作成した `@Intdiv` のクラスファイルをクラスパスに追加した状態で、GroovyConsole に以下を入力してみます。

```groovy
import ziphil.transform.Intdiv

@Intdiv
class Test {

  public static void main(String... args) {
    println(7 / 2)
  }

}
```

ここで AST ブラウザを開くと、ウィンドウ下部に以下のように表示されるはずです。「At end of Phase」の部分は「Semantic Analysis」にしています。

```groovy
import ziphil.transform.Intdiv as Intdiv

@ziphil.transform.Intdiv
public class Test extends java.lang.Object { 

    public static void main(java.lang.String[] args) {
        this.println(7.intdiv(2))
    }

}
```

しっかり `intdiv` に変換されているのが分かりますね。

## このシリーズ

- [Groovy で AST 変換 I: AST 変換を作ってみよう](http://qiita.com/Ziphil/items/680b63b2d31aa952e1c2)
- Groovy で AST 変換 II: GroovyConsole の使い方
- [Groovy で AST 変換 III: Tips など](http://qiita.com/Ziphil/items/5cf8904fdea599ac8047)

[^1]: ここでは `getOperation` メソッドを呼び出して `expression.getOperation()` と書いていますが、Groovy なら `expression.operation` でも OK です。
[^2]: 2 番目のコンストラクタを用いると、メソッド名も式にすることができます。メソッドの動的呼び出しです。