2010-07-19 45 views
2

我問了自己這個問題幾次,並提出了一個解決方案,感覺很骯髒。也許你可以給我任何建議,因爲我認爲這是每個用Scala編寫的DSL的基本問題。斯卡拉DSL沒有額外的語法

我想擁有嵌套對象的層次結構,而無需添加任何額外的語法。規格是一個很好的例子:

MySpec extends Specification { 
    "system" should { 
    "example0" in { ... } 
    "example1" in { ... } 
    "example2" in { ... } 
    } 

    "system" can { 
    "example0" in { ... } 
    } 
} 

例如,我不必寫"example0" in { ... } :: "example1" in { ... } :: "example2" in { ... } :: Nil

這和我想要的行爲完全一樣。我認爲這是通過在規格的規範類像一個隱含的定義來實現(請不要如果你是規格作者和得罪我missunderstood東西:))現在

implicit def sus2spec(sus: Sus): Specification = { 
    suslist += sus 
    this 
} 

我主要的問題出現時,我想嵌套這些對象。想象一下,我有這樣的語法:

root: statement*; 

statement: 
    IDENT '{' statement* '}' 
    | declaration* 
    ; 

declaration: IDENT ':=' INT+; 

我想轉換爲DSL,看起來像這樣的:

MyRoot extends Root { 
    "statement0" is { 
    "nested_statement0" is { 
     "nested_nested_statement0" is { 
     "declaration0" := 0 
     } 

     "declaration1" := 1 
     "declaration2" := 2 
    } 

    "declaration3" := 3 
    } 

    "statement1" is { 
    "declaration4" := 4 
    } 
} 

即出現在這裏對我來說是隱式的解決方案不起作用的問題。隱式定義將在根對象的範圍內執行,這意味着我會將所有對象添加到根並且層次結構丟失。

然後我想我可以使用類似Stack [聲明]的東西。每次致電is時,我都可以推送一個對象,但感覺非常髒。

用一句話來說明問題:如何創建一個遞歸的DSL,並尊重其層次結構而不添加任何額外的語法,並且是否有解決方案僅對不可變對象執行此操作?

+1

請注意,您可以很好地在規格中嵌套示例:http://code.google.com/p/specs/wiki/DeclareSpecifications#Sub_examples因此解決方案應該在那裏。 – 2010-07-19 10:20:06

+0

謝謝你指出。我想我已經找到了解決方案:「if(m.erasure == this.getClass)hasSomeSubExamples = true」。那麼也許我應該堅持我的可變堆棧。 – 2010-07-19 12:56:34

回答

2

我有看過規格,他們沒有做任何差異。基本上所有你需要的是一個可變的堆棧。你可以看看這裏的結果:cssx-dsl

這段代碼很簡單。基本上我有一個mutable builder並將其轉換爲一個不可變的表示。

2

我在XScalaWT中看到了一個很好的技巧來實現DSL中的嵌套。我沒有檢查規格是否使用相同或不同的東西。

我想下面的例子顯示了主要思想。它的核心是設置功能:它接受一些功能(更確切地說,關閉,如果我沒有弄錯),只需要一個Nestable,並且將會調用它。
printName碰巧就是這樣一種方法,就像addChild一樣,參數爲第一個參數列表填充。

對我來說,理解這是揭示部分。之後,您可以相對簡單地添加許多其他奇特功能(如隱式魔術,基於結構類型的dsl方法等)。

當然,你可以有任何「上下文」類而不是Nestable,特別是如果你去尋找純粹的不可變的東西。如果父母需要引用孩子,您可以在設置()期間收集孩子,並且只在最後創建父母。

在這種情況下,你可能會碰到這樣的

private def setupChildren[A, B](a : A, setups:(A => B)*) : Seq[B] = { 
    for (setup <- setups) yield setup(a) 
    } 

您將通過在「背景」,並創建使用返回的孩子家長。

順便說一句我認爲這個設置的東西是需要在XScalaWT中,因爲它是SWT的子對象需要對其父控件的引用。如果你不需要它(或者當前「上下文」中的任何東西),那麼一切都變得容易一些。
使用適當的應用方法伴隨對象應該主要解決問題。最有可能的是,他們也應該接受其他功能,(如果你需要更多的話,可以有相同數量的參數或者元組)。

這個技巧的一個缺點是你必須爲你想要在你的類上調用的每個方法都有一個單獨的dsl方法(即使是簡單的方法)。或者你可以使用像

x => x.printName 

這將做的工作,但不是很好(尤其是如果你必須經常這樣做)。

object NestedDsl { 

    object Nestable { 
    def apply(name: String, setups:(Nestable => Unit)*): Nestable = { 
     val n = new Nestable(None, name) 
     setup(n, setups: _*) 
     n 
    } 
    } 

    class Nestable(parent: Option[Nestable], name: String) { 
    def printName() { println(name) } 
    } 

    // DSL part 

    def addChild(name: String, setups:(Nestable => Unit)*)(parent: Nestable) = { 
    val n = new Nestable(Some(parent), name) 
    setup(n, setups: _*) 
    n 
    } 

    def printName(n: Nestable) = n.printName 

    private def setup[T](t : T, setups:(T => Unit)*) : T = { 
    setups.foreach(setup => setup(t)) 
    t 
    } 

    def main(args: Array[String]) { 
    Nestable("root", 
     addChild(
      "first", 
      addChild("second", 
      printName 
     ) 
    ) 
    ) 
    } 
} 
+0

謝謝你的回答。如何這是我想達到的反例。特別是暗示和讓他們在正確的範圍內是我的問題。 – 2010-07-19 12:54:00

+0

有趣的是,我正在研究類似的DSL,我想我已經遇到了相同(或類似)的範圍/可見性問題。我有一些想法,如果他們工作,我會發送另一個答案,希望能更接近你想達到的目標...... – 2010-07-19 15:39:38