我遇到了为简单的Book DSL测试Scala Parser Combinator功能的问题。
首先是一本书类:
case class Book (name:String,isbn:String) {
def getNiceName():String = name+" : "+isbn
}
接下来,有一个简单的解析器:
object BookParser extends StandardTokenParsers {
lexical.reserved += ("book","has","isbn")
def bookSpec = "book" ~> stringLit ~> "has" ~> "isbn" ~> stringLit ^^ {
case "book" ~ name ~ "has" ~ "isbn" ~ isbn => new Book(name,isbn) }
def parse (s: String) = {
val tokens = new lexical.Scanner(s)
phrase(bookSpec)(tokens)
}
def test (exprString : String) = {
parse (exprString) match {
case Success(book) => println("Book"+book.getNiceName())
}
}
def main (args: Array[String]) = {
test ("book ABC has isbn DEF")
}
}
我在尝试编译时遇到了一系列错误 - 有些在尝试解构互联网上的其他示例时对我来说似乎很奇怪。例如,bookSpec函数与其他示例几乎相同?
这是构建像这样的简单解析器的最佳方法吗?
由于
答案 0 :(得分:15)
你走在正确的轨道上。解析器中存在一些问题。我将发布更正的代码,然后解释更改。
import scala.util.parsing.combinator._
import scala.util.parsing.combinator.syntactical._
case class Book (name: String, isbn: String) {
def niceName = name + " : " + isbn
}
object BookParser extends StandardTokenParsers {
lexical.reserved += ("book","has","isbn")
def bookSpec: Parser[Book] = "book" ~ ident ~ "has" ~ "isbn" ~ ident ^^ {
case "book" ~ name ~ "has" ~ "isbn" ~ isbn => new Book(name, isbn) }
def parse (s: String) = {
val tokens = new lexical.Scanner(s)
phrase(bookSpec)(tokens)
}
def test (exprString : String) = {
parse (exprString) match {
case Success(book, _) => println("Book: " + book.niceName)
case Failure(msg, _) => println("Failure: " + msg)
case Error(msg, _) => println("Error: " + msg)
}
}
def main (args: Array[String]) = {
test ("book ABC has isbn DEF")
}
}
<强> 1。分析器返回值
为了从解析器返回一本书,您需要为类型推理器提供一些帮助。我将bookSpec函数的定义更改为显式:它返回一个Parser [Book]。也就是说,它返回一个对象,它是书籍的解析器。
<强> 2。 stringLit 强>
您使用的stringLit函数来自StdTokenParsers特征。 stringLit是一个返回Parser [String]的函数,但它匹配的模式包括大多数语言用来分隔字符串文字的双引号。如果您对DSL中的双引号字感到满意,那么stringLit就是您想要的。为了简单起见,我用ident替换了stringLit。 ident查找Java语言标识符。这不是ISBN的正确格式,但它确实通过了您的测试用例。 : - )
为了正确匹配ISBN,我认为你需要使用正则表达式而不是idents。
第3。忽略左序列
你的匹配器使用了一串〜&gt;合。这是一个函数,它接受两个Parser [_]对象并返回一个Parser,它按顺序识别它们,然后返回右侧的结果。通过使用它们的整个链引导到最终的stringLit,您的解析器将忽略除句子中的最后一个单词之外的所有内容。这意味着它也会丢掉书名。
另外,当你使用〜&gt;或者&lt;〜,忽略的标记不应出现在模式匹配中。
为简单起见,我将这些全部更改为简单的序列函数,并在模式匹配中留下额外的标记。
<强> 4。匹配结果
测试方法需要匹配parse()函数的所有可能结果。所以,我添加了Failure()和Error()案例。此外,甚至成功还包括两者您的返回值和Reader对象。我们不关心读者,所以我只是在模式匹配中使用“_”忽略它。
希望这有帮助!
答案 1 :(得分:6)
当您使用~>
或<~
时,您将丢弃箭头所在的元素。例如:
"book" ~> stringLit // discards "book"
"book" ~> stringLit ~> "has" // discards "book" and then stringLit
"book" ~> stringLit ~> "has" ~> "isbn" // discards everything except "isbn"
"book" ~> stringLit ~> "has" ~> "isbn" ~> stringLit // discards everything but the last stringLit
你可以这样写:
def bookSpec: Parser[Book] = ("book" ~> stringLit <~ "has" <~ "isbn") ~ stringLit ^^ {
case name ~ isbn => new Book(name,isbn)
}