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 thelift
method on functions to allow them to work on parsers.parsley.syntax.zipped
: enables thezipped
method 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._
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.