我正在手工编写一个小型语言的递归下降解析器。在我的词霸中,我有:
trait Token{def position:Int}
trait Keyword extends Token
trait Operator extends Token
case class Identifier(position:Int, txt:String) extends Token
case class If (position:Int) extends Keyword
case class Plus (position:Int) extends Operator
/* etcetera; one case class per token type */
我的解析器效果很好,现在我想加入一些错误恢复:替换,插入或丢弃令牌,直到某个同步点。
为此,有一个函数,在无效的Scala中看起来像这样的
是很方便的。def scanFor(tokenSet:Set[TokenClass], lookahead:Int) = {
lexer.upcomingTokens.take(lookahead).find{ token =>
tokenSet.exists(tokenClass => token.isInstanceOf[tokenClass])
}
}
我会打电话,例如:scanFor(Set(Plus, Minus, Times, DividedBy), 4)
但是TokenClass
当然不是有效类型,我不知道如何创建上一组。
作为替代方案:
instanceOf
检查。但是,我可能有几个这样的集合,这些集合很难命名,而且代码很难在以后维护。有什么建议吗?
答案 0 :(得分:3)
我实际上建议,如果只有少数这样的组合,使用额外的特性。它易于编写和理解,并且在运行时会很快。说起来并不是那么糟糕
case class Plus(position: Int)
extends Operator with Arithmetic with Precedence7 with Unary
但是有很多种选择。
如果你不介意一个挑剔的手动维护过程并且需要非常快的东西,那么为每个标记类型定义一个ID号(必须手动保持不同)将允许你使用Set[Int]
或{{1或者甚至只是BitSet
来选择你喜欢的那些类。然后,您可以设置操作(并集,交集)以相互构建这些选择器。编写单元测试以帮助使得挑剔的位更加可靠并不难。如果您至少可以设法列出所有类型:
Long
因此,如果您认为性能和可组合性至关重要,那么您不应该对此方法过于担心。
你也可以编写自定义提取器,它可以(更慢)通过模式匹配拉出正确的标记子集。例如,
val everyone = Seq(Plus, Times, If /* etc */)
assert(everyone.length == everyone.map(_.id).toSet.size)
如果不是正确的操作类型,会给你object ArithOp {
def unapply(t: Token): Option[Operator] = t match {
case o: Operator => o match {
case _: Plus | _: Minus | _: Times | _: DividedBy => Some(o)
case _ => None
}
case _ => None
}
}
。 (在这种情况下,我假设除了None
之外没有父母。)
最后,您可以将您的类型表达为工会和HLists,然后使用Shapeless以这种方式选择它们,但我没有亲身使用解析器的经验,所以我不确定您可能遇到的困难