views:

59

answers:

1

Im trying to create a parser for a small language with commands including labels and goto:

...
lazy val cmds  = opt("{")~>rep(cmd<~opt(";"))<~opt("}") ^^ {...}
lazy val cmd = ("if"~boolexpr~"then"~cmds~"else"~cmds 
   ^^ { case _~b~_~c1~_~c2 => IFCMD(boolexpr,c1
 | ident ~":="~numericLit ^^ {case i1~_~v => ASSIGN(i1,v) }
 | "goto" ~>ident ^^ { case l => GOTO(l) }
 | ident~":"~cmd ^^ { case l~_~c  => <APPENDLABELTO_CORE>
...

the GOTO, IFCMD etc are case classes extending abstract class Core

In keeping with the functional/scala-like/immutable-objecty -way I'm thinking that defining Core like this is wrong:

abstract class Core(var label:Option[String] = None )

but would allow me to replace the part with <APPENDLABELTO_CORE> with:

 | ident~":"~cmd ^^ { case l~_~c  => c.label = Some(l); c }

Can anyone point out the "scalaish" way to do this?

( I've tried c copy (label=Some(l)) but the abstract base class hasn't got the automatic copy constructor magic )

+4  A: 

It is entirely possible to create your own copy-like method:

abstract class Core(val label: Option[String]) {
  def set(label: Option[String]): Core
}
class Impl(label: Option[String] = None) extends Core(label) {
  def set(label: Option[String] = this.label) = new Impl(label)
}

used thusly:

scala> val i = new Impl
i: Impl = Impl@1930ebb

scala> i.label
res0: Option[String] = None

scala> i.set(label = Some("thing"))
res1: Impl = Impl@b28f30

scala> res1.label
res2: Option[String] = Some(thing)

But, pragmatically, I wouldn't be too quick to dismiss the use of vars here. It is easier to reason about immutable values, but the ones you get are pretty well isolated within the parser, as far as I can tell. An alternative idea would be to create a method that converts everything to an immutable version at the end, or if the parser code ends up storing all the data elsewhere anyway, just leaving it mutable.

Another way to go is to make the abstract class not abstract, but to actually make it a case class. You can derive classes from a case class (deriving case classes from case classes is a no-no, however). The trick then would be to make your variable data that you may need to preserve live in a field:

abstract class SpecializedStuff { }
case class ParticularStuff(val i: Int) extends SpecializedStuff
case class Core(details: SpecializedStuff, label: Option[String] = None)
class Impl(p: ParticularStuff) extends Core(p)

scala> val i = new Impl( ParticularStuff(5) )
i: Impl = Core(ParticularStuff(5),None)

scala> i.copy(label = Some("thing"))
res0: Core = Core(ParticularStuff(5),Some(thing))
Rex Kerr