我正在尝试编写一个允许非常通用代码生成的 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(...))
重要的插件代码可能如下所示(基于this):
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 中,它们还不够通用。
顺便说一句,我发现我概述的方法存在两个问题:1)编译器插件采用 AST,而不是闭包。它需要某种创建闭包的方法,可能会添加对用户代码的构建依赖项。2)用户无权访问 scala.reflect.internal.Trees.Tree,只能访问 scala.reflect.runtime.universe.Tree,因此插件需要在两者之间进行转换。