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 № 1Jest 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.