考虑
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”匹配第一个输入字符,解析完成。为了获取更多,我们需要将 sum 设为第一个选择。然而,愚蠢至极
惰性 val expr: Parser[String] = sum | “1”
以堆栈溢出结束。因此,图书馆作者从另一面接近它
val sum: P[Any] = P( num ~ ("+".! ~/ num).rep )
val top: P[Any] = P( sum ~ End )
在这里,我们从终端开始求和,这很好,但这种语法更冗长,此外,它产生一个终端,然后是一个列表,这对于一个归约运算符很有用,比如 sum,但很难映射到一个系列二元运算符。
如果您的语言定义了允许二元运算符的表达式怎么办?您想匹配每个出现的expr op expr
并将其映射到相应的树节点
expr ~ "op" ~ expr ^^ {case a ~ _ ~ b => BinOp(a,b)"}
你是怎样做的?简而言之,我想要一个贪婪的解析器,它会消耗整个字符串。这就是我所说的“贪婪”而不是贪婪的算法,它跳进第一辆马车并最终陷入死胡同。