2

我正在写一个简单的解析器是Scala。

我有一个基本特征,它代表文件中的一个元素。

trait Token[T] {
    def stringValue: String
    def value: T
}

这就是我需要的 - 字符串(文本)值和解析值(有时会是相同的字符串)。现在我想要一组子类:

  • 保留符号/关键字,例如classvoid
  • 特殊符号,例如+/
  • 整数字面量,例如 123
  • 真正的文字,例如 1.23
  • 字符串文字,例如'123'

你将如何实现这样的层次结构?由于这是有限的,因此使用案例类会很好。我认为。但是枚举也很棒..如何组合?


换句话说,在 Scala 中以更 Scal-ish 的方式编写这个(下)的最佳方法是什么?

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; }
}

等等

4

2 回答 2

5

在 Scala 中构建这样的层次结构时,请尝试应用以下原则:

  1. 绘制您需要的类层次结构。以仅实例化叶节点而内部节点是抽象的方式设计它。
  2. 将内部节点实现为特征
  3. 将叶节点实现为案例类

这样做的原因是,case 类自动添加了许多有用的魔法(toString、unapply、serialize、equals 等)。但是生成的必要代码与案例类之间的继承不兼容(例如,equals 无法正常工作)。

通常没有参数的叶子类型通常被建模为,case object而有参数的叶子类型被建模为case class

当您需要实例化类型树的内部节点时,只需添加一个人工叶并将其实现为案例类/案例对象。

您也可以在 Scala 中使用枚举,但通常情况类更实用。当您需要将给定字符串转换为相应的枚举时,枚举通常是一个不错的选择。您可以使用Enumeration.withName(String).

在 Alexey Romanov 的回答中,您可以看到如何将此原则应用于具有一个根节点和三个叶节点的类型树。

  1. Token(内部节点=>特征)

    1.1。ClassSymbol(不带参数的叶节点 => 案例对象)

    1.2. VoidSymbol(不带参数的叶节点 => 案例对象)

    1.3. IntegerLiteral. (带参数的叶节点=>案例类)

您的情况的一个示例,同时使用枚举和案例类:

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
}

一些使用示例:

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
于 2013-09-16T11:32:25.963 回答
3
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] { ... }
于 2013-09-16T10:57:01.910 回答