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:
parsley.syntax.character: contains conversions that allow for character and string literals to serve as parsers.parsley.syntax.lift: enables theliftmethod on functions to allow them to work on parsers.parsley.syntax.zipped: enables thezippedmethod on tuples of parsers to sequence and combine their results with a single function.
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.