2011-07-25 102 views
21

我是相當新的Scala和一邊唸叨解析器組合(The Magic Behind Parser CombinatorsDomain-Specific Languages in Scala)我整個的方法定義出來是這樣的:瞭解在Scala的解析器組合子波浪號

def classPrefix = "class" ~ ID ~ "(" ~ formals ~ ")" 

我一直在閱讀throught的scala.util.parsing.Parsers的API文檔定義了一個名爲(代字號)的方法,但我仍然無法真正瞭解它在上例中的用法。 在那個例子中(代字號)是一個在java.lang.String上調用的方法,它沒有該方法並導致編譯器失敗。 我知道(波浪)被定義爲

case class ~ [+a, +b] (_1: a, _2: b) 

但是,這如何幫助在上面的例子?

如果有人能給我一個提示,讓我知道這裏發生了什麼,我會很高興。 非常感謝您提前!

1月

回答

30

這裏的結構有點棘手。首先,請注意,您總是在某個解析器的子類中定義了這些東西,例如class MyParser extends RegexParsers。現在,你可能會注意到兩個隱含的定義裏面RegexParsers

implicit def literal (s: String): Parser[String] 
implicit def regex (r: Regex): Parser[String] 

什麼這些都會做的是採取任何字符串或正則表達式,並將其轉換成一個解析器,該字符串或正則表達式作爲標記相匹配。它們是隱含的,所以它們會在需要時隨時應用(例如,如果您調用Parser[String]String(或Regex)不具備的方法)。

但這是什麼Parser的事?它的內部Parsers限定的內班,supertrait爲RegexParser

class Parser [+T] extends (Input) ⇒ ParseResult[T] 

看起來它是一個函數,它接受輸入,並將其映射到的結果。那麼,這是有道理的!你可以看到它的文檔here

現在我們只是仰望的~方法:

def ~ [U] (q: ⇒ Parser[U]): Parser[~[T, U]] 
    A parser combinator for sequential composition 
    p ~ q' succeeds if p' succeeds and q' succeeds on the input left over by p'. 

所以,如果我們看到類似

def seaFacts = "fish" ~ "swim" 

發生的事情是,首先,"fish"沒有~方法,所以它被隱含地轉換爲Parser[String]。然後,~方法需要參數類型爲Parser[U],因此我們將"swim"隱式轉換爲Parser[String](即U == String)。現在我們有一些匹配輸入"fish"的東西,並且輸入中留下的任何內容都應該匹配"swim",如果兩者都是這種情況,那麼seaFacts將匹配成功。

+1

非常感謝您的解釋。我對scala的「隱式轉換」功能並不熟悉。 關於該主題的一篇很好的文章可以在這裏找到:http://scalada.blogspot.com/2008/03/implicit-conversions-magical-and.html – Jano

3

您應該結帳Parsers.Parser。 Scala有時會使用相同的名稱來定義方法和案例類以幫助模式匹配等,如果您正在閱讀Scaladoc,那麼它有點令人困惑。

特別是,"class" ~ ID"class".~(ID)相同。 ~是一種將解析器與另一個解析器按順序組合的方法。

RegexParsers中定義了an implicit conversion,該值自動從String值創建解析器。因此,"class"自動成爲Parser[String]的一個實例。

val ID = """[a-zA-Z]([a-zA-Z0-9]|_[a-zA-Z0-9])*"""r 

RegexParsers還定義了自動從Regex值創建解析器另一隱式轉換。因此,ID也自動成爲Parser[String]的一個實例。

通過組合兩個解析器,"class" ~ ID返回Parser[String],它與文字「class」匹配,然後按順序出現正則表達式ID。還有其他方法,如||||。欲瞭解更多信息,請閱讀Programming in Scala

13

解析器上的~方法將兩個解析器合併爲一個,它將連續應用兩個原始解析器並返回兩個結果。這可能是簡單的(在Parser[T]

def ~[U](q: =>Parser[U]): Parser[(T,U)]. 

如果你從來沒有合併兩個以上的解析器,這將是確定。但是,如果您將他們三人,p1p2p3,與返回類型T1T2T3,然後p1 ~ p2 ~ p3,這意味着p1.~(p2).~(p3)Parser[((T1, T2), T3)]類型。如果你結合其中的五個,如你的例子,那將是Parser[((((T1, T2), T3), T4), T5)]。然後,當你對結果進行模式匹配時,你會得到所有這些缺口:

case ((((_, id), _), formals), _) => ... 

這很不舒服。

然後來了一個聰明的語法技巧。當案例類有兩個參數時,它可以出現在中綴中,而不是模式中的前綴位置。也就是說,如果您有 case class X(a: A, b: B),則可以與case X(a, b)進行模式匹配,但也可以使用case a X b進行模式匹配。 (這是用x::xs來匹配非空列表的做法,::是一個案例類)。 當你編寫案例a ~ b ~ c,這意味着case ~(~(a,b), c),但是也比case ((a,b), c)更愉快,更愉快,這很難得到正確的。

因此,解析器中的~方法返回Parser[~[T,U]]而不是Parser[(T,U)],因此您可以輕鬆地對多個〜的結果進行模式匹配。除此之外,~[T,U](T,U)幾乎是相同的東西,因爲你可以得到同構。

爲解析器和結果類型中的組合方法選擇相同的名稱,因爲生成的代碼是自然可讀的。立即看到結果處理中的每個部分如何與語法規則的項目相關。

parser1 ~ parser2 ~ parser3 ^^ {case part1 ~ part2 ~ part3 => ...} 

選擇Tilda是因爲它的優先級(它緊緊地綁定)與解析器上的其他運算符很好地配合。

最後一點,有輔助運算符~><~,它們丟棄操作數之一的結果,通常是規則中不攜帶有用數據的常量部分。所以人們寧願寫

"class" ~> ID <~ ")" ~ formals <~ ")" 

並只得到在結果中的ID和形式的值。

+0

對不起,我錯過了關於String的部分問題。 @Rex Kerr正確回答了它。所以〜既是Parser上的一種方法,也是它返回的數據類型。當作爲方法應用於String時,它會觸發將字符串隱式轉換爲解析器。 –

相關問題