The or combinator tries a first parser and runs a second parser on the same input only when the first fails without commitment. A successful first parser, or a committed failure, ends the choice immediately. This makes branching both controllable and more efficient than blindly exploring every alternative. section 9.6.4
Choosing between two phrases
Let p1 parse “abra cadabra” and p2 parse “abba babba”; p1 | p2 first tries the magic-spell branch. If p1 fails before consuming input, p2 gets the same input. If p1 has consumed input and then fails, the failure is reported without trying p2. Thus an input such as “abra cAdabra” can preserve the more informative failure from the selected branch. section 9.5.3
Implementations represent a failure with both a ParseError and an isCommitted flag. The or combinator inspects that flag; attempt converts committed failures into uncommitted failures, allowing another branch to run when the parser should not yet have committed. section 9.6.4
extension [A](p: Parser[A]) def or(p2: => Parser[A]): Parser[A] =
l => p(l) match
case Failure(e, false) => p2(s)
case r => r #1
Inspect the compact implementation that preserves successes and committed failures while invoking the second parser only for uncommitted failures. section 9.6.4
