Не легко, потому что успешное совпадение не повторяется.Рассмотрим, например:
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
^
Первое совпадение было успешным, в парсере внутри круглых скобок, поэтому оно перешло к следующему.Это не удалось, поэтому p
не удалось.Если бы p
было частью альтернативных совпадений, альтернатива была бы опробована, поэтому хитрость заключается в том, чтобы создать нечто, способное справиться с подобными вещами.
Предположим, у нас есть это:1010 * Затем вы можете использовать это следующим образом:
def p = nonGreedy("a", "ab")
Кстати, я всегда обнаруживал, что рассмотрение того, как определяются другие вещи, полезно при попытке придумать что-то вроде nonGreedy
выше.В частности, посмотрите, как определяется rep1
и как он был изменен, чтобы избежать переоценки параметра повторения - то же самое, вероятно, будет полезно для nonGreedy
.
Вот полное решение,с небольшим изменением, чтобы избежать потребления "терминала".
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)
}