I am writing a simple parser is Scala.
I have a base trait which represents an element in the file.
trait Token[T] {
def stringValue: String
def value: T
}
This is what I need - the string (text) value and the parsed value (which sometimes will the the same string). Now I want to have a set of subclasses for:
class, void etc.+, / etc.How would you implement a hierarchy like this? Since this is finite it'd be nice to use a case class.. I think. But an enumeration would be great too .. How to combine?
In other words, what's the best way to write this (below) in Scala in a more Scal-ish way?
public interface Token<T> {
String stringValue();
T value();
}
public enum ReservedSymbol implements Token<ReservedSymbol> {
CLASS('class'), VOID('void');
private String val;
private ReservedSymbol(String val) { this.val = val; }
public String stringValue() { return val; }
public ReservedSymbol value() { return this; }
}
public class IntegerLiteral implements Token<Integer> {
private Integer val;
public IntegerLiteral(String val) { this.val = Integer.valueOf(val); }
public String stringValue() { return val.toString(); }
public Integer value() { return val; }
}
etc.
When building such a hierarchy in Scala, try to apply the following principles:
The reason for this is, that case classes add a lot of useful magic automatically (toString, unapply, serialize, equals, etc.). But the necessary code is generated in away that is not compatible with inheritance between case classes (e.g. equals would not work properly).
Usually leaf types without parameters are usually modeled as case object while leaf types with parameters are modeled as case class.
When you need to instantiate an inner node of the type tree, just add an artificial leaf and implement it as case class / case object.
You can also use Enumerations in Scala, but usually case classes are more practical. Enumerations are usually a good choice, when you need to convert a given string to the corresponding enumeration. You can do this with Enumeration.withName(String).
In the answer from Alexey Romanov you can see how to apply this principles to a type tree with one root node and three leaf nodes.
Token (inner node => trait)
1.1. ClassSymbol (leaf node without parameters => case object)
1.2. VoidSymbol (leaf node without parameters => case object)
1.3. IntegerLiteral. (leaf node with parameters => case class)
An example for your situation, using both enums and case classes:
trait Token[T]{
def stringValue: String
def value: T
}
object ReservedSymbolEnum extends Enumeration {
type ReservedSymbolEnum = Value
val `class`, `void` = Value
val NullValue = Value("null") // Alternative without quoting
}
case class ReservedSymbol(override val stringValue: String)extends Token[ReservedSymbolEnum.ReservedSymbolEnum] {
def value = ReservedSymbolEnum.withName(stringValue)
}
case class StringLiteral(override val stringValue: String) extends Token[String] {
override def value = stringValue
}
case class IntegerLitaral(override val stringValue: String) extends Token[Int] {
override def value = stringValue.toInt
}
Some usage examples:
scala> def `void`=ReservedSymbol("void")
void: ReservedSymbol
scala> `void`.value
res1: ReservedSymbolEnum.Value = void
scala> def `42`=IntegerLiteral("42")
42: IntegerLitaral
scala> `42`.value
res2: Int = 42
sealed trait Token[+T] { // sealed means it only can be extended in this file
def stringValue: String
def value: T
}
// cast can be avoided if you are happy with extending Token[ReservedSymbol]
// as in the Java example
// class instead of trait so that it can have a constructor argument
sealed class ReservedSymbol[+T <: ReservedSymbol[T]](val stringValue: String) extends Token[T] {
def value = this.asInstanceOf[T]
}
// no body necessary
case object ClassSymbol extends ReservedSymbol[ClassSymbol]("class")
case object VoidSymbol extends ReservedSymbol[VoidSymbol]("void")
case class IntegerLiteral(val: Int) extends Token[Int] { ... }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With