Wednesday, August 17, 2011

Scala DSL technique - if-else constructs

Believe it or not, I've had a hard time coming up with this construct, so I figured it's worth publishing it, maybe save others the hassle.

You want to come up with a recursive if/else construct, like below (I used a dollar sign to denote the DSL if - I've used wif/welse in the past - use whatever you want):

  var branch = 1

  val expr =

    $if(branch == 1) {

      v("a") := 11

      v("b") := 12

    } $else $if(branch == 2) {

      v("a") := 21

      v("b") := 22

    } $else $if(branch == 3) {

      v("a") := 31

      v("b") := 32

    } $else {

      v("a") := 41

      v("b") := 42

    }


Note the recursive if-else-if-else-if-else. The idea is that the if/else token will modify its own else branch depending on the following else - either the final else {} or an else followed by an if:

  case class TIf(cond: () => Boolean, body: () => Unit, var elsebranch: Option[TElse] = None) extends Token("IF ") {

    val scope = TScope(body)

    def $else[T](body: => T) = { this.lastif.elsebranch = Some(TElse(() => body)); this }

    def $else(i: TIf) = {

      this.lastif.elsebranch = Some(TElseIf(i))

      this

    }

    def lastif = elsebranch.map(_.lastif).getOrElse(this)

    ...


Also note that the way scala works is that the if-else tree is built from the top, so each else() is actually invoked on the root IF, therefore it needs to recursively find the last unbound IF and bind itself there as the elsebranch.

Having said that, find the full code and use it as a template, here.

Enjoy!

P.S. One issue with the simplified version built here is that it doesn't stop you from simple syntax errors like having an if-else-else... how would you fix it so the compiler stops you rather than the runtime exception?

Tuesday, August 16, 2011

A scala DSL technique - collecting

When coming up with a scala internal DSL, it is often that one will come up with constructs that produce things that need collected, in a list, tree or similar.

For instance, instructions for a robot:

$if (condition) {

move(1) :: move(2) :: move(3) :: Nil
move(1) + move(2) + move(3)
}


Due to syntax restrictions, it is often that people use operators to chain these, like the overloaded + above.

There is a technique you can use to make this look more familiar:

$if (condition) {

move(1)
move(2)
move(3)
}


While not the nicest, the idea is to use a static collector and collect the actions in there. You'll need levels, so that collecting nested blocks don't interfere, so there is a stack of collectors. Also, these collectors need to be thread-local so that different collecting threads don't interfere.

A reusable collector is in DslCollector.scala and a sample in DslCollectorTest.scala

For this pattern, you have a Collector, a Collectable and the actual constructs:

  case class move(i: Int) extends DslCollectable


def test1 = expect(move(1) :: move(2) :: Nil) {
val collected = new collection.mutable.ListBuffer[Any]()

DslCollector.collect { collected += _ }(1) { // start a collector level
move(1) //collects itself
move(2) //collects itself
} // collector ends

collected
}


You can then embedd this in a higher-level construct of your DSL, like the $if used in the example.

Enjoy!