華南理工大學
《編譯原理》課程實驗報告
實驗題目: 實現TINY+編譯器
源代碼及報告書: (爲了大家的學習着想,此處就不放github的地址了 可以在我的個人上傳資源中找到這份文檔)。
(此處圖片未顯示,可以在github中下載報告書觀看。報告書即爲如下內容)
【實驗目的】
構造一個將TINY+高級程序設計語言轉換爲TINY+虛擬機上的中間代碼的編譯器。
整個實驗包括兩個部分:實驗一完成TINY+編譯器的詞法分析器部分;實驗二完成TINY+編譯器的語法分析器部分、語義分析器部分及中間代碼生成器部分。
【實驗環境】
Microsoft Visual Studio 2015
C++語言實現
分爲實驗一、二。實驗一、 實現TINY+編譯器的詞法分析器
【實驗目的及要求】
學生在該實驗中構造TINY+語言的詞法分析程序。具體要求如下:
1 詞法分析器的輸入是源程序文件,輸出是單詞(token)流。
2 構造的詞法分析器必須遵循最長子串原則,例如字符串‘:=’ 應該被識別爲一個代表賦值符號的單詞,而不是識別爲兩個單詞‘:’和 ‘=’。
3 詞法分析器識別出的單詞應該被表示成二元組的形式(Kind, Value),其中Kind表示單詞的種類,Value表示單詞的實際值。要求用如下符號表示不同種類的單詞:
l KEY 表示關鍵字;
l SYM 表示特殊符號;
l ID 表示標識符;
l NUM 表述數字常量
l STR 表示字符串常量
4 詞法分析器的任務除了完成對單詞的識別之外,還要檢查程序中的詞法錯誤,給出錯誤的信息以及錯誤在源程序中出現的位置(所在的行號)。詞法錯誤的種類包括:
l Ø 非法符號,詞法分析器可能識別到一個TINY+程序的字母表中不允許的符號,例如,詞法分析器在一個源程序中識別到$,應報告一個詞法錯誤,發現了一個非法符號;
l Ø 字符串類型的單詞STRING,單引號不匹配。例如,源程序中出現' scanner,詞法分析分析器應報告錯誤“字符串缺少右引號”;
l 註釋的括號不匹配。例如源程序中出現{this is an example,詞法分析器應報告錯誤“註釋缺少右括號”。
- 在main函數中將NO_ANALYZE、NO_CODE、PARSE設置爲TRUE,使程序只進行詞法分析過程。
- 由於增加了關鍵字(keyword),TINY語言關鍵字如下:
Tiny關鍵字 |
if,then,else,end ,repeat,until,read,write |
Tiny+新增關鍵字 |
Or,and ,int,bool,char,while,do |
其中所有的關鍵字是程序設計語言保留使用的,並且用小寫字母表示,用戶自己定義的標識符不能和關鍵字重複。
- 特殊符號定義:
Tiny符號 |
{ } ; := + - * / ( ) < = |
Tiny+新增符號 |
> <= >= , ' |
- 詞法分析器中識別的單詞用二元組形式表示(Kind,Value),其中Kind表示單詞的種類,Value表示單詞的實際值。要求用如下符號表示不同種類的單詞:
KEY |
關鍵字 |
SYM |
特殊符號 |
ID |
標識符 |
NUM |
數字常量 |
STR |
字符串常量 |
- 詞法分析器的任務除了完成對單詞的識別之外,還要檢查程序中的詞法錯誤,給出錯誤的信息以及錯誤在源程序中出現的位置(所在的行號)。詞法錯誤的種類包括:
- 非法符號,詞法分析器可能識別到一個TINY+程序的字母表中不允許的符號,例如,詞法分析器在一個源程序中識別到$,應報告一個詞法錯誤,發現了一個非法符號;
- 字符串類型的單詞STRING,單引號不匹配。例如,源程序中出現' scanner,詞法分析分析器應報告錯誤“字符串缺少右引號”;
註釋的括號不匹配。例如源程序中出現{this is an example,詞法分析器應報告錯誤“註釋缺少右括號”。
實驗過程:
- 在GLOBALS.h中的TokenType枚舉中增加相應的類型:
關鍵詞 |
特殊符號 |
||
新增枚舉定義 |
對應的關鍵詞 |
新增枚舉定義 |
對應特殊符號 |
OR |
or |
RT |
> |
AND |
and |
LTEQ |
<= |
INT |
Int |
RTEQ |
>= |
BOOL |
bool |
COMMA |
, |
CHAR |
char |
CO |
‘ |
WHILE |
while |
|
|
DO |
do |
|
|
- 在SCAN.C的getToken()函數中進行相應修改,讓程序能夠正確識別新添加的關鍵詞以及特殊符號,並且對程序出錯進行相應提示)
詞法分析測試結果
測試一:
輸入 |
輸入右邊所示文件,運行程序後,結果如下圖: (下面兩張圖爲同一輸出結果) |
|
輸出 |
|
測試二:
輸入 |
輸入右邊所示文件,運行程序後,結果如下圖: (下面兩張圖爲同一輸出結果) |
|
輸出 |
|
測試三:(測試錯誤提示)
輸入 |
輸入右邊所示文件,運行程序後,結果如下圖: 其中有兩處錯誤:
|
|
輸出 |
|
實驗二、實現TINY+的語法分析器、語義分析器以及中間代碼生成器
【實驗目的及要求】
學生在該實驗中構造TINY+語言的語法分析器、語義分析器以及中間代碼生成器,具體要求如下:
- 爲TINY+構造一個遞歸下降語法分析器。該語法分析器對詞法分析器生成的單詞序列進行語法分析,產生一顆抽象語法樹作爲語法分析器的輸出。
- 構造TINY+的語義分析器,該語義分析器構建符號表,然後檢查程序中的語義錯誤。
- 構造TINY+的中間代碼生成系,該中間代碼生成器將TINY+程序翻譯爲三地址中間代碼。
- 要求能檢查程序中語法和語義錯誤,具體包括:
- 語法錯誤:
- 語法結構的開始符號以及跟隨符號錯誤;
- 標識符錯誤,例如在程序的變量聲明中,關鍵字int 後沒有跟隨標識符;
- 括號不匹配的錯誤,例如左括號(和右括號 )不匹配。
- 符號錯誤,例如賦值語句中要求使用的正確符號是‘:=’,而在關係比較表達式要求使用的正確符號是‘=’。
- 語義錯誤:
- 一個標識符沒有聲明就使用,以及一個標識符被聲明不止一次。
- 一個條件表達式的類型不是布爾類型bool。
- 一個二元操作符的兩個操作數的類型不相等。
- 賦值語句左右部的類型不相等。
語法分析實驗過程:
- 在main函數中將NO_ANALYZE、NO_CODE 設置爲TRUE,使程序只進行PARSE(語法分析)過程。
- Tiny+的語法EBNF定義如下(其中紅色是指Tiny+在Tiny上新增的語法):
20 bool-exp -> bterm { or bterm } 21 bterm -> bfactor { and bfactor} 22 bfactor -> comparison-exp 23 comparison-exp -> arithmetic-exp comparison-op arithmetic-exp 24 comparison-op -> < | = | > | >= | <=
|
- 根據EBNF的定義,修改PARSE.C文件,構造遞歸下降語法分析器,產生一棵語法樹。
- 基本語法樹木的結構如下:
語句系列 |
|
語句間通過鏈表相連,除了第一條語句,其他語句均可通過前一個語句的->slibing查找到。 |
If語句 |
|
test位於If語句的child[0], then-part爲child[1], else-part爲child[2] |
Repeat |
|
Body位於repeat語句中的child[0], test位於chlid[1] |
Assign |
|
Expression 位於assign語句child[0] |
write |
|
Expression位於write的child[0] |
Op 邏輯運算 |
|
Left-operand 位於op語句的child[0], Right-opreand位於語句的child[1]; |
while |
|
Test位於while語句的child[0] Do-part位於child[1] |
Define 如: int A,B |
|
B位於A的slibing中, 其中A、B的子節點均未賦值(此處留給將來拓展用) |
- 新建一個define的結點類型,用來識別定義語句.
- 重寫tiny中不適合的語法分析樹枝,按照新的ENBF定義寫出合適的分析函數,從而構建出正確語法樹。
語法分析測試結果:
測試一:
輸入 |
輸入右邊所示文件,運行程序後,結果如下圖: |
|
輸出 |
|
語義分析實驗過程:
1在main函數中將 NO_CODE 設置爲TRUE,使程序進行語義分析過程。
2一個用TINY+語言編寫的程序包括變量的聲明和語句序列兩個部分。變量聲明部分可以爲空,但一個TINY+程序至少要包含一條語句。
- 所有的變量在使用之前必須聲明,並且每個變量只能被聲明一次。
- 變量以及表達式的類型可以是整型int,布爾類型bool或者字符串類型char,必須對變量的使用和表達式進行類型檢查。
3構建語義分析器主要包括
- 符號表的設計以及符號表的相應操作
- 符號表保存信息:變量的地址信息、變量被訪問的行號
- 變量以其變量名ID,通過鏈式hash儲存查找到相應數據。
- 語義分析程序本身的操作,包括符號表的構建以及類型檢查。
- 符號表的創建:
- 前序遍歷語法樹:
當遇到定義的語法結點時候,將其變量新增到哈希表中,並存儲其行號位置;
遇到含有變量的非定義語句的結點時,查找該變量位於哈希表中的位置,將該行號儲存到哈希相應表項的next位置。
- 類型檢查:
- 後續遍歷語法樹,對每一個結點進行語義判斷:
1. if語句結點中,if-test(child[0])的類型必定爲boolean,
2. while語句結點中,while-test(child[0])的類型必定爲boolean,
3. op算術運算結點中,child[0]、child[1]必須爲integer類型
4. op邏輯運算結點中,child[0]、child[1]必須爲boolean
5. 賦值運算結點中,child[0]、child[1]的類型必須一致
6. ……
- 在後續遍歷語法樹中,查看變量是否非法使用:
- 當結點爲ID結點時候,在哈希表中查看是否已經定義。
- 如果該結點爲定義語句結點,則判斷此變量是否重複定義。
- 如果該變量已經定義,則將變量的type(類型)賦於定義時候的類型
- 進入上一層遍歷,當其類型不符合上一層所需類型時候提示錯誤。(即如在賦值語句中,該變量的類型與賦值語句另一個子節點的類型不一致,提示語義錯誤)。
詞法分析測試結果
測試一:
輸入 |
輸入右邊所示文件,運行程序後,結果如下圖:
|
|
輸出 |
|
測試二:
輸入 |
輸入右邊所示文件,運行程序後,結果如下圖:
|
|
輸出 |
|
測試三:(測試錯誤提示)
輸入 |
輸入右邊所示文件,運行程序後,結果如下圖: 代碼中有三處錯誤:
|
|
輸出 |
|
中間代碼生成實驗過程:
1 在main函數中將 NO_PARSE、NO_ANALYZE、NO_CODE 設置爲FALSE,使程序進行中間代碼生成過程。
2 構造TINY+的中間代碼生成系,該中間代碼生成器將TINY+程序翻譯爲三地址中間代碼。
3在原tiny中間代碼生成器的實現位於cgen.c和cgen.h中。其接口爲codeGen(TreeNode* syntaxTree,char* codefile).
主要功能爲:
- 函數的開始,產生一些註釋以及建立運行時環境的指令。
- 對語法樹調用cGen函數,對每一個語法樹結點生成相應指令
- 最後生成HALT指令,結束目標代碼生成。
註釋以及指令均寫在某指定.tm文件上,供TM程序調用並且運行。
- Tiny+在tiny的基礎上進行修改。其中codeGen函數將保持不變,修改其cGen函數即可。
- 修改cGen函數調用的getExp()函數。
- 增加OpK的類型,即在原有功能代碼基礎上,增加新增符號其操作的中間代碼生成步驟。即新增符號RT,EQ,LTEQ,RTEQ.
如增加“>”的中間代碼生成過程
- 增加LogicOpK的處理(即增加AND、OR的中間代碼的生成)
此處中間代碼生成比算術運算的中間代碼複雜得多,根據TM程序的指令以及其操作原理,寫出AND和OR中間生成代碼功能:
- 修改cGen函數調用的getStmt()函數。增加語句while的中間代碼生成。
此處通過Tiny+文法規則產生對於的三地址中間代碼屬性,完成while生成中間代碼功能。
中間代碼測試結果
測試一:
輸入 |
輸入右邊所示sample.tny文件到TINY+,運行程序後,獲得example.tm文件。
將example.tm文件輸入TM程序中,運行結果如圖: 輸入x=5,輸出120(正確) 輸入x=101,跳出if(正確) 輸入x=4,輸出24(正確) 輸入x=-1,跳出if(正確) |
|
輸出 |
Example.TM文件如下截圖:
輸入上述文件到TM程序中運行,結果如下:
|
- 在TINY+編譯器的設計編寫中,將書上學到的知識運用到實踐中,從而完成TINY編譯器的拓展。
- 加深對文法的理解、詞法分析中的無窮自動機(NFA)轉爲有窮自動機(DFA)再轉成最間DFA的過程以及原理有了新的認識。
- 語法分析是耗費我最多時間的地方。TINY+語法定義稍有不準確,將爲後期代碼調試帶來很大的麻煩。並且語法的定義在精簡壓縮方面需要下很大功夫。從給定的EBNF定義,到真正可運行的程序代碼,期間需要大量的測試調試。
- 在構建語法生成樹中,曾多次修改之前寫的詞法分析器,並且重寫了之前寫的質量不高的語法分析器,將代碼結構進行解耦,方便了後續的調試以及改進。
- 在中間代碼生成中,深入瞭解生成三地址的中間代碼方式。其中就if語句的中間代碼生成進行了大量的測試,最終將原先不太瞭解的彙編代碼看懂,並且寫出了邏輯表達式AND、OR、以及While的中間代碼生成功能。
- 在TM程序中,通過一步步調試程序代碼,學會執行彙編語言,其中學會自定義寄存器、內存緩衝區、指令緩衝區來執行彙編語言。也在此過程中,改正了中間代碼生成中的許多bug。
- 該TINY+編譯器存在着許多不足,仍需要進行改造進化。但完成該編譯器後,已經向編譯原理世界邁進一大步了。