S-JIS[2011-02-24] 変更履歴
Scala2.9.1のコンパイラプラグインのサンプル。
override def newPhase(prev: Phase) = new StdPhase(prev) {
override def name = selfPlugin.name
// プラグイン実行時に呼ばれるメソッド
override def apply(unit: CompilationUnit): Unit = {
printTree(unit.body) //全要素の巡回
newTraverser().traverse(unit.body) //全要素の巡回
printMethod(unit.body) //メソッド呼び出しを表示する例
warningPrintln(unit) //printlnがあったら警告にする例
}
}
tree.children(List[Tree])を再帰的に探索する例。
def printTree(tree: Tree, tab: Int=0) {
println("--" * tab)
printf("%s\t[%s][%s]%n", tree.getClass, tree.tpe, tree.symbol)
println(tree)
// for(t <- tree.children){ printTree(t, tab+1) }
tree.children.foreach{ printTree(_, tab+1) }
}
Treeを探索する為のTraverserを使う方法。(色々なTraverserがscala.tools.nsc.ast.Treesに用意されている)
def newTraverser(): Traverser = new ForeachTreeTraverser(printTraverse)
def printTraverse(tree: Tree) {
printf("%s\t[%s][%s]%n", tree.getClass, tree.tpe, tree.symbol)
}
Treeの具体的な構成要素はTreeを継承したクラスによって保持されている。
例えばpackage宣言はPackageDef、クラス定義はClassDef、変数定義はValDef(valもvarも)、メソッド定義はDefDef、メソッド呼び出しはApplyといった具合。
これらはケースクラスなので、unapplyメソッドが使える(パターンマッチが出来る)。
メソッド呼び出しはApplyというケースクラスで表現されている。
パターンマッチの第1引数はSelect(というTree)で、Selectはクラス名とメソッド名を保持している。
パターンマッチの第2引数はListで、メソッド呼び出しの引数。
マッチしたい条件が1種類しか無いならfor式、複数あるならTraverserを使うのが美しいかな?
def printMethod(tree: Tree) {
def print(tree: Tree): Unit = tree match {
case Apply(Select(obj, name), List(args @ _*)) =>
printf("%s#%s(%s)%n", obj, name, args.mkString(", "))
case _ => ()
}
new ForeachTreeTraverser(print).traverse(tree)
}
def printMethod(tree: Tree) {
for (Apply(Select(obj, name), List(args @ _*)) <- tree) {
printf("%s#%s(%s)%n", obj, name, args.mkString(", "))
}
}
>scalac -Xplugin:cplugin.jar hello.scala
Hello.super#<init>()
scala.this.Predef#println("hello")
※Hello.superの<init>は、object Helloのコンストラクター呼び出しと思われる。
Predef.println()の呼び出しがあったら警告を出力する例。
Selectで受け取れるオブジェクト名・メソッド名(下記のobjとname)はNameというクラスであり、Stringではない。
したがって、比較するためにはStringに変換する必要がある。
def warningPrintln(unit: CompilationUnit) {
for (tree @ Apply(Select(obj, name), _) <- unit.body) {
val oname = obj.toString
val mname = name.toString
if (oname == "scala.this.Predef" && mname == "println") {
unit.warning(tree.pos, "Predef.println() found")
}
}
}
>scalac -Xplugin:cplugin.jar hello.scala
hello.scala:3: warning: Predef.println() found
println("hello")
^
one warning found
ちなみに、Scala的にはfor式の中にフィルターで条件を書く方がいいのかな?
def warningPrintln(unit: CompilationUnit) {
for {tree @ Apply(Select(obj, name), _) <- unit.body
val oname = obj.toString
val mname = name.toString
if oname == "scala.this.Predef" && mname == "println"
} {
unit.warning(tree.pos, "Predef.println() found")
}
}