There are some combinators that are, due to Scala limitations,
strict in all their parameters. Usually, a combinator is strict
in its "first position", which is to say the first part of the
combinator to be executed; and lazy in all other "positions".
The rationale behind this is that recursion appearing in a
"first position" will result in infinite recursion at parse-time,
it is left-recursive after all, and so it makes little sense to
waste efficiency and complicate the API to support laziness
there. Since method receivers are strict and only
arguments can be lazy under regular conditions, this works well.
However, for combinators that are always strict, this poses a
problem: a recursion point inside one of these strict fields
will cause an infinite loop at runtime! This can be fixed by
ensuring that this becomes part of a lazy argument. This is
a solution described by the skip
combinator, for instance: p *> skip(q, .., r) will ensure
that the skip is in a lazy position in *> meaning that
even if any of q to r must be lazy, they can go in the strict
positions of skip because the p *> provides the required
laziness. However, if this isn't possible (for instance, with
the zipped combinators), then how can
this problem be solved?
This is the job of the ~ combinator: very simply it wraps up
a parser in a lazy box, so that even if the box is forced by
a strict position, the parser will remain lazy. This means it
serves as an adequate solution to this problem.
Attributes
Returns:
the parser p, but guaranteed to be lazy.
Example:
// this works fine, even though all of `zipped`'s parsers are strict
lazy val expr = (attempt(term) <* '+', ~expr).zipped(_ + _) <|> term
// in this case, however, the following would fix the problem more elegantly:
lazy val expr = (attempt(term), '+' *> expr).zipped(_ + _) <|> term