Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apply parser combinator to case class

I'm looking at using Scala's Parser Combinators to parse a string (no newlines, contrived example).

The string is made up of many different parts which I want to extract separately and populate a case class.

case class MyRecord(foo: String, bar: String, baz: String, bam: String, bat: String)

object MyParser extends scala.util.parsing.combinator.RegexParsers {

  val foo: Parser[String] = "foo"
  val bar: Parser[String] = "bar"
  val baz: Parser[String] = "baz"
  val bam: Parser[String] = "bam"
  val bat: Parser[String] = "bat"

  val expression: Parser[MyRecord] =
    foo ~ bar ~ baz ~ bam ~ bat ^^ {
      case foo ~ bar ~ baz ~ bam ~ bat => MyRecord(foo, bar, baz, bam, bat)
    }

}

This works perfectly well, but is there a way to apply the parts of the matched results directly to the case class without deconstructing?

val expression: Parser[MyRecord] =
  foo ~ bar ~ baz ~ bam ~ bat ^^ MyRecord

Further information: The string I'm parsing is quite long and complex (in reality, it's a whole file full of long complex strings) so changing to regexp is out of the question.

like image 976
adlawson Avatar asked Jul 10 '15 14:07

adlawson


1 Answers

It's possible with Shapeless2 library. For given:

 object MyParser extends scala.util.parsing.combinator.RegexParsers   
 import MyParser._     

 val foo: Parser[String] = "foo"
 val bar: Parser[String] = "bar"
 val car: Parser[String] = "car"

 case class Record(f: String, b: String, c: String)

You can combine parsers using generic foldRight intead of ~:

 import shapeless._
 object f extends Poly2 { 
  implicit def parser[T, U <: HList] = 
   at[Parser[T], Parser[U]]{(a, b) => 
     for {aa <- a; bb <- b} yield aa :: bb  
   }
 }

 val p: Parser[Record] = (foo :: bar :: car :: HNil)
    .foldRight(success(HNil))(f).map(Generic[Record].from)

Result:

 scala> parseAll(p, "foo bar car").get
 res50: Record = Record(foo,bar,car)

P.S. The problem with built-in scala functionality is that they built ~-based typed binary tree, which is hard to traverse and flatten to tuple. Shapeless solves this problem - it has it's own ::-based binary tree called HList, it's simmilar but has interesting operations, like convertion to tuples or case classes (probably macro-based). In this example I use foldLeft to build Shapeless-hlist and for-comprehension (expands to flatMap on parser) to combine parsers as they have monadic nature. In shapeless you have to define foldLeft's handler as set of generic implicits, that can process generic input (like T or U).

You can reuse my f object to combine any parsers in typesafe way (you can combine even different types here - that's fine).


Second, less generic, way is:

implicit class as2[A, B](t: Parser[A ~ B]){ def ^^^^[T] (co: (A, B) => T) = t map {tt => val (a ~ b) = tt; co(a, b)} }
implicit class as3[A, B, C](t: Parser[A ~ B ~ C]){ def ^^^^[T] (co: (A, B, C) => T) = t map {tt => val (a ~ b ~ c) = tt; co(a, b, c)} }
...
implicit class as21 ...

Usage:

scala> val p = foo ~ bar ~ car ^^^^ Record
p: MyParser.Parser[Record] = Parser ()

scala> parseAll(p, "foo bar car").get
res53: Record = Record(foo,bar,car)

It's not so cool, but doesn't require external libraries.

like image 123
dk14 Avatar answered Sep 27 '22 23:09

dk14