0

我一直在尝试编写一个 Scala (2.10.0) 编译器插件来分析遍历代码的某些部分。

这是我最初拥有的:

class MyPlugin (val global: Global) extends Plugin {
  import global._
  val name = "myPlugin"
  val components = List[PluginComponent](MyComponent)

  private object MyComponent extends PluginComponent {
    val global: MyPlugin.this.global.type = MyPlugin.this.global
    val runsAfter = List ("refchecks")
    val phaseName = "codeAnalysis"

    def newPhase (_prev: Phase) = new AnalysisPhase (_prev)

    class AnalysisPhase (prev: Phase) extends StdPhase (prev) {
      override def name = phaseName

      def apply (unit: CompilationUnit) {
        codeTraverser traverse unit.body
        printLinesToFile(counters.map{case (k,v) => k + "\t" + v},out)
      }

      def codeTraverser = new ForeachTreeTraverser (tree => /* Analyze tree */)
    }
  }
}

此代码按预期工作,但我不喜欢它,因为我无法将代码遍历器方法与此对象分离。我想编写一个单独的CodeTraverser类来对给定的树执行分析。除其他外,这可能会帮助我更好地测试此代码。

主要问题是unit.body内部的 Tree 类型scala.reflect.internal.Trees。如果我可以使用scala.reflect.api.Trees#Tree而不是内部版本,我可以解耦遍历器功能,甚至可以很容易地对其进行测试。

我试图找到一种在两者之间转换的方法,但无济于事。甚至可能吗?从他们的源代码来看,许多事情看起来太相似了,这是不可能的。

4

2 回答 2

4

您可能正在为编译器实现的蛋糕模式以及随之而来的大量路径依赖性而苦苦挣扎。前段时间,当我编写一些非常强大的宏并想将宏实现中的一堆函数重构为单独的实用程序类时,我已经经历过这个。我发现这是一个非常烦人的问题。

这是我将如何Traverser在单独的类中实现您的方法:

class MyPluginUtils[G <: Global with Singleton](global: G) {
  import global._

  class AnalyzingTraverser extends ForeachTreeTraverser(tree => /* analyze */)
}

现在,在您的插件中,您必须像这样使用它:

val utils = new MyPluginUtils[global.type](global)
import utils.{global => _, _}

val traverser = new AnalyzingTraverser

正如你所看到的,它不是世界上最直观的东西(即这令人困惑),但这是我能想到的最好的东西,它确实有效,我尝试了很多东西,然后最终解决了这个问题一。我真的很高兴看到一些更好的方法来做到这一点。

AFAIK,这种可扩展性是蛋糕模式的普遍问题之一(如在 scalac 实现中使用的那样)。我看到其他人也抱怨这个。

于 2013-07-28T21:44:13.680 回答
1

必须使用早期初始化的成员(即作为早期定义)创建组件(SubComponent或)。PluginComponentglobal

不要忘记查看一个问题的常见问题解答。我可能会去设置谷歌日历来提醒我每周一早上这样做。

例如,请参阅continuation 插件

该组件是使用混合在.

实用程序类遵循通常的蛋糕配方。(将其保留为抽象依赖项,让编译器确保所有内容都正确混合。)

这是最近的编辑,显示了更多早期定义,以证明这种用法没有异常。

  val anfPhase = new {
    val global = SelectiveCPSPlugin.this.global
    val cpsEnabled = pluginEnabled
    override val enabled = cpsEnabled
  } with SelectiveANFTransform {
    val runsAfter = List("pickler")
  }

(将来,他们计划在语言中可用时弃用早期定义,转而支持参数化特征。)

更一般地,global即“编译器”,例行地被实例化以测试编译器本身。我还没有看到它被嘲笑,但它computeInternalPhases是用于选择哪些阶段是组装模插件的模板方法。

目前正在努力减少内部依赖性,以进行测试,作为了解所涉及困难的窗口。

于 2013-07-29T04:50:45.300 回答