2009-08-28 81 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