2013-07-28 53 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減少內部依賴關係,爲測試的目的,作爲一個窗口涉及到的困難。

相關問題