2009-11-25 6 views
9

J'essaie de définir une grammaire pour les commandes ci-dessous.Scala Parser Token Delimiter Problème

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

La deuxième commande doit être tokenizés comme:

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

Quand je lance cette entrée sur la grammaire définie ci-dessous, je reçois le message d'erreur suivant:

[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 
           ^

En ce qui concerne Je peux voir qu'il échoue parce que le modèle pour faire correspondre les mots du message est presque identique au modèle pour la clé de la clé de propriété: paire de valeurs, de sorte que l'analyseur ne peut pas dire où le message se termine et la propriété commence. Je peux résoudre ce problème en insistant sur le fait que jeton de départ utilisé pour chaque propriété comme ceci:

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

Mais je préférerais garder le commandement le plus proche du langage naturel que possible. J'ai deux questions:

Que signifie réellement le message d'erreur? Et comment est-ce que je modifierais la grammaire existante pour travailler pour les chaînes d'entrée données?

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

Je pense que vous devriez changer votre syntaxe à quelque chose comme ceci: todo "lien todo à la base de données": date limite: "mardi prochain": contexte: "app.model " – ziggystar

+0

C'est une solution que je veux éviter, car je veux garder la grammaire de Todo aussi proche que possible d'un langage naturel –

Répondre

21

C'est vraiment simple. Lorsque vous utilisez ~, vous devez comprendre qu'il n'y a pas de retour arrière sur les parseurs individuels qui se sont terminés avec succès. Ainsi, par exemple, message a tout obtenu avant le deux-points, car tout cela est un modèle acceptable. Ensuite, properties est un rep de property, qui nécessite propertyName, mais il ne trouve que le colon (le premier caractère n'est pas englouti par message). Donc propertyName échoue, et property échoue. Maintenant, properties, comme mentionné, est un rep, donc il se termine avec succès 0 répétitions, ce qui fait ensuite command terminer avec succès.

Donc, retour à parseAll. L'analyseur command est retourné avec succès, ayant tout consommé avant le côlon. Il pose ensuite la question: sommes-nous à la fin de l'entrée (\z)? Non, parce qu'il y a un colon juste à côté. Donc, il s'attendait à la fin de l'entrée, mais a eu deux points.

Vous devrez modifier l'expression rationnelle afin qu'elle ne consomme pas le dernier identifiant avant un deux-points. Par exemple:

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

Par ailleurs, lorsque vous utilisez def vous forcer l'expression à réévaluées. En d'autres termes, chacune de ces définitions crée un analyseur chaque fois que quelqu'un est appelé. Les expressions régulières sont instanciées chaque fois que les parseurs auxquels elles appartiennent sont traités. Si vous changez tout à val, vous obtiendrez de bien meilleures performances.

Rappelez-vous, ces choses définissent l'analyseur, ils ne le font pas course il. C'est parseAll qui exécute un analyseur.

+0

Merci Daniel, explication très claire, bien écrite –