所以我一直在尝试用Scala的解析器编写一个计算器,它很有趣,除了我发现运算符关联性是向后的,当我尝试使我的语法左递归时,即使它完全是明确的,我得到一个堆栈溢出。
澄清一下,如果我有这样的规则: def subtract:Parser [Int] = num~“ - ”~add {x => x._1._1 - x._2} 然后评估7 - 4 - 3是6而不是0.
我实际实现这个的方式是我正在组成一个二叉树,其中运算符是非叶节点,叶节点是数字。我评估树的方式是留给孩子(操作员)的右孩子。在为7 - 4 - 5构建树时,我希望它看起来像是:
-
- 5
7 4 NULL NULL
其中 - 是根,其子节点是 - 和5,第二个子节点是7和4。
然而,我唯一可以轻松构建的树是
-
7 -
NULL NULL 4 5
这是不同的,而不是我想要的。
基本上,简单的括号是7 - (4 - 5),而我想要(7 - 4) - 5。
我怎么能破解这个?无论如何,我觉得我应该能够编写一个具有正确运算符优先级的计算器。我应该首先对所有内容进行标记,然后反转我的令牌吗?我是否可以通过抓住正确孩子的所有左子女并让他们成为正确孩子的父母的正确孩子并让父母成为前右孩子的左孩子来翻转我的树?它在第一次近似似乎很好,但我并没有真正考虑过它。我觉得必须有一些我失踪的案例。
我的印象是我只能用scala解析器创建一个LL解析器。如果您了解其他方式,请告诉我!
答案 0 :(得分:7)
Scala的解析器组合子的标准实现(Parsers
特征)不支持左递归语法。但是,如果需要左递归,则可以使用PackratParsers
。也就是说,如果你的语法是一个简单的算术表达式解析器,你绝对不需要左递归。
修改强>
有一些方法可以使用正确的递归并仍然保持左关联性,如果你热衷于此,只需查找算术表达式和递归下降解析器。当然,正如我所说,你可以使用PackratParsers
,它允许左递归。
但是,在不使用PackratParsers
的情况下处理关联性的最简单方法是避免使用递归。只需使用其中一个重复运算符即可获得List
,然后根据需要获得foldLeft
或foldRight
。简单的例子:
trait Tree
case class Node(op: String, left: Tree, right: Tree) extends Tree
case class Leaf(value: Int) extends Tree
import scala.util.parsing.combinator.RegexParsers
object P extends RegexParsers {
def expr = term ~ (("+" | "-") ~ term).* ^^ mkTree
def term = "\\d+".r ^^ (_.toInt)
def mkTree(input: Int ~ List[String ~ Int]): Tree = input match {
case first ~ rest => ((Leaf(first): Tree) /: rest)(combine)
}
def combine(acc: Tree, next: String ~ Int) = next match {
case op ~ y => Node(op, acc, Leaf(y))
}
}
您可以在scala-dist存储库中找到其他更完整的示例。
答案 1 :(得分:1)
我正在解释你的问题如下:
如果您编写def expression = number ~ "-" ~ expression
之类的规则,然后在语法树的每个节点上进行评估,那么您会发现在3 - 5 - 4
中,首先计算5 - 4
,结果为1 ,然后计算3 - 1
,结果为2。
另一方面,如果您编写类似def expression = expression ~ "-" ~ number
的规则,则规则是左递归的并溢出堆栈。
这个问题有三种解决方案:
对抽象语法树进行后处理,将其从右关联树转换为左关联树。如果您正在使用语法规则上的操作立即进行计算,那么这对您无效。
将规则定义为def expression = repsep(number, "-")
,然后在评估计算时,循环解析的数字(将出现在平面列表中),无论哪个方向都为您提供所需的关联性。如果出现多种操作员,则不能使用此操作,因为操作员将被丢弃。
将规则定义为def expression = number ~ ( "-" ~ number) *
。您将在平面列表中有一个初始编号和一组操作员编号对,以便在任何方向上处理(尽管从左到右可能更容易)。
使用PackratParsers
作为Daniel Sobral的建议。这可能是您最好也是最简单的选择。