Synactic Extensions (parsley.syntax)

The parsley.syntax package contains several modules that enable new "syntax" on parsers or other values. There are currently four such modules:

Implicit Conversions

The charLift and stringLift conversions in parsley.syntax.character allow for Scala character and string literals to work directly as parsers for those specific literals. For example:

import parsley.syntax.character._

val p = 'a' ~> "bc"
// p: parsley.Parsley[String] = parsley.Parsley@602b6ed
p.parse("abc")
// res0: parsley.Result[String, String] = Success(bc)
p.parse("axy")
// res1: parsley.Result[String, String] = Failure((line 1, column 2):
//   unexpected "xy"
//   expected "bc"
//   >axy
//     ^^)

In the above, 'a': Parsley[Char], and "bc": Parsley[String].

If you see an error like this, when you otherwise have the implicit imported:

val p = "cb" <~ 'a'
p.parse("cba")
// error: value <~ is not a member of String
// val p = "cb" <~ 'a'
//         ^^^^^^^

Then this likely means that you have another conversion in scope and the ambiguity is not resolved. If the arguments reversed, this will become more evident:

val p = 'a' ~> "bc"
p.parse("abc")
// error: type mismatch;
//  found   : String("bc")
//  required: parsley.Parsley[?]
// Note that implicit conversions are not applicable because they are ambiguous:
//  both method stringLift in object character of type (str: String): parsley.Parsley[String]
//  and method implicitSymbol in class ImplicitSymbol of type (s: String): parsley.Parsley[Unit]
//  are possible conversion functions from String("bc") to parsley.Parsley[?]

In this case, a lexer.lexeme.symbol.implicits is imported and is clashing.

Improved Sequencing

Both the lift and zipped modules within parsley.implicits enable new ways of sequencing parsers in an idiomatic way. The lift syntax is perhaps more natural, where the function to apply appears to the left of the arguments:

import parsley.character.char
import parsley.syntax.lift._

val add = (x: Int, y: Int) => x + y
// add: (Int, Int) => Int = <function2>
add.lift(char('a').as(5), char('b').as(6)).parse("ab")
// res4: parsley.Result[String, Int] = Success(11)

However, while lift works well when the function has its type fully elaborated, it does not infer well:

(_ + _).lift(char('a').as(5), char('b').as(6)).parse("ab")
// error: missing parameter type for expanded function ((<x$2: error>, x$3) => x$2.$plus(x$3))
// (_ + _).lift(char('a').as(5), char('b').as(6)).parse("ab")
//      ^
// error: missing parameter type for expanded function ((<x$2: error>, <x$3: error>) => x$2.$plus(x$3))

This is where zipped comes in: by placing the function to the right of its arguments, it can infer the type of the function based on the arguments. This may appear slightly less natural, however:

import parsley.syntax.zipped._
(char('a').as(5), char('b').as(6)).zipped(_ + _).parse("ab")
// res6: parsley.Result[String, Int] = Success(11)

The zipped syntax, unlike the liftN combinators or lift syntax, is not lazy in any of its arguments, so care may be needed to use LazyParsley.unary_~ to restore laziness to those arguments that need it.

Both lift and zipped work for up to 22-argument functions.