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.given

val p = 'a' ~> "bc"
// p: Parsley[String] = parsley.Parsley@b837db4
p.parse("abc")
// res0: Result[String, String] = Success(bc)
p.parse("axy")
// res1: 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.
// An extension method was tried, but could not be fully constructed:
// 
//     lexer.lexeme.symbol.implicits.implicitSymbol("cb")
// 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: 
// Found:    ("bc" : String)
// Required: parsley.Parsley[Any]
// Note that implicit conversions cannot be applied because they are ambiguous;
// both method stringLift in object character and method implicitSymbol in class ImplicitSymbol convert from ("bc" : String) to parsley.Parsley[Any]

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: Function2[Int, Int, Int] = repl.MdocSession$MdocApp$$Lambda$17032/0x0000000804228840@46d1bc8c
add.lift(char('a').as(5), char('b').as(6)).parse("ab")
// res4: 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: 
// Could not infer type for parameter _$1 of anonymous function
// 
// In expanded function:
// (_$1, _$2) => _$1 + _$2
// 
// Expected type for the whole anonymous function: ?{ lift: ? }
// error:
// Could not infer type for parameter _$2 of anonymous function
// 
// In expanded function:
// (_$1, _$2) => _$1 + _$2
// 
// Expected type for the whole anonymous function: ?{ lift: ? }
// (_ + _).lift(char('a').as(5), char('b').as(6)).parse("ab")
//      ^

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: 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.