Parsing 使用Scala解析器的运算符关联性
所以我一直在尝试用Scala的解析器编写一个计算器,这很有趣,除了我发现操作符的关联性是向后的,而且当我试图使我的语法左递归时,即使它完全不含糊,我也会得到堆栈溢出 为了澄清,如果我有如下规则: def减去:解析器[Int]=num~“-”~add{x=>x.\u 1.\u 1-x.\u2} 然后计算7-4-3得到的结果是6,而不是0 我实际实现这一点的方式是,我正在构建一个二叉树,其中操作符是非叶节点,叶节点是数字。我计算树的方法是左子(操作符)右子。当为7-4-5构建树时,我希望它看起来像: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构建树时,我希望它看起来像:
-
- 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)*
。您将有一个初始编号,加上一个平面列表中的一组运算符编号对,可以按您想要的任何方向进行处理(尽管这里从左到右可能更容易)PackratPassers
。这可能是您最好、最简单的选择我将你的问题解释如下: 如果您编写规则,如
def expression=number~“-”~expression
,然后在语法树的每个节点上求值,那么您会发现在3-5-4
中,首先计算5-4
,结果为1,然后计算3-1
,结果为2
另一方面,如果编写规则,如def expression=expression~“-”~number
,则这些规则是左递归的,并溢出堆栈
此问题有三种解决方案: