Build a list from a series of expressions in Scala

When I try to create internal DSLs in Scala, I run into a common problem and I could not create a solution. To make everything look like a typical language, I would like the syntax to look something like this:

model 'Foo { decl 'Real 'x; decl 'Real 'y; } 

In practice, there are several problems. The first problem is getting the model object here to take two arguments this way. If anyone has any ideas let me know. But what I did was to do something more like this:

 model('Foo) { ... } 

If the model is now a function, which then returns the object using the apply method, which then consumes the lambda that follows it. I can live with that. I could live with a similar problem inside the lambda, so things like decl 'Real 'x or decl('Real,'x) inside. But what I want to do is get the results of all these expressions inside squiggly braces to get a β€œreturn” as a list. In other words, I want to write something like this:

 model 'Foo { decl('Real,'x); decl('Real,'y); } 

where decl(...) is of type Declaration , and {...} is List[Declaration] . I suspect there is a way to use implicits for this, but I could not find it. In short, I would like to do:

 model 'Foo { decl('Real,'x); decl('Real,'y); } 

... evaluate the equivalent ...

 model 'Foo { decl('Real,'x) :: decl('Real,'y) :: Nil } 

Comments or suggestions?

+8
scala dsl
source share
3 answers

As a first idea, you can try lists of variable arguments that allow you to use commas instead of half columns:

 case class Declaration(name: String) def decl( s: String ) = Declaration(s) case class Model( sym: Symbol, decls: List[Declaration] ) def model( sym: Symbol)( decls: Declaration* ) = Model( sym, decls.toList ) val m = model( 'Foo )( decl( "bar" ), decl( "baz" ) ) 

Alternatively, you can extend trait to get rid of some parentheses and commas:

 case class ModelBuilder( sym: Symbol ) { def using( decls: Declarations ) = Model( sym, decls.toList ) } trait Declarations { protected var decls = List[Declaration]() protected def decl( s: String ) = decls ::= Declaration( s ) def toList = decls } def model( sym: Symbol ) = ModelBuilder( sym ) model( 'Foo ) using new Declarations { decl( "bar" ) decl( "baz" ) } 
+4
source share

Oh god, what have I done?

 import scala.collection.mutable.ListBuffer case class Declaration(t: Symbol, name: Symbol) case class Model(name: Symbol, declarations: List[Declaration]) object model extends Dynamic { val buffer = ListBuffer.empty[Model] def applyDynamic(name: String)(args: Any*) { buffer += Model(Symbol(name), decl.buffer.toList) decl.buffer.clear() } } object decl extends Dynamic { val buffer = ListBuffer.empty[Declaration] def applyDynamic(t: String)(args: Any*) { args match { case Seq(name: Symbol) => buffer += Declaration(Symbol(t), name) } } } model Foo { decl Real 'x decl Real 'y } assert(model.buffer.head == Model('Foo, List( Declaration('Real, 'x), Declaration('Real, 'y)))) 
+4
source share

OK, I completely reworked this, realizing that 'Foo should be the name of the model.

 trait DSL { private var currentModel: ModelBuilder = null case class Declaration(kind: Symbol, name: Symbol) case class Model(name: Symbol, declarations: List[Declaration]) case class ModelBuilder(name: Symbol, var declarations: Vector[Declaration]) { def -(f: => Unit) = { currentModel = this f Model(name, declarations.toList) } } def decl (s1: Symbol, s2: Symbol) { currentModel.declarations :+= Declaration(s1, s2) } object model { def - (s: Symbol) = ModelBuilder(s, Vector.empty) } } 

Then on the site use:

 object UseSite extends App with DSL { val m = model - 'Foo - { decl ('Real, 'x) decl ('Real, 'y) } println(m) //Model('Foo,List(Declaration('Real,'x), Declaration('Real,'y))) } 

So the tricks here

1) using a variable to track the current model

2) using characters - for method names (you can use apply instead if you prefer parentheses)

3) using the builder so that the returned class can be immutable

Although, TBH it may be a little to avoid some commas ... :)

+2
source share

All Articles