Scala Parser原理介紹與源碼分析

Scala Parser原理介紹與源碼分析

版權聲明:本文爲博主原創文章,未經博主容許不得轉載。正則表達式

手動碼字不易,請你們尊重勞動成果,謝謝編程

做者:http://blog.csdn.net/wang_wbqapp

爲了避免耽誤你們的時間,重要的事情說三遍:
本文不講Scala Parser的用法!!
本文不講Scala Parser的用法!!
本文不講Scala Parser的用法!!ide

今天這篇文章,我準備介紹一下Status Monad,奧不,應該是Scala ParserStatus Monad是啥,好吃不。Scala Parser是scala庫中提供的一個詞法解析器,學習過編譯原理的同窗對這個東西的做用應該已經瞭解了。函數式編程

話說Scala Parser是我當初在學習scala的時候遇到的一個大老虎了,做爲電子專業畢業的,當初爲了搞懂Scala Parser是幹啥的,我花兩週時間看完了大半本《編譯原理》,又上網看各類官方文檔。不得不說,百度上基本搜不到Scala Parser的東西,因此這一次,我專門寫這一篇文章來介紹下。函數

廢話很少說,咱們正式開始咱們的Scala Parser旅程。源碼分析

Scala Parser源碼分析

對於scala parser而言,主要的類就一個:學習

package scala.util.parsing.combinator

trait Parsers {
  type Elem
  type Input = Reader[Elem]

  sealed abstract class ParseResult[+T] {

    def map[U](f: T => U): ParseResult[U]

    def mapPartial[U](f: PartialFunction[T, U], error: T => String): ParseResult[U]
  }

  abstract class Parser[+T] extends (Input => ParseResult[T]) {

    def apply(in: Input): ParseResult[T]

    def flatMap[U](f: T => Parser[U]): Parser[U] = Parser{ in => this(in) flatMapWithNext(f)}

    def map[U](f: T => U): Parser[U] = Parser{ in => this(in) map(f)}

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

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

    //以及下面一大堆奇形怪狀的函數定義,想看本身開源碼吧

從上面的摘錄代碼中,咱們大體能夠看到Scala Parser的幾個核心特質和類this

trait Parsers

首先咱們先來看最外層的類trait Parser。這個類是全部Scala Parser類的基類,包括咱們常用的RegexParsersspa

trait RegexParsers extends Parsers

這個特質爲咱們提供了最基本的Parser環境,經過對其進行擴展,咱們就能夠衍生出各類各樣的匹配方式,好比RegexParsers就引入了正則表達式的匹配。

sealed abstract class ParseResult[+T]

顧名思義,這個類就是用來表示轉換結果的,而且它被sealed標記,表示它的全部子類都在這個文件裏,這個關鍵字簡直就是源碼閱讀者的福音:

case class Success[+T](result: T, override val next: Input) extends ParseResult[T]

  sealed abstract class NoSuccess(val msg: String, override val next: Input) extends ParseResult[Nothing]

  case class Failure(override val msg: String, override val next: Input) extends NoSuccess(msg, next)

  case class Error(override val msg: String, override val next: Input) extends NoSuccess(msg, next)

這個類共有四個子類,而且只有三個是實體類。具體幹啥的你們一看也就明白了吧。

abstract class Parser[+T]

重點來了,這個類簡直就是一個神奇的存在。咱們先來看一下它的類聲明:

abstract class Parser[+T] extends (Input => ParseResult[T])

你看人家繼承了啥東西,(Input => ParseResult[T])這個表達式是Function1[Input,ParseResult[T]]的語法糖,代表這個Parser類繼承了一個函數,而且須要重寫apply方法。學過scala的都知道apply方法就是爲咱們直接用括號來造語法糖的。

咱們深刻思考一個這個apply方法,它接受一個Input類型的輸入,返回一個ParseResult[T]類型的輸出,上面咱們也看到了ParseResult[T]的幾個子類,用來表示轉換的結果。如今咱們能夠大膽猜想下,Parser類的apply方法就是實現了一個輸入值的解析,並生成一個解析結果。

講到這裏,Scala Parser是否是就顯而易見了,咱們只用實現Parserapply方法就能實現輸入數據的解析了。既然Scala Parser是一個詞法解析器,那咱們就能夠實現一個Json或者XML的解析器啊。是能夠,想當初我爲了讀一個XML的註釋和內容,花了一天寫了一個XML解析器,可把我煩死了。可是咱們要怎麼來實現呢?直接重寫apply方法,而後本身去讀字符串嗎?那還要Scala Parser幹啥,一本《編譯原理》不就搞定啦。

說道Scala Parser爲咱們提供的便利,那固然就是它的解析器組合能力了,它能把全部同一類型的Parser類實例以某種方式組合在一塊兒,提供一系列DSL組合方法,讓你能夠清晰地定製你的解析器。下面咱們就來詳細分析一下它其中的原理。

Scala for語法糖介紹

在scala中,有些表達式長這個樣子:

//看清楚了,帶yield
for(a <- listA; b <- listB) {
    yield (a, b)
}

這個表達式返回了listA和listB的笛卡爾積。

其實這句話只是一句語法糖而已,它真正的實現是:

listA.flatMap(a -> listB.map(b -> (a, b)))

不要懷疑,下面這個代碼就是上邊那個代碼,一毛同樣。

好了,語法糖介紹完畢,咱們回到Scala Parser中來

Parser的組合

def flatMap[U](f: T => Parser[U]): Parser[U] = Parser{ in => this(in) flatMapWithNext(f)}

