Scala internal DSL: lists without ","

I am trying to build an internal DSL in Scala to represent algebraic definitions. Consider this simplified data model:

case class Var(name:String) case class Eq(head:Var, body:Var*) case class Definition(name:String, body:Eq*) 

For example, a simple definition would be:

 val x = Var("x") val y = Var("y") val z = Var("z") val eq1 = Eq(x, y, z) val eq2 = Eq(y, x, z) val defn = Definition("Dummy", eq1, eq2) 

I would like to have an internal DSL to represent this equation as:

 Dummy { x = yz y = xz } 

The closest I could get is the following:

 Definition("Dummy") := ( "x" -> ("y", "z") "y" -> ("x", "z") ) 

The first problem I encountered is that I cannot have two implicit conversions for the definition and Var, hence Definition("Dummy") . The main problem, however, is lists. I do not want to surround them with something, for example. (), And I also do not want their elements to be separated by commas.

I want me to be able to use Scala? If so, can someone show me a simple way to achieve this?

+4
source share
3 answers

Although Scalas syntax is powerful, it is not flexible enough to create arbitrary delimiters for characters. Thus, you cannot leave commas and replace them only with spaces.

However, you can use macros and parse a string with arbitrary content at compile time. This is not a "simple" solution, but one:

 object AlgDefDSL { import language.experimental.macros import scala.reflect.macros.Context implicit class DefDSL(sc: StringContext) { def dsl(): Definition = macro __dsl_impl } def __dsl_impl(c: Context)(): c.Expr[Definition] = { import c.universe._ val defn = c.prefix.tree match { case Apply(_, List(Apply(_, List(Literal(Constant(s: String)))))) => def toAST[A : TypeTag](xs: Tree*): Tree = Apply( Select(Ident(typeOf[A].typeSymbol.companionSymbol), newTermName("apply")), xs.toList ) def toVarAST(varObj: Var) = toAST[Var](c.literal(varObj.name).tree) def toEqAST(eqObj: Eq) = toAST[Eq]((eqObj.head +: eqObj.body).map(toVarAST(_)): _*) def toDefAST(defObj: Definition) = toAST[Definition](c.literal(defObj.name).tree +: defObj.body.map(toEqAST(_)): _*) parsers.parse(s) match { case parsers.Success(defn, _) => toDefAST(defn) case parsers.NoSuccess(msg, _) => c.abort(c.enclosingPosition, msg) } } c.Expr(defn) } import scala.util.parsing.combinator.JavaTokenParsers private object parsers extends JavaTokenParsers { override val whiteSpace = "[ \t]*".r lazy val newlines = opt(rep("\n")) lazy val varP = "[az]+".r ^^ Var lazy val eqP = (varP <~ "=") ~ rep(varP) ^^ { case lhs ~ rhs => Eq(lhs, rhs: _*) } lazy val defHead = newlines ~> ("[a-zA-Z]+".r <~ "{") <~ newlines lazy val defBody = rep(eqP <~ rep("\n")) lazy val defEnd = "}" ~ newlines lazy val defP = defHead ~ defBody <~ defEnd ^^ { case name ~ eqs => Definition(name, eqs: _*) } def parse(s: String) = parseAll(defP, s) } case class Var(name: String) case class Eq(head: Var, body: Var*) case class Definition(name: String, body: Eq*) } 

It can be used with something like this:

 scala> import AlgDefDSL._ import AlgDefDSL._ scala> dsl""" | Dummy { | x = yz | y = xz | } | """ res12: AlgDefDSL.Definition = Definition(Dummy,WrappedArray(Eq(Var(x),WrappedArray(Var(y), Var(z))), Eq(Var(y),WrappedArray(Var(x), Var(z))))) 
+11
source

In addition to the good sschaef solution, I want to mention a few features that are commonly used to get rid of the commas in building lists for DSL.

Colons

It may be trivial, but sometimes it is ignored as a solution.

 line1 :: line2 :: line3 :: Nil 

DSL often requires that each line containing some instructions / data be completed the same way (against lists where everyone except the last line will receive a comma). With such solutions, replacing lines can no longer ruin the trailing comma. Unfortunately, Nil looks a little ugly.

Fluid API

Another alternative that might be interesting for DSL is something like this:

 BuildDefinition() .line1 .line2 .line3 .build 

where each line is a member function of the builder (and returns a modified builder). This solution requires you to eventually convert the builder to a list (which can be performed as an implicit conversion). Note that for some APIs it may be possible to bypass the linker instances themselves and retrieve data only where necessary.

Constructor API

Similarly, another possibility is to use constructors.

 new BuildInterface { line1 line2 line3 } 

Here BuildInterface is a sign, and we just create an anonymous class from the interface. Line functions call some member functions of this characteristic. Each call can internally update the state of the assembly interface. Note that this usually results in a volatile design (but only during construction). To retrieve a list, you can use implicit conversion.

Since I don’t understand the actual purpose of your DSL, I’m not sure if any of these methods are interesting for your scenario. I just wanted to add them, as they are the usual ways to get rid of ",".

+5
source

Here is another solution that is relatively simple and allows syntax that is close to your ideal (as others pointed out, the exact syntax you requested is not possible, in particular because you cannot override separator characters). My solution is a little different than what is reasonable to do, because it adds an operator directly to scala.Symbol , but if you are going to use this DSL in a limited area, then it should be OK.

 object VarOps { val currentEqs = new util.DynamicVariable( Vector.empty[Eq] ) } implicit class VarOps( val variable: Var ) extends AnyVal { import VarOps._ def :=[T]( body: Var* ) = { val eq = Eq( variable, body:_* ) currentEqs.value = currentEqs.value :+ eq } } implicit class SymbolOps( val sym: Symbol ) extends AnyVal { def apply[T]( body: => Unit ): Definition = { import VarOps._ currentEqs.withValue( Vector.empty[Eq] ) { body Definition( sym.name, currentEqs.value:_* ) } } } 

Now you can do:

 'Dummy { x := (y, z) y := (x, z) } 

Creates the following definition (as specified in the REPL):

 Definition(Dummy,Vector(Eq(Var(x),WrappedArray(Var(y), Var(z))), Eq(Var(y),WrappedArray(Var(x), Var(z))))) 
+1
source

All Articles