Groovy の AST (Abstract Syntax Tree) の勉強をはじめた。
学生時代にプログラミングの意味論 *1 や型理論 *2 を勉強していたので、「抽象構文」とか「具象構文」というキーワードが気分にささったものと思う。
とりあえず Groovy サイトの次のページを読み始めた。
2.2. Developing AST transformations
http://groovy-lang.org/metaprogramming.html#developing-ast-xforms
難解である。(=_=;)
このページと Groovy API の JavaDoc でわかったことをまとめる。
(1)Global AST Transformation (大域的AST変換)と Local AST Transformation (局所的AST変換)の二つがある。
局所的…というのは、AST変換の対象となる箇所(クラス定義・メソッド定義など)にアノテーションでマークをつけるやりかたのようだ。これに対して大域的の方はマークが不要らしい。
そういえば、深く考えず "@Field" アノテーションはよく使っていた。
import groovy.transform.Field @Field def counter=0 def foo () { counter++ } def bar () { counter + 25 }
上のように書いておくと、変数 counter がクラスのフィールド変数になってくれるので、複数のメソッド foo,bar から参照することができる(@Field がないとエラーになる)。知らずに局所的 AST 変換を使っていたのだ。
(2)AST変換は org.codehaus.groovy.control.CompilePhase で定義される9つのコンパイラフェーズのどこかで行う。
直感的には、ASTがトークンツリーから作成される CONVERSION と呼ばれるフェーズ以後に注目すればよいのではないのかと思った。で、AST変換は一か所だけなのか?複数フェーズで何度でもできるのか?異なるAST変換を同時に行えるのか?などはよくわからなかった。
さらに読み続けると少し具体的な話が載っていた。
(3)AST変換のクラスは、どのフェーズでAST変換を行うかをアノテーションで指定し、 org.codehaus.groovy.transform.ASTTransformation インターフェースを実装すること
// CONVERSIONフェーズを指定 @GroovyASTTransformation(phase=CompilePhase.CONVERSION) class Foo implements ASTTransformation { ... public void visit(ASTNode[] nodes, SourceUnit sourceUnit){ ... } ... }
visit メソッドに渡される sourceUnit が元のソースコードに対応するオブジェクト。sourceUnit.getAST() で取得できる org.codehaus.groovy.ast.ModuleNode クラスのインスタンスが AST を表現していて、次のような情報にたどり着く。
クラスのリスト : sourceUnit.getAST().getClasses() → List<ClassNode>
メインクラス名:sourceUnit.getAST().getMainClassName() → String
(4)ASTは以下のクラス(およびそのサブクラス)のノードで構成されている。
クラス org.codehaus.groovy.ast.ClassNode フィールド org.codehaus.groovy.ast.FieldNode メソッド org.codehaus.groovy.ast.MethodNode ステートメント(文) org.codehaus.groovy.ast.stmt.Statement エクスプレッション(式) org.codehaus.groovy.ast.expr.Expression
(5)ASTのツリー構造をトラバースするには以下2種類のインターフェースを実装する。
org.codehaus.groovy.ast.GroovyClassVisitor
→ ノード系 ClassNode,FieldNode,MethodNode,PropertyNode,ConstructorNode
org.codehaus.groovy.ast.GroovyCodeVisitor
→ コード系 Expression,Statement
ここまでで、ASTTransformation を実装すれば、AST のツリー構造をたどれることがわかった。
でも、もっと簡単に Groovy のコードをコンパイルし、AST を取得するやり方はないものか?
調べてみた。というか、困ったときの Google 先生頼みである。
次のようなコードが見つかった。
// sample of SourceUnit and CompilationUnit def name = "Test" def source = "println \"hello, world\"" def sourceUnit = SourceUnit.create(name, source) def compUnit = new CompilationUnit() compUnit.addSource(sourceUnit) compUnit.compile(CompilePhase.CONVERSION.phaseNumber) def ast = sourceUnit.getAST()
SourceUnit と CompilationUnit のシンプルな使い方がわかった。上のコードに続けて compUnit.compile() すると最終フェーズまでコンパイルが進み、Test.class が出力される。
ここまで学んだことを踏まえて、クラス定義の AST のツリー構造を一枚の図で可視化してみたい。*3
次回は下のような実装をしてみる:
・入力となるソースコードをコンパイルする。
・メインクラスの ClassNode を取得する。
・取得した ClassNode のツリー構造を画像データにレンダリングする。
*1:"Structual Operational Semantics" 構造的操作意味論を中心にやってた。こんな本とか→"The semantics of programming languages: an elementary introduction using structural operational semantics", Matthew Hennessy, John Wiley & Sons Ltd 1990. ISBN 978-0471927723
*2:Robin Milber の再帰関数のタイピングとか刺激的だった。ちょっと違うけど、ML の本→"Commentary on Standard ML", Robin Milner, Mads Tofte, MIT Press 1997. ISBN 0-262-63137-7
*3:GroovyConsole の Ctrl-T で起動する AST Browser で簡単に AST のツリー構造を確認することができることを知ったのはこのずっと後だった…