實驗課上實現了對詞法分析程序
的編寫。整個過程中,最爲困難的就是對整個編譯過程的設計。在這裏整理一下我的整個從設計到實現過程以及中間出現的問題。
問題描述
假定一種高級程序設計語言中的單詞主要包括關鍵字begin、end、for、if、then、else
;標識符
;整數
;六種關係運算符
,試構造能識別這些單詞的詞法分析程序。
下面是各類單詞符號以及編碼對應表
最後我們將實現輸入一段代碼,然後輸出鍵值對的形式:
輸入:if myid >= 15 then x = y
輸出:(IF, " ")
(ID, "myid")
(GE, " ")
(INT, "15")
(THEN, " ")
(ID, "x")
(EQ, " ")
(ID, "y")
狀態轉換圖
首先同時也是最爲複雜的就是如何設計狀態轉換圖。
根據我們過去使用過的各種編程語言,再結合上面所給的類型表格,可以簡單分爲幾種情況:
1.字母開頭
1.1 關鍵字
1.2 標識符(字母開頭的字母數字串)
2.數字開頭: 整數
3.比較符號: 關係運算符
那麼現在就是如何將上面的情況轉換成圖了。
通過對第一種情況的翻譯,我們會得到上面的狀態轉換圖。可以發現,當我們將各種情況都列好以後,轉換圖好像也沒有想象的那麼難了。
然後跟上面的方法一樣,我們將得到一個較爲完整的轉換圖:
可以看到,上面的轉換圖不僅包含了轉換的條件,同時,也標明瞭轉換過程中用到的方法。這也是我們下一步要解決的。
功能函數
狀態轉換圖中一共有這麼幾個功能函數:GETCHAR(讀取一個字符)
,CAT(拼接單詞)
,LOOKUP(檢查是否爲關鍵字)
,RETRACT(回退一個字符)
,OUT(輸出)
。
然後就到我們要實際編寫代碼的時候了。
1.GETCHAR
由於在實現這個程序的時候,設計是由文件中讀取出源程序,然後輸出到控制檯上。所以會用到一些文件的讀取操作。相應的,C語言的類庫中有一些已經寫好的函數,所以我們可以直接拿來用:
char ch = fgetc(fp);
fp
是一個函數指針,fgetc
函數的作用就是讀取一個字符,然後將指針向後移動一個。
2.CAT
拼接的實現是利用了字符數組實現的:
char TOKEN[20];
定義一個數組用來存放單詞,然後就可以將上面獲取的單詞依次存如數組中。
TOKEN[i] = ch; // 保存字符
i++;
當然,這需要一個循環:
while (isalnum(ch)) {
TOKEN[i] = ch; // 保存字符
i++; // 長度加一
ch = fgetc(fp); // 讀取下一個字符
}
3.LOOKUP
這個的實現也比較簡單,只需要按順序比價保留字列表,看時候與剛剛掃描出的單詞匹配就可以了。
int lookup(char *token) {
int n = 0;
// 依次比較所有保留字
while (strcmp(KeyWordTable[n], KEY_WORD_END)) {
// 比較token所指向的關鍵字和保留字表中哪個關鍵字相符
if (!strcmp(KeyWordTable[n], token)) {
return n + 1; // 返回關鍵字對應的編碼
}
n++;
}
return 0; // 單詞不是關鍵字,而是標識符
}
4.RETRACT
同讀取一樣,這個的實現也是藉助於類庫中的方法:
fseek(fp, -1, 1); // 將文件指針指向當前位置的前一個位置
5.OUT
最後輸出的功能就比較簡單了,只要將所有的情況都不遺漏,就可以了。
switch (code) {
case BEGIN:
cout << "(BEGIN, \" \")" << endl;
break;
case END:
cout << "(END, \" \")" << endl;
break;
case IF:
cout << "(IF, \" \")" << endl;
break;
case THEN:
cout << "(THEN, \" \")" << endl;
break;
case ELSE:
cout << "(ELSE, \" \")" << endl;
break;
case ID:
cout << "(ID, \"" << value << "\")" << endl;
break;
case INT:
cout << "(INT, \"" << value << "\")" << endl;
break;
case LT:
cout << "(LT, \" \")" << endl;
break;
case LE:
cout << "(LE, \" \")" << endl;
break;
case EQ:
cout << "(EQ, \" \")" << endl;
break;
case NE:
cout << "(NE, \" \")" << endl;
break;
case GT:
cout << "(GT, \" \")" << endl;
break;
case GE:
cout << "(GE, \" \")" << endl;
break;
default:
cout << "編碼錯誤!" << endl;
break;
}
}
總結
通過對編譯原理的學習,越發感覺思維的重要性。
當我們有了思想後,代碼實現不過只是一個工具而已。