工程中的編譯原理 -- Mapfile解析器

發表於2015-11-12

前言

Mapfile是MapServer用來描述一個地圖的配置檔案。它是一個很簡單的宣告式語言,一個地圖(Map)可以有多個層(Layer),每個層可以有很多屬性(鍵值對)。在一個層的定義中,還可以定義若干個類(Class),這個類用以管理不同的樣式(Style)。而每個類或者樣式都可以由若干個屬性(鍵值對)。

這裡有一個實際的例子:

最簡單的層的定義

最簡單的情形是,我們定義了一個層Layer,但是沒有指定任何的屬性:

我們期望parser可以輸出:

要做到這一步,首先需要定義符號LAYER和END,以及一些對空格,非法字元的處理等:

對於,空格,回車換行等,我們都直接跳過。對應的BNF也非常簡單:

為層新增屬性

接下來我們來為層新增Name屬性,首先還是新增符號NAME和對字串的定義。這裡的字串被定義為:由雙引號括起來的所有內容。

然後我們就可以為BNF新增一個新的節:

在decl中,我們將獲得的字串兩頭的引號去掉$2.substring。這樣decl的值就會是字串本身,而不是帶著雙引號的字串了。修改之後的程式碼可以解析諸如這樣的宣告:

併產生這樣的輸出:

但是如果我們用來解析兩個以上的屬性:

解析器會報告一個錯誤:

即,期望一個END符號,但是卻看到了一個WORD符號。我們只需要稍事修改,就可以讓當前的語法支援多個屬性的定義:

先看,pair的定義,它由NAME STRING或者DATA STRING組成,是我們語法中的終結符。再來看pairs的定義:

這個遞迴的定義可以保證我們可以寫一條pair或者多條pairs pair屬性定義語句。而對於多條的情況,我們需要將這行屬性規約在一起,即當遇到這樣的情形時:

我們需要產生這樣的輸出:{name: “counties”, data: “counties-in-shaanxi-3857”}。但是由於符號是逐個匹配的,我們會得到這樣的匹配結果:{name: “counties”}和{data: “counties-in-shaanxi-3857”},因此我們需要編寫一個簡單的函式來合併這些屬性:

按照慣例,這種自定義的函式需要被定義在%{和}%括起來的section中:

現在我們的解析器就可以識別多條屬性定義了:

巢狀的結構

現在新的問題又來了,我們的解析器現在可以識別對層的對個屬性的解析了,不過由於CLASS並不是由簡單的鍵值對定義的,所以還需要進一步的修改:

類由CLASS關鍵字和END關鍵字定義,而類的屬性定義和Layer的屬性定義並無二致,都可以使用pairs(多條屬性)。而classes事實上是pair的另一種形式,就像對屬性的定義一樣,所以:

這樣,解析器就可以識別CLASS子句了。我們注意到,在CLASS中,還可以定義STYLE,因此又需要稍作擴充套件:

這樣,我們的解析器就可以處理樣例中的所有語法了:

完整的程式碼在github上的這個repo中

總結

使用BNF定義一個複雜配置檔案的規則,事實上一個比較容易的工作。要手寫這樣一個解析器需要花費很多的時間,而且當你需要parser多種配置檔案時,這將是一個非常無聊且痛苦的事情。學習jison可以幫助你很快的編寫出小巧的解析器,在上面的Mapfile的例子中,所有的程式碼還不到100行。下一次再遇到諸如複雜的文字解析,配置檔案讀取的時候,先不要忙著編寫正規表示式,試試更高效,更輕便的jison吧。

相關文章