Chain Combinators

The parsley.expr.chain module contains a variety of combinators for abstracting, most commonly, the application of operators to values in expressions. This allows parsley to handle left recursion idiomatically. To distinguish between these chains and the functionality found in parsley.expr.infix, it is recommended to always import this module qualified as import parsley.expr.chain -- except for postfix and prefix.

The Scaladoc for this page can be found at parsley.expr.chain.

Binary Chains

The first kind of chains found in chain are the binary chains, which handle infix application of a binary operator to values in either a left- or right-associative way. These are called chain.left1 or chain.right1. The 1 here means that there must be at least one value present, though there may be no operators. As an example:

p op p op p op p op p

The above can be parsed using chain.left1(p)(op) to have the effect of parsing like:

(((p op p) op p) op p) op p

It can also be parsed using chain.right1(p)(op) to have the effect of parsing like:

p op (p op (p op (p op p)))

Both of these combinators share the same type, where the parser p: Parsley[A], and the parser op: Parsley[(A, A) => A]. This means that the two combinators can be freely swapped between in an implementation. This is useful when the grammar being encoded for is fully-associative and the associativity within the parser is an implementation detail.

However, if more type-safety is desired, the infix.left1 and infix.right1 combinators may be more appropriate.

Unary Chains

The other kind of chains found in chain are unary chains, which handle repeated prefix or postfix application of an operator to a single value. These are called chain.prefix and chain.postfix. There also 1 variants of these combinators, which will be discussed later.

Given input of shape:

p op op op op

The combinator postfix(p)(op) will parse the input and apply results such that it would look like:

(((p op) op) op) op

Similarly, given input of shape:

op op op op p

The combinator prefix(p)(op) will parse the input and apply results such that it would look like:

op (op (op (op p)))

Unlike chain.left1 and chain.right1, there is no infix equivalent for prefix and postfix. This is because a refined type will not add much to the way the combinators operate.

Ensuring an operator exists

Unlike the difference between chain.left and chain.left1, which allows for an absence of terminal value; prefix vs prefix1 describe whether or not an operator is required or not. This is enforced by the type signature of the operations themselves:

def prefix1[A, B <: A](p: Parsley[A])(op: Parsley[A => B]): Parsley[B]

Given that B is a subtype of A, it is not possible for the p's result of type A to be the final return value of type B. As such, an operator must be parsed which wraps the A into a B. The subtying relation then allows a nested application of an operator to be upcast into an A so it can be fed into another layer of operator.