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 )
It is entirely possible to create your own copy-like method:
used thusly:
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: