2009-11-25 9 views
9

Estoy tratando de definir una gramática para los siguientes comandos.Scala Parser Token Delimiter Problema

object ParserWorkshop { 
    def main(args: Array[String]) = { 
     ChoiceParser("todo link todo to database") 
     ChoiceParser("todo link todo to database deadline: next tuesday context: app.model") 
    } 
} 

El segundo comando debe tokenizados como:

action = todo 
message = link todo to database 
properties = [deadline: next tuesday, context: app.model] 

Cuando ejecuto esta entrada en la gramática se define a continuación, aparece el siguiente mensaje de error:

[1.27] parsed: Command(todo,link todo to database,List()) 
[1.36] failure: string matching regex `\z' expected but `:' found 

todo link todo to database deadline: next tuesday context: app.model 
           ^

Por lo Puedo ver que falla porque el patrón para hacer coincidir las palabras del mensaje es casi idéntico al patrón de la clave de la clave de propiedad: par de valores, por lo que el analizador no puede decir dónde termina el mensaje y la propiedad comienza. Puedo resolver esto insistiendo en esa señal de inicio se utilizará para cada propiedad, así:

todo link todo to database :deadline: next tuesday :context: app.model 

Pero yo preferiría mantener el comando tan cerca como sea posible del lenguaje natural. Tengo dos preguntas:

¿Qué significa realmente el mensaje de error? ¿Y cómo modificaría la gramática existente para que funcione con las cadenas de entrada dadas?

import scala.util.parsing.combinator._ 

case class Command(action: String, message: String, properties: List[Property]) 
case class Property(name: String, value: String) 

object ChoiceParser extends JavaTokenParsers { 
    def apply(input: String) = println(parseAll(command, input)) 

    def command = action~message~properties ^^ {case a~m~p => new Command(a, m, p)} 

    def action = ident 

    def message = """[\w\d\s\.]+""".r 

    def properties = rep(property) 

    def property = propertyName~":"~propertyValue ^^ { 
     case n~":"~v => new Property(n, v) 
    } 

    def propertyName: Parser[String] = ident 

    def propertyValue: Parser[String] = """[\w\d\s\.]+""".r 
} 
+0

Creo que deberías cambiar tu sintaxis a algo como esto: todo "link todo a la base de datos": fecha límite: "next tuesday": context: "app.modelo " – ziggystar

+0

Esta es una solución que quiero evitar, ya que quiero mantener la gramática de Todo lo más cercana posible a un lenguaje natural. –

Respuesta

21

Es realmente simple. Cuando usa ~, debe comprender que no hay retrocesos en los analizadores individuales que se han completado satisfactoriamente.

Así, por ejemplo, message tiene todo listo antes de los dos puntos, ya que todo eso es un patrón aceptable. A continuación, properties es rep de property, que requiere propertyName, pero solo encuentra los dos puntos (el primer carácter no engullido por message). Por lo tanto, propertyName falla y property falla. Ahora, properties, como se mencionó, es rep, por lo que termina satisfactoriamente con 0 repeticiones, lo que hace que command termine con éxito.

Entonces, volviendo a parseAll. El analizador command volvió con éxito, habiendo consumido todo antes del colon. Luego se hace la pregunta: ¿estamos al final de la entrada (\z)? No, porque hay dos puntos a continuación. Por lo tanto, esperaba el final de la entrada, pero obtuvo dos puntos.

Tendrá que cambiar la expresión regular para que no consuma el último identificador antes de dos puntos. Por ejemplo:

def message = """[\w\d\s\.]+(?![:\w])""".r 

Por cierto, cuando se utiliza def se fuerza la expresión a ser reevaluado. En otras palabras, cada uno de estos defs crea un analizador cada vez que se llama a cada uno. Las expresiones regulares se instancian cada vez que se procesan los analizadores a los que pertenecen. Si cambia todo al val, obtendrá un rendimiento mucho mejor.

Recuerde, estas cosas definen el analizador, no plazo ella. Es parseAll que ejecuta un analizador.

+0

Gracias Daniel, explicación muy clara y bien escrita –