斯卡拉解析器令牌分隔符问题

问题描述:

我想为下面的命令定义一个语法。斯卡拉解析器令牌分隔符问题

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

当运行如下所定义的语法这个输入时,收到以下错误消息::

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

至于

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

第二命令作为应该标记化我可以看到它失败了,因为用于匹配消息的单词的模式与属性键:值对的模式几乎相同,所以解析器无法知道消息的结束位置并开始物业。我可以坚持起始令牌被用来为每个属性像这样解决这个问题:

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

但我宁愿保留命令接近自然语言越好。 我有两个问题:

错误信息实际上是什么意思? 而我将如何修改现有语法以适用于给定的输入字符串?

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

我认为你应该改变你的语法是这样的: todo“链接待办事项数据库”:截止日期:“下周二”:上下文:“应用程序。模型“ – ziggystar 2009-11-26 13:28:02

+0

这是一个我想避免的解决方案,因为我想尽可能使Todo语法尽可能地接近自然语言。 – 2009-11-27 10:14:31

这很简单。当您使用~时,您必须明白,已成功完成的单个解析器没有回溯。

因此,例如,message将所有内容都放到了冒号之前,因为所有这些都是可接受的模式。接下来,propertiespropertyrep,它需要propertyName,但它只能找到冒号(第一个字符不会被message吞噬)。所以propertyName失败,property失败。现在,如上所述,propertiesrep,因此它以0次重复成功完成,然后使command成功完成。因此,回parseAllcommand解析器成功返回,消耗了冒号前的所有内容。然后它会问这个问题:我们是否在输入的末尾(\z)?不,因为下一个冒号。所以,它预期的结束输入,但得到了冒号。

您必须更改正则表达式,以便它不会消耗冒号前的最后一个标识符。例如:

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

顺便说一句,当你使用def你强迫表达重新评估。换句话说,每次调用每个def都会创建一个解析器。正则表达式在每次处理它们所属的解析器时被实例化。如果您将所有内容更改为val,则您的性能将得到提高。

请记住,这些东西定义为的解析器,他们不会运行它。运行解析器的是parseAll

+0

感谢Daniel,非常清晰,写得很好的解释 – 2009-11-25 21:11:42