    def map[U](f: T => U): Parser[U] = Parser{ in => this(in) map(f)}

下面咱們重點來看Parser類中的這兩個方法。首先咱們先看flatMap,這個方法會生成一個Parser的實例,這個實例接受一個in的入參,而後交給this的apply方法。以前咱們介紹過了,apply會解析輸入,而後生成一個ParseResult[T]。而後調用ParseResult[T]的flatMapWithNext方法,把faltMap的入參傳進去了。

咱們進入咱們跟蹤解析成功的路徑,進入到Success類的flatMapWithNext方法中:

case class Success[+T](result: T, override val next: Input) extends ParseResult[T] {
    def flatMapWithNext[U](f: T => Input => ParseResult[U]): ParseResult[U] = f(result)(next)
  }

咱們能夠很明顯看到,這個函數就是把以前解析出來的結果和剩下的還沒解析完的一部分結果應用到了f函數裏,咱們看一下f的類型:f: T => Parser[U],由於Parser[U]Input => ParseResult[U]類型,因此f函數的類型就是T => Input => ParseResult[U],咱們看到上面這個flatMapWithNext函數裏,給f傳入了上次解析的結果result,這個的用處後面再說。緊接着,把剩下的未解析的一部分輸入交給了f。到這裏,你可能已經明白了這個解析器的原理,那就是經過這種組合方式,跟鏈表同樣一個一個函數進行解析,並把剩下的沒法解析的部分交給後面的解析器。

這裏寫圖片描述

基本上就是圖中畫的這樣了,不過關注下result引出的箭頭,它實際上並無進入到Input => ParseResult[U]函數中去,而是到了flatMap的第一個函數入參中去了。

最後,咱們就能夠看一下Scala Parser的Parser組合函數了:

def ~ [U](q: => Parser[U]): Parser[~[T, U]] = { lazy val p = q // lazy argument
      (for(a <- this; b <- p) yield new ~(a,b)).named("~")
    }

    def ~> [U](q: => Parser[U]): Parser[U] = { lazy val p = q // lazy argument
      (for(a <- this; b <- p) yield b).named("~>")
    }

    def <~ [U](q: => Parser[U]): Parser[T] = { lazy val p = q // lazy argument
      (for(a <- this; b <- p) yield a).named("<~")
    }

    def ^^ [U](f: T => U): Parser[U] = map(f).named(toString+"^^")

函數還有不少,咱們舉幾個有表明性的講一下。咱們先看第一個方法~,它其實就是一個鏈接符號。看它的定義,加上咱們以前說的for語法糖,咱們能夠看出a <- this這句話中的a,就是以前咱們看到的result: T,也就是當前Parser類的解析結果會被導出到a變量中。同理可知,Parserp接受了當前類this的解析剩下的輸入next: Input而且將其解析出的result: U導出到了b變量中。以後Scala Parser又定義了一個類~,其實就是一個Tuple2來保存這兩個解析出的結果值,並將其命名爲~

解釋了第一個方法的原理後,第二個和第三個方法的原理就很顯而易見了:~>這個像態射同樣的方法,跟它毛關係沒有,它就是匹配this和p,而後只保留p的解析結果,而將this的解析結果直接丟棄。<~這個函數同理,就是把p的解析結果丟棄,只保留this的解析結果。

第四個方法看着就簡單是吧,就是把以前已經解析好的類型爲T的結果轉換爲類型爲U的類上,僅僅是一個直接對解析結果的轉換。

到這裏,Scala Parser的主要內容已經講完了,不知道你們看懂沒有。由於這些內容涉及到函數式編程的相關知識,還涉及到Status Monad這個東西,着實會讓人十分費解。因爲本文不是專門來介紹Status Monad,所以對其中的原理思想也沒有過多的闡述,若是讀者有興趣,歡迎進入到函數式編程這個大坑裏來~~~

相關文章
相關標籤/搜索