2010-07-19 58 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