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.
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.
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