uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

Groovyを勉強したい人に贈る、astprintコマンド

今ごろ言うな、って話かもしれませんが、自分で2年ほど前にこっそり作っていて最高に便利だと思っているコマンド「astprint」を紹介します。

astprintの内容は以下のようにシェルスクリプトです。

#!/bin/sh

groovy -e 'groovy.inspect.swingui.AstNodeToScriptAdapter.main(args)' $*

やってることは"こちら"で紹介したAstNodeToScriptAdapterを呼んでるだけです。以下ようにコンパイルの途中経過が標準出力にテキストで出力されるので簡単に見れます。本当便利です。

% cat hoge.groovy
cat hoge.groovy
trait FileNameIsHogeHoge {
    @groovy.transform.ForceOverride
    String getPath() {
        return "hogehoge"
    }
}

class MyFile extends File implements FileNameIsHogeHoge {
    MyFile(String fileName) {
        super(fileName)
    }
}

f = new MyFile("/tmp/file.txt")
assert f.getPath() == "hogehoge"

% astprint hoge.groovy 4
astprint hoge.groovy 4
f = new MyFile('/tmp/file.txt')
assert f.getPath() == 'hogehoge' : null
public class script1397699411969 extends groovy.lang.Script { 

    public script1397699411969() {
    }

    public script1397699411969(groovy.lang.Binding context) {
        super.setBinding(context)
    }

    public static void main(java.lang.String[] args) {
        org.codehaus.groovy.runtime.InvokerHelper.runScript(script1397699411969, args)
    }

    public java.lang.Object run() {
        f = new MyFile('/tmp/file.txt')
        assert f.getPath() == 'hogehoge' : null
    }

}
@groovy.transform.Trait
abstract interface public class FileNameIsHogeHoge extends java.lang.Object { 

    @groovy.transform.ForceOverride
    @org.codehaus.groovy.transform.trait.Traits$Implemented
    abstract public java.lang.String getPath() {
    }

}
public class MyFile implements FileNameIsHogeHoge extends java.io.File { 

    public MyFile(java.lang.String fileName) {
        super(fileName)
    }

}
abstract public static class FileNameIsHogeHoge$Trait$Helper extends java.lang.Object { 

    public static void $init$(FileNameIsHogeHoge $self) {
    }

    public static void $static$init$(java.lang.Class<FileNameIsHogeHoge> $static$self) {
    }

    @groovy.transform.ForceOverride
    public static java.lang.String getPath(FileNameIsHogeHoge $self) {
        return 'hogehoge'
    }

}

これで見るとtraitは@groovy.transform.Trait指定になってるんですねー。4はコンパイルのフェイズで、以下の意味があります。

数字 シンボル 説明
1 INITIALIZATION ファイルを開いたり
2 PARSING 字句・構文解析ANTLRのAST構築
3 CONVERSION CSTからASTへの変換
4 SEMANTIC_ANALYSIS ASTの意味解析と解明
5 CANONICALIZATION ASTの補完
6 INSTRUCTION_SELECTION クラス生成(フェーズ1)
7 CLASS_GENERATION クラス生成(フェーズ2)
8 OUTPUT クラスをファイルに出力
9 FINALIZATION 後始末
9 ALL 後始末まですべて実行

groovyConsoleでも同じ内容が表示できますが、コマンドラインからできるのも大変便利です。
traitについては7ぐらいにするともっと展開された結果になりますが、バイトコード生成レベルではなく、かなりソースコードに近い側で展開処理してるようで参考になます。