I'm looking to design a DSL in Scala that has the least amount of syntax cruft possible. It's meant to be used by users who don't know Scala, but can take advantage of Scala type system for validation and error checking. In my head the DSL looks like this:
outer {
inner(id = "asdf") {
value("v1")
value("v2")
}
}
This snipped should produce a value like this:
Outer(Inner("asdf", Value("v1") :: Value("v2") :: Nil))
Given data structures
case class Outer(inner: Inner)
case class Inner(values: List[Value])
case class Value(value: String)
The idea is that inner function is only available in the closure following outer, value function is only available withing the closure after inner, etc. That is the following won't compile: outer { value("1") }.
How can I implement something like this? In the end the data structures don't need to be immutable, it can be anything as long as it's strongly typed.
I have no familiarity with Scala macros, but can I solve this problem with macros by any chance?
The closest I have so far is the following implementation:
object DSL extends App {
def outer = new Outer()
class Outer(val values: mutable.MutableList[Inner] = mutable.MutableList.empty) {
def inner(id: String): Inner = {
val inner = new Inner(id)
values += inner
inner
}
def apply(func: Outer => Unit): Outer = {
func(this)
this
}
override def toString: String = s"Outer [${values.mkString(", ")}]"
}
class Inner(val id: String, val values: mutable.MutableList[Value] = mutable.MutableList.empty) {
def value(v: String): Value = {
val value = new Value(v)
values += value
value
}
def apply(func: Inner => Unit): Unit = func(this)
override def toString: String = s"Inner (${values.mkString(", ")})"
}
class Value(val str: String) {
override def toString: String = s"Value<$str>"
}
val value = outer { o =>
o.inner(id = "some_id") { i =>
i.value("value1")
i.value("value2")
}
}
println(value)
How can I get rid of the anonymous function annotations (i.e. o => and o., etc.)?
Alternatively is there a way to treat outer as new Outer (in this case the following code block will be treated as constructor and I will be able to call member functions)?