我在测试简单的Book DSL的Scala Parser Combinator功能时遇到了问题.
首先是一本书类:
case class Book (name:String,isbn:String) {
def getNiceName():String = name+" : "+isbn
}
Run Code Online (Sandbox Code Playgroud)
接下来,有一个简单的解析器:
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")
}
}
Run Code Online (Sandbox Code Playgroud)
我在尝试编译时遇到了一系列错误 - 当我试图解构互联网上的其他例子时,这些错误对我来说似乎很奇怪.例如,bookSpec函数与其他示例几乎相同?
这是构建这样的简单解析器的最佳方法吗?
谢谢
小智 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")
}
}
Run Code Online (Sandbox Code Playgroud)
1.分析器返回值
为了从解析器返回一本书,您需要为类型推理器提供一些帮助.我将bookSpec函数的定义更改为显式:它返回一个Parser [Book].也就是说,它返回一个对象,它是书籍的解析器.
2. stringLit
您使用的stringLit函数来自StdTokenParsers特征.stringLit是一个返回Parser [String]的函数,但它匹配的模式包括大多数语言用来分隔字符串文字的双引号.如果您对DSL中的双引号字感到满意,那么stringLit就是您想要的.为了简单起见,我用ident替换了stringLit.ident查找Java语言标识符.这不是ISBN的正确格式,但它确实通过了您的测试用例.:-)
为了正确匹配ISBN,我认为您需要使用正则表达式而不是ident.
3.忽略左序列
你的匹配器使用了一串〜>组合器.这是一个函数,它接受两个Parser [_]对象并返回一个Parser,它按顺序识别它们,然后返回右侧的结果.通过使用它们的整个链引导到最终的stringLit,您的解析器将忽略除句子中的最后一个单词之外的所有内容.这意味着它也会丢掉书名.
此外,当您使用〜>或<〜时,忽略的标记不应出现在模式匹配中.
为简单起见,我将这些全部更改为简单的序列函数,并在模式匹配中留下额外的标记.
4.匹配结果
测试方法需要匹配parse()函数的所有可能结果.所以,我添加了Failure()和Error()案例.此外,即使成功包括两个您的返回值和Reader对象.我们不关心读者,所以我只是在模式匹配中使用"_"来忽略它.
希望这可以帮助!
当您使用~>或时<~,您将丢弃箭头所在的元素.例如:
"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
Run Code Online (Sandbox Code Playgroud)
你可以像这样写:
def bookSpec: Parser[Book] = ("book" ~> stringLit <~ "has" <~ "isbn") ~ stringLit ^^ {
case name ~ isbn => new Book(name,isbn)
}
Run Code Online (Sandbox Code Playgroud)