手把手教你構建 C 語言編譯器(5)
本章中我們用 EBNF 來大致描述我們實現的 C 語言的文法,並實現其中解析變數定義部分。
由於語法分析本身比較複雜,所以我們將它拆分成 3 個部分進行講解,分別是:變數定義、函式定義、表示式。
手把手教你構建 C 語言編譯器系列共有10個部分:
EBNF 表示
EBNF 是對前一章提到的 BNF 的擴充套件,它的語法更容易理解,實現起來也更直觀。但真正看起來還是很煩,如果不想看可以跳過。
program ::= {global_declaration}+ |
其中 expression
相關的內容我們放到後面解釋,主要原因是我們的語言不支援跨函式遞迴,而為了實現自舉,實際上我們也不能使用遞迴(虧我們說了一章的遞迴下降)。
P.S. 我是先寫程式再總結上面的文法,所以實際上它們間的對應關係並不是特別明顯。
解析變數的定義
本章要講解的就是上節文法中的 enum_decl
和 variable_decl
部分。
#program()
首先是之前定義過的 program
函式,將它改成:
void program() { |
我知道 global_declaration
函式還沒有出現過,但沒有關係,採用自頂向下的編寫方法就是要不斷地實現我們需要的內容。下面是 global_declaration
函式的內容:
#global_declaration()
即全域性的定義語句,包括變數定義,型別定義(只支援列舉)及函式定義。程式碼如下:
int basetype; // the type of a declaration, make it global for convenience |
看了上面的程式碼,能大概理解嗎?這裡我們講解其中的一些細節。
向前看標記 :其中的 if (token == xxx)
語句就是用來向前檢視標記以確定使用哪一個產生式,例如只要遇到 enum
我們就知道是需要解析列舉型別。而如果只解析到型別,如 int identifier
時我們並不能確定 identifier
是一個普通的變數還是一個函式,所以還需要繼續檢視後續的標記,如果遇到 (
則可以斷定是函數了,反之則是變數。
變數型別的表示 :我們的編譯器支援指標型別,那意味著也支援指標的指標,如
int **data;
。那麼我們如何表示指標型別呢?前文中我們定義了支援的型別:
// types of variable/function |
所以一個型別首先有基本型別,如 CHAR
或 INT
,當它是一個指向基本型別的指標時,如 int *data
,我們就將它的型別加上 PTR
即程式碼中的:type = type + PTR;
。同理,如果是指標的指標,則再加上 PTR
。
#enum_declaration()
用於解析列舉型別的定義。主要的邏輯用於解析用逗號(,
)分隔的變數,值得注意的是在編譯器中如何儲存列舉變數的資訊。
即我們將該變數的類別設定成了 Num
,這樣它就成了全域性的常量了,而注意到上節中,正常的全域性變數的類別則是 Glo
,類別資訊在後面章節中解析 expression
會使用到。
void enum_declaration() { |
#其它
其中的 function_declaration
函式我們將放到下一章中講解。match
函式是一個輔助函式:
void match(int tk) { |
它將 next
函式包裝起來,如果不是預期的標記則報錯並退出。
程式碼
本章的程式碼可以在 Github 上下載,也可以直接 clone
git clone -b step-3 https://github.com/lotabout/write-a-C-interpreter |
本章的程式碼還無法正常執行,因為還有許多功能沒有實現,但如果有興趣的話,可以自己先試著去實現它。
小結
本章的內容應該不難,除了開頭的 EBNF 表示式可能相對不好理解一些,但如果你查看了 EBNF 的具體表示方法後就不難理解了。
剩下的內容就是按部就班地將 EBNF 的產生式轉換成函式的過程,如果你理解了上一章中的內容,相信這部分也不難理解。
下一章中我們將介紹如何解析函式的定義,敬請期待。