为什么不解析组合子回溯?

时间:2015-11-16 22:32:26

标签: scala regex-greedy parser-combinators fastparse

考虑

import util.parsing.combinator._
object TreeParser extends JavaTokenParsers {

    lazy val expr: Parser[String] = decimalNumber | sum
                                                  //> expr: => TreeParser.Parser[String]
    lazy val sum: Parser[String] = expr ~ "+" ~ expr ^^ {case a ~ plus ~ b => s"($a)+($b)"}
                                                  //> sum: => TreeParser.Parser[String]
    println(parseAll(expr, "1 + 1"))                       //> TreeParser.ParseResult[String] = [1.3] failure: string matching regex 
                                              //| `\z' expected but `+' found
}

与fastparse相同的故事

import fastparse.all._
val expr: P[Any] = P("1" | sum)
val sum: P[Any] = expr ~ "+" ~ expr
val top = expr ~ End
println(top.parse("1+1")) // Failure(End:1:2 ..."+1")

解析器很高兴发现第一个文字是一个坏主意,但不要试图回到总和产量。为什么呢?

我理解解析器采用第一个可以成功占用输入字符串的一个分支并退出。在这里," 1" of表达式与第一个输入char匹配,解析完成。为了获得更多,我们需要将总和作为第一选择。但是,简单的愚蠢

lazy val expr:Parser [String] = sum | " 1"

endы up with stack overflow。因此,图书馆作者从另一方面接近它

val sum: P[Any] = P( num ~ ("+".! ~/ num).rep )
val top: P[Any]   = P( sum ~ End )

这里,我们用终端开始求和,这很好但是这个语法更冗长,而且,它产生一个终端,后面跟一个列表,这对于简化运算符很有用,比如求和,但很难映射一系列二元运算符。

如果您的语言定义了允许二元运算符的表达式,该怎么办?您希望匹配expr op expr的每个匹配项并将其映射到相应的树节点

expr ~ "op" ~ expr ^^ {case a ~ _ ~ b => BinOp(a,b)"} 

你是怎么做到的?简而言之,我想要一个贪婪的解析器,它消耗整个字符串。这就是我所说的贪婪'而不是贪婪的algorigthm,跳进第一辆马车,最终走向死胡同。

2 个答案:

答案 0 :(得分:2)

正如我found here所述,我们需要将|替代运营商替换为秘密|||

//lazy val expr: Parser[String] = decimalNumber | sum
lazy val backtrackGreedy: Parser[String] =  decimalNumber ||| sum

lazy val sum: Parser[String] = decimalNumber ~ "+" ~ backtrackGreedy ^^ {case a ~ plus ~ b => s"($a)+($b)"}

println(parseAll(backtrackGreedy, "1 + 1")) // [1.6] parsed: (1)+(1)

此操作符的替代顺序无关紧要。要停止堆栈溢出,我们需要消除左递归,sum = expr + expr => sum = number + expr

Another answer说我们需要规范化,而不是

  def foo = "foo" | "fo"
  def obar = "obar"

  def foobar = foo ~ obar

我们需要使用

def workingFooBar = ("foo" ~ obar) | ("fo" ~ obar)

但第一种解决方案更引人注目。

答案 1 :(得分:1)

解析器确实回溯了。例如,请尝试val expr: P[String] = P(("1" | "1" ~ "+" ~ "1").!)expr.parse("1+1")

问题在于你的语法。 expr解析1并且按您的定义成功解析。那么sum失败了,现在你想要责备那些尽职尽责的expr

有很多关于如何处理二元运算符的例子。例如,这里的第一个例子是:http://lihaoyi.github.io/fastparse/