6

我正在尝试编写一个允许非常通用代码生成的 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,因此插件需要在两者之间进行转换。

4

1 回答 1

9

您面临的实现困难部分是 2.10 中的宏不够通用的原因。它们看起来非常具有挑战性,甚至是根本性的,但我乐观地认为它们最终会被击败。以下是一些棘手的设计问题:

1)你怎么知道你调用的函数是正确的insertTree?如果用户编写了自己的名为的函数insertTree怎么办?那么您如何区分对您的特殊函数的魔术调用和对用户定义函数的正常调用?为确保您需要对函数的引用进行类型检查。但这并不容易(见下文)。

2)您如何准确评估createFooTree(...)电话?和以前一样,您需要对createFooTree部件进行类型检查以找出它代表什么,这并不容易。

3) 然后还有一个问题。如果createFooTree在您当前正在编译的文件之一中定义了怎么办?然后,您需要以某种方式将它及其依赖项与程序的其余部分分开,将其放入不同的编译运行中,编译然后调用它。然后,如果函数或这些依赖项之一的编译导致宏扩展,这应该会改变编译器的某些全局状态。我们如何将它传播到程序的其余部分?

4)我一直在谈论类型检查。那是问题吗?显然,是的。如果你的宏可以在任何地方扩展成任何东西,那么类型检查就变得非常棘手。例如,你如何检查这个:

class C {
  insertTree(createFoo(bar)) // creates `def foo = 2`, requires `bar` to be defined to operate
  insertTree(createBar(foo)) // creates `def bar = 4`, requires `foo` to be defined to operate
}

5) 不过,好消息是您不必使用scala.reflect.runtime.universe.Tree. 您可能已经createFooTree依赖类型:def createFooTree[U <: scala.reflect.api.Universe with Singleton](u: Universe): u.Tree. 这个,或者scala.reflect.macros.Context我们在 Scala 2.10 中使用的方法。不是很漂亮,但解决了宇宙不匹配的问题。

作为底线,我目前的感觉是静态类型语言中的宏(尤其是在面向对象的语言中,因为 OO 为代码片段之间的相互依赖带来了许多惊人的方式)真的很棘手。尚未发现用于修改正在编译的程序中的任意片段的类型宏的健壮模型。

如果您希望我们可以通过电子邮件进行更详细的讨论。我们还可以合作以实现适当宏的想法。或者,如果您可以分享您的用例,我可以尝试帮助您找到适合您特定情况的解决方法。

于 2013-03-14T13:33:35.030 回答