/ / Zastosuj kombinator parsera do klasy case - scala, parser-combinator

Zastosuj kombinator parsera do klasy case - scala, parser-combinator

Patrzę na używanie Scali Kombinatory parserów parsować ciąg znaków (bez nowych linii, zaimplementowany przykład).

Ciąg składa się z wielu różnych części, które chcę wyodrębnić osobno i wypełnić klasę przypadku.

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)
}

}

Działa to doskonale, ale czy istnieje sposób zastosowania części dopasowanych wyników bezpośrednio do klasy spraw bez konieczności dekonstrukcji?

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

Dalsza informacja: Ciąg, który "parsuję" jest dość długi i złożony (w rzeczywistości jest to cały plik pełen długich, złożonych ciągów), więc zmiana na wyrażenie regularne nie wchodzi w grę.

Odpowiedzi:

5 dla odpowiedzi № 1

Jest to możliwe dzięki Shapeless2 biblioteka. Dla danych:

 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)

Można łączyć analizatory składni za pomocą generycznych 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)

Wynik:

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

P.S. Problem z wbudowaną funkcjonalnością scala polega na tym, że zostały one zbudowane ~oparte na typie drzewo binarne, które trudno przemierzyć i spłaszczyć do krotki. Bezkształtny rozwiązuje ten problem - ma swoje ::-based drzewo binarne o nazwie HList, jest podobny, ale ma ciekawe operacje, takie jak konwersja do krotek lub klas przypadków (prawdopodobnie opartych na makrach) .W tym przykładzie używam foldLeft zbudować Shapeless-hlist i dla zrozumienia (rozszerza się do flatMap na parserze), aby połączyć parsery, ponieważ mają monadyczną naturę. W bezkształtnym musisz zdefiniować foldLeft"s handler jako zestaw ogólnych implicite, które mogą przetwarzać ogólne dane wejściowe (np T lub U).

Możesz ponownie użyć mojego f spróbuj połączyć dowolne parsery w sposób bezpieczny (możesz łączyć różne typy tutaj - to w porządku).


Drugi, mniej ogólny, sposób to:

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

Stosowanie:

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)

To nie jest takie fajne, ale nie wymaga zewnętrznych bibliotek.