私は非常に一般的なコード生成を可能にする Scala コンパイラ プラグインを書こうとしています: C プリプロセッサの一般性に似ていますが、もう少しタイプセーフです (これがひどいアイデアかどうかはわかりませんが、楽しい演習です)。 )。私の理想的なユースケースは次のようになります。
// User code. This represents some function that might take some args
// and outputs an abstract syntax tree.
def createFooTree(...): scala.reflect.runtime.universe.Tree = ...
// Later user code (maybe separate compilation?). Here the user generates
// code programmatically using the function call to |createFooTree| and inserts
// the code using insertTree.
insertTree(createFooTree(...))
重要なプラグイン コードは次のようになります (これに基づく):
class InsertTreeComponent(val global: Global)
extends PluginComponent
with TypingTransformers {
import global._
import definitions._
override val phaseName = "insertTree"
override val runsRightAfter = Some("parser")
override val runsAfter = runsRightAfter.toList
override val runsBefore = List[String]("typer")
def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
def apply(unit: CompilationUnit) {
val onTransformer = new TypingTransformer(unit) {
override def transform(tree: Tree): Tree = tree match {
case orig @ Apply(
function,
// |treeClosure| is the closure we passed, which should
// evaluate to a Tree (albeit a runtime Tree).
// The function.toString bit matches anything that looks like a
// function call with a function called |insertTree|.
treeClosure) if (function.toString == "insertTree") => {
// This function evaluates and returns the Tree, inserting it
// into the call site as automatically-generated code.
// Unfortunately, the following line isn't valid.
eval(treeClosure): Tree
}
...
これを行う方法はありますか?「マクロを使うだけ」とは言わないでください。少なくとも 2.10 では、十分に一般的ではありません。
ところで、私が概説したアプローチには 2 つの問題があります。1) コンパイラ プラグインは、クロージャではなく AST を取ります。おそらく、ユーザー コードにビルドの依存関係を追加して、クロージャを作成する何らかの方法が必要になります。2) ユーザーは scala.reflect.internal.Trees.Tree にアクセスできず、scala.reflect.runtime.universe.Tree にしかアクセスできないため、プラグインは 2 つの間で変換する必要があります。