1. 程式人生 > >自制指令碼語言(5) 自制的編譯器——LR(1) parser

自制指令碼語言(5) 自制的編譯器——LR(1) parser

摘要:針對自定義的指令碼語言,設計LR(1) parser。LR(1) parser利用之前的parser generator生成的action table和grammar表格,生成抽象語法樹(AST)作為中間語言(IR)。

程式碼地址:https://github.com/nklofy/Compiler

也可以在我的上傳資源中下載

  先說LR的自動生成的parser和LL的手寫的parser。業界上常用的語言的編譯器,往往手寫的LL的較多。這是因為LR的編譯器,狀態數特別多,複雜度過高,人腦往往不能直觀的思考,這樣就依賴於parser generator自動生成。而parser generator自動生成,必須保證語法產生式設計合理,無衝突,無bug,如果是作為練手的玩具還好,如果業界實用的程式語言,往往corner case太多,難以用一張語法表清晰地歸納出來。除錯也較困難,因為往往問題出在語法產生式的設計上。手寫的LL編譯器,程式碼結構即反映了語法規則,corner case可以隨意發揮,除錯起來也容易定位。但是缺點是正確性難以保證,而LR parser只需保證語法產生式的正確性。

   前面已經寫好了lex analyzer和parser generator,接下來的任務就是定義語法式。這個版本的指令碼語言,支援的語言特性有:

1,基本型別。int(內部實現為long,只支援64位);double;bool;string;char。以後會再增加class型別、陣列型別。對應TypeExp,產生語法樹的型別是AST_TypeExp。

2,變數定義與賦值。與C的基本型別操作相似。對應VarDef和VarAssign。意為definition和assignment。產生語法樹的型別是AST_VarDef和AST_VarAssign。

3,函式定義。與C相似,支援遞迴函式。但不支援函式宣告。對應FuncDef。產生語法樹的型別是AST_FuncDef。

4,函式呼叫。與C相似。歸到ApplyExp中。產生語法樹的型別是AST_Apply。

5,If表示式與while表示式。與C相似。歸到If_Exp和While_Exp。產生語法樹的型別是AST_IfExp和AST_WhileExp。

6,continue,break,return語句。這都屬於control flow。產生語法樹的型別是AST_CtrFlw。

7,加減乘除四則運算、布林運算、圓括號。屬於Calc_Exp。表示calculation。產生語法樹的型別是AST_Calc。

具體語法定義見grammar.txt檔案。

  語法產生式通過parser generator轉換為一張狀態轉移表,包含goto和reduce兩種操作。Parser是一個帶棧的有限狀態機,tokenizer讀取檔案,依序返回各個token。Parser根據token查表,轉移新的狀態。同時,查表得到shift或reduce操作。觸發shift操作時,token被壓棧,當前狀態編號也被壓棧。並觸發reduce操作時,就有一個語法產生式發生作用,若干語法符號及對應數量的狀態編號從棧中退出,一個非終結符號產生並壓入棧中。同時,根據當前棧頂的狀態編號和非終結符號,檢視goto表,轉移到新的狀態並將狀態號壓棧。當最終讀取eof符號狀態回到0時,則全文編譯完成。

  為實現上述功能,設計了一些類。首先是抽象類AST類。這是所有具體語法樹型別的祖先。具體語法樹型別,則命名為AST_StmtList、AST_Stmt、AST_TypeExp等。有關具體語法樹型別的資料結構,都把物件的引用型別設定為AST再放入資料結構中。前面提到的reduce操作,在grammar_AST.txt檔案中定義了相應的String名稱以對應每條操作。當reduce執行時,由對應的String名稱匹配函式,並從棧中特定位置找到已存在的AST,由該函式組裝產生新的AST。編譯完成後,生成一棵整體的AST語法樹。後續或者解釋執行,或者生成三地址碼,都可以。

  另外,我用到了一個工廠設計模式。這是為了解耦。順便插一句,我認為分層和解耦是最重要的軟體設計思想。有機會可以展開談。

  我設計了一個AST工廠類ASTGenerator。AST的種類數(24種)遠小於語法產生式的數量(81條)。因為很多語法產生式都可以歸併到同一個非終結符號。而每一條語法產生式,都對應一個reduce操作函式。如果在每個reduce操作函式裡直接組裝AST,不夠靈活,也不夠清晰。reduce操作函式統一通過這個工廠類組裝新的AST。工廠在組裝時,設定每種具體型別的AST的屬性,並返回一個AST引用型別。隨著非終結符號的不停壓棧出棧,AST越來越大,直到分析完整個檔案。

  以上是我這個編譯器的設計思路。

程式碼地址:https://github.com/nklofy/Compiler

也可以在我的上傳資源中下載