tl; dr
I do not know how packrat can save you from two big issues that you have. It did save me from stackoverflow but I did not have such blatant left recusion.
I mean that your recursion expr + expr
should never terminate. I understand that you have some base of induction somewhere, i.e. expr = expr + expr | term
.
Now, you can easily make right associativity by term + expr | term
for right associative because when last term is found, you are deep under + recursion. Similarly, you make left associativity expr + term | term
. Left associative causes left recursion and you are never at the final term. Even packrat does not save from it. I do not understand how you get your results. Mine
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))
}
stack overflows. It saved me once but I did not have such blatant left recusion. And, I do not know how can it save you from the second issue that you ask.
Anyway, you have to eliminate the recursion changing expr + term | term
to
def left: Parser[_] = ident ~ appendix
def appendix = "+" ~> left | ""
But this is however the right recursion again because we see the ident is the first node again.
Solution:
You therefore simply use what all people do: use rep
parser, which provides you a list, iterable from the left:
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"))
produces
right => [1.13] parsed: Right(a, Right(b, Right(c, d)))
left => [1.13] parsed: Left(Left(Left(a, b), c), d)