2011-10-18 8 views
14

Supongamos que estoy escribiendo un analizador sintáctico de SQL rudimentario en Scala. Tengo el siguiente:coincidencia no codiciosa en 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 
} 

Al tratar de igualar selectstatement contra SELECT foo FROM bar, ¿cómo puedo evitar la selectclause de engullir toda la frase debido a la rep(token) en ~ tokens?

En otras palabras, ¿cómo especifico la coincidencia no codiciosa en Scala?

Para aclarar, soy plenamente consciente de que puedo usar la sintaxis estándar no codiciosa (*?) O (+?) Dentro del patrón String, pero me pregunto si hay una forma de especificarlo en un nivel superior dentro def tokens. Por ejemplo, si yo hubiera definido token de la siguiente manera:

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

Entonces, ¿cómo puedo especificar coincidencia no expansivo para el representante (token) en el interior fichas def?

+0

Parece que estamos tratando [con la característica de PEG] (https://en.wikipedia.org/wiki/Parsing_expression_grammar#Operational_interpretation_of_parsing_expressions) aquí: Mientras comparadores de expresiones regulares pueden comenzar haciendo coincidir con avidez, pero luego dar marcha atrás y pruebe coincidencias más cortas si fallan y CFG intenta todas las posibilidades, los operadores '*', '+' y '' 'de PEG siempre se comportan con avidez, consumiendo la mayor cantidad de información posible y nunca retrocediendo: la expresión' a * 'siempre consumirá muchas a's están disponibles consecutivamente en la cadena de entrada, lo que hace que '(a * a)' falle siempre. –

Respuesta

12

No es fácil, porque una coincidencia exitosa no se reintenta. Consideremos, por ejemplo:

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 
^ 

El primer partido tuvo éxito, en analizador entre paréntesis, por lo que procedieron a la siguiente. Ese falló, por lo que p falló. Si p fue parte de coincidencias alternativas, se probaría la alternativa, por lo que el truco es producir algo que pueda manejar ese tipo de cosas.

Digamos que tenemos esto:

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

A continuación, puede utilizar de esta manera:

def p = nonGreedy("a", "ab") 

Por cierto, siempre he encontrado que mirar cómo otras cosas se definen es útil tratando de llegar a cosas como nonGreedy arriba. En particular, observe cómo se define rep1 y cómo se modificó para evitar la reevaluación de su parámetro de repetición; lo mismo probablemente sea útil en nonGreedy.

Aquí hay una solución completa, con un pequeño cambio para evitar consumir el "terminal".

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

Noté que cambiar a def p = ("a" ||| "aa" ||| "aaa") ~ "ab" sí analiza en tu ejemplo, pero no tengo claro qué ||| el operador realmente está haciendo o si tiene alguna relación con la pregunta original – Magnus

+0

@Magnus '|||' seleccionará la coincidencia más grande, que es la correcta. Agrega uno '||| "aaaa" 'y verá que falla. –

+1

Gracias por esta solución def nonGreedy. No entiendo cómo aplicarlo ... nonGreedy toma dos argumentos, pero el representante (token) que estoy tratando de hacer no codicioso toma una arg. ¿Cuáles deberían ser los argumentos en mi caso particular? – Magnus

Cuestiones relacionadas