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")
}
}