我使用Scala的PackratParsers(解析器组合器),使用以下形式的左递归语法
lazy val expr: PackratParser[Expr] = (
...
| expr ~ (":" ~ expr).+ ^^ {
case expr ~ rest => (expr /: rest)(combineBinary)
}
| ...
)
def combineBinary(acc: Expr, next: String ~ Expr) = next match {
case op ~ expr => FunctionCall(op, acc, expr)
}
我想要二元运算符":"为左关联,使x1:x2:...:xn
形式的表达式解析为(((x1:x2):x3):...:xn)
,即导致FunctionCall(":", FunctionCall(":", FunctionCall(":", x1, x2), x3), ...)
形式的AST。
令人惊讶的是,使用上面定义的PackratParsers语法,得到的AST仍然是右关联的。为什么会这样,可以采取哪些措施来改变它?
我发现了this关于Scala解析器组合器和运算符关联性的讨论,但它似乎没有给出我的问题答案。
答案 0 :(得分:0)
TL;博士 我不知道packrat能否帮助您解决两个大问题。 It did save me from stackoverflow但是我没有那种明显的左撇子。
我的意思是你的递归expr + expr
永远不会终止。我知道你在某处有一些归纳基础,即expr = expr + expr | term
。
现在,您可以轻松地通过term + expr | term
为正确关联创建正确的关联性,因为在找到最后一个术语时,您处于+递归的深处。同样,您可以创建左关联expr + term | term
。左关联导致左递归,你永远不会在最后一个词。即使是packrat也没有从中拯救。我不明白你是如何得到你的结果的。我的
object EP extends JavaTokenParsers with PackratParsers {
def expr: Parser[_] = expr ~ ("+" ~> expr) | ident /*^^ {
case ident ~ rest => (ident /: rest){case (acc, e) => acc + s" + (${e.toString})"}
} | ident*/
}
List("a", "a + b", "a + b + c+ d") foreach {input =>
println("left: " + EP.parseAll(EP.expr, input))
}
堆栈溢出。 It saved me once但我没有那种明显的左撇子。而且,我不知道如何从你提出的第二个问题中拯救你。
无论如何,您必须消除将expr + term | term
更改为
def left: Parser[_] = ident ~ appendix
def appendix = "+" ~> left | ""
但是这又是正确的递归,因为我们再次看到ident是第一个节点。
<强>解决方案强>:
因此,只需使用所有人的操作:使用rep
解析器,它为您提供了一个列表,可从左侧迭代:
def right: Parser[_] = ident ~ ("+" ~> right) ^^ {case head ~ tail => s"Right($head, $tail)"} | ident
lazy val left: Parser[_] = ident ~ rep("+" ~> ident) ^^
{case head ~ tail => (head /: tail){case (acc, expr) => s"Left($acc, $expr)"}}
println("right => " + parseAll(right, "a + b + c+ d"))
println("left => " + parseAll(left, "a + b + c+ d"))
产生
right => [1.13] parsed: Right(a, Right(b, Right(c, d)))
left => [1.13] parsed: Left(Left(Left(a, b), c), d)