Scala Parser问题

Sha*_*unL 5 parsing scala

我在测试简单的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对象.我们不关心读者,所以我只是在模式匹配中使用"_"来忽略它.

希望这可以帮助!


Dan*_*ral 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
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)