2013-07-28 52 views
0

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

这是我本来有:

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是内部树型scala.reflect.internal.Trees。如果我可以使用scala.reflect.api.Trees#Tree而不是内部版本,我可以解耦移植程序的功能,甚至可以非常轻松地进行测试。

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

回答

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,这样的可扩展性是Cake模式的一般问题之一(在scalac实现中使用)。我见过其他人也抱怨这件事。

+0

是的,这将解除耦合器与插件的耦合。虽然正如你和som-snytt所建议的那样,问题根源在于有问题的蛋糕(反)模式。 –

1

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

不要忘记检讨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减少内部依赖关系,为测试的目的,作为一个窗口涉及到的困难。

相关问题