Well, I followed Eric Meyer's advice on how to follow these views on a happy journey. After seeing how ^^ is defined in Scala programming (which is different from the actual code), I realized that this is 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 is Parser[T] => Parser[U] .
Parser[T] itself is a function Input => ParseResult[T] , and ^^ simply defines a new parser by providing an apply method that, when invoked, converts Success[T] to Success[U] or simply runs along Failure .
To achieve the goal of entering a new Failure during matching, I need a new mapping function that accepts a function like f: T => Either[String,U] so that I can signal an error message or a successful match. I chose Either with a string, since Failure just accepts 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"}) } }