Scala Parser问题

时间:2009-08-28 16:30:48

标签: scala parsing

我遇到了为简单的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函数与其他示例几乎相同?

这是构建像这样的简单解析器的最佳方法吗?

由于

2 个答案:

答案 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) 
}