Niełatwo, ponieważ pomyślne dopasowanie nie jest ponawiane. Rozważmy na przykład:
object X extends RegexParsers {
def p = ("a" | "aa" | "aaa" | "aaaa") ~ "ab"
}
scala> X.parseAll(X.p, "aaaab")
res1: X.ParseResult[X.~[String,String]] =
[1.2] failure: `ab' expected but `a' found
aaaab
^
Pierwszy mecz był udany, w parsera wewnątrz nawiasu, więc udał się do następnego. To się nie udało, więc nie udało się. Jeśli p
byłby częścią alternatywnych dopasowań, alternatywa byłaby wypróbowana, więc sztuczka polega na stworzeniu czegoś, co poradzi sobie z takimi rzeczami.
Powiedzmy mamy to:
def nonGreedy[T](rep: => Parser[T], terminal: => Parser[T]) = Parser { in =>
def recurse(in: Input, elems: List[T]): ParseResult[List[T] ~ T] =
terminal(in) match {
case Success(x, rest) => Success(new ~(elems.reverse, x), rest)
case _ =>
rep(in) match {
case Success(x, rest) => recurse(rest, x :: elems)
case ns: NoSuccess => ns
}
}
recurse(in, Nil)
}
Następnie można go używać tak:
def p = nonGreedy("a", "ab")
Nawiasem mówiąc, ja zawsze uważałem, że patrząc na to, jak inne rzeczy są zdefiniowane jest pomocne w próbując wymyślić powyższe informacje, takie jak nonGreedy
. W szczególności spójrz, jak zdefiniowano rep1
i jak został zmieniony, aby uniknąć ponownej oceny jego parametru powtarzania - ta sama funkcja byłaby prawdopodobnie przydatna na nonGreedy
.
Oto pełne rozwiązanie, z niewielką zmianą, aby uniknąć zużycia "terminala".
trait NonGreedy extends Parsers {
def nonGreedy[T, U](rep: => Parser[T], terminal: => Parser[U]) = Parser { in =>
def recurse(in: Input, elems: List[T]): ParseResult[List[T]] =
terminal(in) match {
case _: Success[_] => Success(elems.reverse, in)
case _ =>
rep(in) match {
case Success(x, rest) => recurse(rest, x :: elems)
case ns: NoSuccess => ns
}
}
recurse(in, Nil)
}
}
class Arith extends RegexParsers with NonGreedy {
// Just to avoid recompiling the pattern each time
val select: Parser[String] = "(?i)SELECT".r
val from: Parser[String] = "(?i)FROM".r
val token: Parser[String] = "(\\s*)\\w+(\\s*)".r
val eof: Parser[String] = """\z""".r
def selectstatement: Parser[Any] = selectclause(from) ~ fromclause(eof)
def selectclause(terminal: Parser[Any]): Parser[Any] =
select ~ tokens(terminal)
def fromclause(terminal: Parser[Any]): Parser[Any] =
from ~ tokens(terminal)
def tokens(terminal: Parser[Any]): Parser[Any] =
nonGreedy(token, terminal)
}
Wydaje się, że mamy do czynienia [z funkcją PEG] (https://en.wikipedia.org/wiki/Parsing_expression_grammar#Operational_interpretation_of_parsing_expressions) tutaj: Podczas gdy regularne dopasowujące ekspresyjne mogą rozpocząć dopasowując łapczywie, ale potem wracać i próbuj krótszych dopasowań, jeśli zawiodą, a CFG spróbuje każdej możliwości, operatorzy PEG '*', '+' i '?' zawsze zachowują się chciwie, zużywają jak najwięcej danych wejściowych i nigdy nie wycofują się: wyrażenie 'a *' zawsze będzie zużywać jako wiele a, które są kolejno dostępne w ciągu wejściowym powodując, że '(a * a)' zawsze zawodzi. –