Scala编译器插件解构

时间:2013-07-28 18:49:34

标签: scala

我一直在尝试编写一个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.bodyscala.reflect.internal.Trees内的内部树类型。如果我可以使用scala.reflect.api.Trees#Tree而不是内部版本,我可以解耦遍历器功能,甚至可以很容易地测试它。

我试图找到一种在两者之间进行转换的方法,但无济于事。它甚至可能吗?从查看源代码看,很多东西看起来太相似了,不可能。

2 个答案:

答案 0 :(得分: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实现中使用)。我见过其他人也抱怨过这个。

答案 1 :(得分:1)

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

不要忘记查看the one-question faq。我可以设置谷歌日历,提醒我每周一早上这样做。

有关示例,请参阅the continuations plugin

该组件使用a utility class mixed in定义。

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

Here is a recent edit显示更多早期定义,以证明此用法不是异常。

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

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

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

为了测试的目的,有a current effort来减少内部依赖关系,作为解决所遇困难的窗口。