2011-10-18 124 views
14

假設我在Scala中編寫了一個基本的SQL分析器。我有以下幾點:Scala中的非貪婪匹配RegexParsers

class Arith extends RegexParsers { 
    def selectstatement: Parser[Any] = selectclause ~ fromclause 
    def selectclause: Parser[Any] = "(?i)SELECT".r ~ tokens 
    def fromclause: Parser[Any] = "(?i)FROM".r ~ tokens 
    def tokens: Parser[Any] = rep(token) //how to make this non-greedy? 
    def token: Parser[Any] = "(\\s*)\\w+(\\s*)".r 
} 

當試圖對陣SELECT foo FROM bar selectstatement,我該如何防止吞噬了整句話的selectclause由於~ tokensrep(token)

換句話說,我該如何在Scala中指定非貪婪匹配?爲了澄清,我完全知道我可以在字符串模式本身中使用標準的非貪婪語法(*?)或(+?),但是我想知道是否有辦法在更高級別指定它在def令牌中。例如,如果我是這樣定義的標記:

def token: Parser[Any] = stringliteral | numericliteral | columnname 

然後,我怎麼可以指定代表(令牌)DEF令牌內非貪婪匹配?

+0

好像我們正在處理[用PEG功能(https://en.wikipedia.org/wiki/Parsing_expression_grammar#Operational_interpretation_of_parsing_expressions)位置:雖然正則表達式匹配器可以通過匹配貪婪地啓動,但會再原路返回如果它們失敗並嘗試更短的匹配,並且CFG嘗試所有可能性,那麼PEG的'*','+'和'?'操作符總是表現得貪婪,消耗盡可能多的輸入並且永不回溯:表達式'a *'將始終消耗爲許多a在輸入字符串中連續可用,導致'(a * a)'總是失敗。 –

回答

12

不容易,因爲成功的匹配不會重試。舉個例子:

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是替代匹配的一部分,則可以嘗試替代方法,所以訣竅是生成一些可以處理這類事情的東西。

比方說,我們有這樣的:

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

然後,您可以使用它像這樣:

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

我注意到,改變爲def p =(「a」|||「aa」|||「aaa」)〜「ab」在你的例子中解析,但我不清楚什麼|||運算符真的在做什麼,或者它是否對原始問題有任何影響 – Magnus

+0

@Magnus'|||'只會選擇最大的匹配,這恰好是正確的匹配。添加一個'||| 「aaaa」'你會看到它失敗。 –

+1

感謝您使用def nonGreedy解決方案。我不明白如何應用它... nonGreedy有兩個參數,但我試圖使非貪婪的代表(標記)需要一個參數。在我的特定情況下,參數應該是什麼? – Magnus