I need to do more complex syntax checking on a parser match than the standard notation permits and am currently doing it in the function application ^^
. A sample, simplified scenario is checking for duplicate keywords:
def keywords: Parser[List[String]] = "[" ~ repsep(keyword, ",") ~ "]" ^^ {
case _ ~ ks ~ _ =>
ks.groupBy(x => x).filter(_._2.length > 1).keys.toList match {
case Nil => ks
case x => throw new DuplicateKeywordsException(x)
}
}
This works, as in my parser will throw an exception, but I want the failure to be captured as a ParseResult.Failure capturing the Input of where it happened. I can't figure out how to signal this from within a ^^
block or use some other construct to achieve the same end.
Ok, I followed Erik Meijer's advice of following the Types down the happy path. Looking at how ^^
is defined in Programming in Scala (which differs from the actual code), i realized it was basically just a Map
function:
def ˆˆ [U](f: T => U): Parser[U] = new Parser[U] {
def apply(in: Input) = p(in) match {
case Success(x, in1) => Success(f(x), in1)
case failure => failure
}
}
Basically it's Parser[T] => Parser[U]
.
Parser[T]
itself is a function of Input => ParseResult[T]
and ^^
just defines a new parser by supplying the apply
method, which on invocation either transforms Success[T]
to Success[U]
or just passes along Failure
.
To achieve the goal of injecting a new Failure
during mapping I need a new mapping function that takes a function like f: T => Either[String,U]
so I can signal an error message or successful mapping. I chose Either with string, since Failure just takes a string message. This new mapping function is then added to Parser[U]
via an implicit class:
implicit class RichParser[+T](p: Parser[T]) {
def ^^? [U](f: T => Either[String,U]): Parser[U] = new Parser[U] {
def apply(in: Input) = p(in) match {
case Success(x, in1) => f(x) match {
case Left(error) => Failure(error,in1)
case Right(x1) => Success(x1,in1)
}
case failure:Failure => failure
case error:Error => error
}
}
}
And now keywords
can be defined as:
def keywords: Parser[List[String]] = "[" ~ repsep(keyword, ",") ~ "]" ^^? {
case _ ~ ks ~ _ =>
ks.groupBy(x => x).filter(_._2.length > 1).keys.toList match {
case Nil => Right(ks)
case x => Left("found duplicate keywords: "+x.reduce[String] { case (a, b) => s"$a, $b"})
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With