Parsing 使用Scala解析器的运算符关联性

Parsing 使用Scala解析器的运算符关联性,parsing,scala,operators,context-free-grammar,ll,Parsing,Scala,Operators,Context Free Grammar,Ll,所以我一直在尝试用Scala的解析器编写一个计算器,这很有趣,除了我发现操作符的关联性是向后的,而且当我试图使我的语法左递归时,即使它完全不含糊,我也会得到堆栈溢出 为了澄清,如果我有如下规则: def减去:解析器[Int]=num~“-”~add{x=>x.\u 1.\u 1-x.\u2} 然后计算7-4-3得到的结果是6,而不是0 我实际实现这一点的方式是,我正在构建一个二叉树,其中操作符是非叶节点,叶节点是数字。我计算树的方法是左子(操作符)右子。当为7-4-5构建树时,我希望它看起来像:

所以我一直在尝试用Scala的解析器编写一个计算器,这很有趣,除了我发现操作符的关联性是向后的,而且当我试图使我的语法左递归时,即使它完全不含糊,我也会得到堆栈溢出

为了澄清,如果我有如下规则: def减去:解析器[Int]=num~“-”~add{x=>x.\u 1.\u 1-x.\u2} 然后计算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解析器。如果你知道另一种方法,请告诉我

Scala的解析器组合器的标准实现(
Parsers
trait)不支持左递归语法。但是,如果需要左递归,可以使用。也就是说,如果您的语法是一个简单的算术表达式解析器,那么您肯定不需要左递归

编辑

有一些方法可以使用右递归,但仍然保持左关联性,如果您对此感兴趣,只需查找算术表达式和递归下降解析器。当然,正如我所说,您可以使用
PackratParsers
,它允许左递归

但是不使用
PackratParsers
处理关联性的最简单方法是避免使用递归。只需使用其中一个重复运算符即可获得
列表
,然后根据需要使用
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的标准解析器组合器实现(解析器特性)不支持左递归语法。但是,如果需要左递归,可以使用。也就是说,如果您的语法是一个简单的算术表达式解析器,那么您肯定不需要左递归

编辑

有一些方法可以使用右递归,但仍然保持左关联性,如果您对此感兴趣,只需查找算术表达式和递归下降解析器。当然,正如我所说,您可以使用
PackratParsers
,它允许左递归

但是不使用
PackratParsers
处理关联性的最简单方法是避免使用递归。只需使用其中一个重复运算符即可获得
列表
,然后根据需要使用
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))
  }
}

您可以在存储库中找到其他更完整的示例。

我将您的问题解释如下:

如果您编写规则,如
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)*
    。您将有一个初始编号,加上一个平面列表中的一组运算符编号对,可以按您想要的任何方向进行处理(尽管这里从左到右可能更容易)

  • 按照Daniel Sobral的建议使用
    PackratPassers
    。这可能是您最好、最简单的选择


  • 我将你的问题解释如下:

    如果您编写规则,如
    def expression=number~“-”~expression
    ,然后在语法树的每个节点上求值,那么您会发现在
    3-5-4
    中,首先计算
    5-4
    ,结果为1,然后计算
    3-1
    ,结果为2

    另一方面,如果编写规则,如
    def expression=expression~“-”~number
    ,则这些规则是左递归的,并溢出堆栈

    此问题有三种解决方案:

  • 对抽象语法树进行后期处理,将其从右关联树转换为左关联树。如果您正在使用语法规则上的操作来立即执行计算,则不会发生这种情况