2009-08-28 83 views
5

我有检验出的Scala解析器组合功能的一个简单的图书DSL的问题。Scala的解析器问题

首先有一本书类:

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函数与其他示例几乎相同?

这是建立一个简单的解析器这样的最佳方式?

感谢

回答

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分析器返回值

为了从解析器返回一本书,你需要给inferencer一些帮助的类型。我将bookSpec函数的定义更改为显式:它返回一个Parser [Book]。也就是说,它返回一个作为书籍解析器的对象。

2. stringLit

您使用的stringLit功能来自StdTokenParsers特质。 stringLit是返回解析器[字符串]功能,但它相匹配的模式包括双引号,大多数语言使用分隔字符串文本。如果你对DSL中的双引号词很满意,那么stringLit就是你想要的。为了简单起见,我用ident替换了stringLit。 ident查找Java语言标识符。这不是真正的ISBN格式,但它确实通过了你的测试用例。 :-)

正确匹配的ISBN,我想你会需要使用正则表达式,而不是表达的idents。

3.忽略 - 左的顺序

你匹配使用〜>组合的字符串。这是一个函数,它有两个分析器[_]对象并返回识别两个序列中,然后返回右侧的结果的分析器。通过使用他们的整个链条导致了你最后stringLit,解析器会忽略除了在句子中的最后一句话一切。这意味着它也会丢掉书名。

此外,当您使用〜>或<〜时,忽略的令牌不应出现在模式匹配中。

为简单起见,我将这些全部更改为简单的序列函数,并在模式匹配中留下额外的标记。

4.匹配结果

的测试方法需要匹配从parse()函数的所有可能结果。所以,我添加了Failure()和Error()情况。此外,即使成功包括您的返回值和Reader对象。我们不关心读者,所以我只是使用“_”在模式匹配中忽略它。

希望这会有所帮助!

+0

优秀的答案谢谢 - 已经历所有的当前和未来的Scala的书籍,这是比两个更好的答案我有哪些处理方式(Martin Odersky以及来自Wampler&Payne的) – ShaunL 2009-08-28 23:48:47

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) 
} 
+0

谢谢丹尼尔对你的回答 - 与mtnygard一样,答案对我很有帮助 – ShaunL 2009-08-28 23:46:53

+0

我认为现在这是错误的, ''是关联的,所以如果我们有'book'〜> stringLit <〜“具有”<〜“isbn”〜stringLi'这相当于'“book”〜>(stringLit <〜(“has” 〜(“isbn”〜stringLi)))'因此isbn不会被返回。测试过Scala 2.9.2 – 2012-07-15 02:00:31

+1

@GuillaumeMassé有一个问题,但是你的优先级分解是错误的。只是'〜'具有比'<〜'更高的优先级,所以行的末尾变成'“具有”<〜(“isbn”〜stringLit)'。我添加了一组括号来避免它。此外,这段时间可能是错误的 - 在相当长的一段时间内,语言中没有优先级改变。 – 2012-07-15 18:30:56