編譯原理——詞法分析程式
前言:這是我學習編譯原理,課程實驗的內容,課程早已結束,現整理髮表。
一、實驗任務
- 閱讀已有編譯器的經典詞法分析源程式;
- 用C或JAVA語言編寫一門語言的詞法分析器。
二、實驗內容
閱讀已有編譯器的經典詞法分析源程式。
選擇一個編譯器,如:TINY或PL/0,其它編譯器也可(需自備原始碼)。閱讀詞法分析源程式,理解詞法分析程式的構造方法——狀態圖程式碼化。尤其要求對相關函式與重要變數的作用與功能進行稍微詳細的描述。若能加上學習心得則更好。根據該語言的關鍵詞和識別的詞法單元以及註釋等,確定關鍵字表,畫出所有詞法單元和註釋對應的DFA圖。
仿照前面學習的詞法分析器,編寫選定語言的詞法分析器。
準備2~3個測試用例,要求包含正例和反例,測試編譯結果。
三、詞法分析
tiny詞法解析
- tiny的記號
可以看出,tiny語言的記號很少,畢竟只是簡單的語言。但一開始時想——這些怎麼存的,又是怎麼判斷的,我一開始也是很懵逼的,記得我當時也是花了一下午看了tiny的詞法分析原始碼和實驗給的文件,才弄懂該怎麼寫,然後回到宿舍一晚上就在tiny的基礎上改寫完了。<( ̄︶ ̄)>
簡單的說,記號就是一個語言的分類,當你讀取字串的時候,你總得識別出這一段字串是什麼,是什麼型別的,所以你按某個規則讀完玩一段字元,你就該判斷這段字元是什麼,是否有錯,怎麼歸類,而記號就是你用來歸類的標準。
- 狀態轉換
然後既然有了型別,那怎麼判斷型別呢?用DFA轉換圖。
這個轉換如很簡單,START
開始狀態,INNUM
數字,INID
字串,+-*/=<();
特殊符號,DONE
狀態,從開始狀態根據又一次 讀取第一個字元,根據讀取的字元轉換狀態,進入某個狀態後,當讀取的下一個字元不符合當前狀態的型別,就不讀取字元,而讀取完的這段字串,當是某個型別。判斷完這段字串,又一次 重複操作,直到讀取所有字元。
把需要判斷的狀態都加上時,就成了tiny的詞法分析轉換圖。
四、程式設計
我寫的詞法分析其實就是在tiny基礎上稍加修改而已,詞法設計這部分大致相同。
記號
保留字:
cin while then cout end
特殊字元:
= + - * / ( ) ; >> <<
註釋:
{這是註釋}
轉換表
參照上面
程式碼解析
- 巨集定義最大匹配字元變數的長度為40
保留字為5個
#define MAXTOKENLEN 40
#define MAXRESERVED 5
- 定義一個列舉型別,用於表示dfa的狀態集
typedef enum { //DFA狀態集
START,
INCOMMENT,
INNUM,
INID,
ININ,
INOUT,
DONE
} stateType;
- 定義個列舉型別,表示根據輸入的字串匹配到的字串型別。
typedef enum { //用於匹配的型別,判斷輸入
/* 異常狀態 */
ENDFILE,
ERROR,
/* 保留字 */
CIN,
COUT,
WHILE,
THEN,
END,
/* DFA狀態 */
ID,
NUM,
/* 特殊符號 */
IN,
OUT,
EQ,
PLUS,
MINUS,
TIMES,
OVER,
LPAREN,
RPAREN,
SEMI
} tokenType;
- 定義一個結構體,用於根據匹配到的保留字輸出保留字。
static struct //保留字結構體,用於輸出
{
const char *str;
tokenType tok;
} reservedWords[MAXRESERVED] = {
{"cin", CIN},
{"while", WHILE},
{"then", THEN},
{"cout", COUT},
{"end", END}};
- 定義一個返回值為bool型別的函式,判斷輸入字元是否為字母。
bool isLetter(char c) //是否為字元
{
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
{
return true;
}
else
{
return false;
}
}
- 判斷匹配的字串是否為保留字。
static tokenType reservedLookup(char *s)
{
for (int i = 0; i < MAXRESERVED; i++)
if (!strcmp(s, reservedWords[i].str))
return reservedWords[i].tok;
return ID;
}
- 根據匹配到的字元型別輸出字串。
void printToken(tokenType token, const char tokenString[]); //輸出函式
- 匹配字串的函式。
void getToken(string ss); // 詞法分析
五、實驗測試
- 輸入
{sdfs
adf}
cin >>{sdfsadf} x;
{sdfsadf}
cin>>y;
while (cin>>z) then{sdfsadf}
x=x z y;
cout << x;
end;
- 輸出
1:{sdfs
2:adf}
3:cin >>{sdfsadf} x;
3: reserved word:cin
3: >>
3: ID, name= x
3: ;
4:{sdfsadf}
5:cin>>y;
5: reserved word:cin
5: >>
5: ID, name= y
5: ;
6:while (cin>>z) then{sdfsadf}
6: reserved word:while
6: (
6: reserved word:cin
6: >>
6: ID, name= z
6: )
6: reserved word:then
7:x=x z y;
7: ID, name= x
7: =
7: ID, name= x
7: ID, name= z
7: ID, name= y
7: ;
8:cout << x;
8: reserved word:cout
8: <<
8: ID, name= x
8: ;
9:end;
9: reserved word:end
9: ;
六、實驗總結
實際上剛寫這篇部落格的時候我有點虛,因為不怎麼記得tiny的語法分析怎麼回事了,只能硬著頭皮有看了下實驗文件,然後才慢慢回想起來。然後就是你們看到的部落格了。
當時我確實是花了下午2,3個小時看這實驗的文件的,看實驗文件,看tiny原始碼,慢慢理解究竟是怎樣的,然後規劃自己要改成什麼樣的,實驗內容那部分我刪改了些,有部分內容要求是我們確定了我們的語言後,就鼓勵我們自己定義一門語言,我當時就是想寫們專門計算數學的語言,也就才有了這樣的輸入。
然後事實上,並不好寫,尤其是要寫整個前端的情況下,越發的力不從心,我後面的實驗深刻理解到了這一點,然後沒有堅持寫出自己的語言,事實上也沒有一個同學寫出自己的語言的整個前端,不是tiny就是pl改的。
還有,我也是在這裡入了列舉和結構體的坑,然後有次實驗程式碼瘋狂用了結構體,搞得結構很複雜,總之,如果你們還看我後面實驗的的程式碼,你就知道什麼叫做喪心病狂了。。。