為了驗證詞法分析理論,本文實作一個**教學級簡易詞法分析器**,支援關鍵字、標識符、整數常數、運算子/界符,輸出 <類別碼, 屬性> 序列。程式碼採用**單通道掃描 + 狀態機硬編碼**風格,無外部依賴,適合初學者研讀。
begin if then while do end
: := + - * / < <= <> > >= = ; ( ) #
ID = letter (letter | digit)*
NUM = digit digit*
| Token | 代碼 | Token | 代碼 |
|---|---|---|---|
| begin | 1 | : | 17 |
| if | 2 | := | 18 |
| then | 3 | < | 20 |
| while | 4 | <> | 21 |
| do | 5 | <= | 22 |
| end | 6 | > | 23 |
| ID | 10 | >= | 24 |
| NUM | 11 | = | 25 |
| + | 13 | ; | 26 |
| - | 14 | ( | 27 |
| * | 15 | ) | 28 |
| / | 16 | # | 0 (結束) |
#include
#include
char prog[80], token[8], ch;
int syn, p, m, n, sum;
char *rwtab[6] = {"begin", "if", "then", "while", "do", "end"};
void scanner(void);
int main() {
p = 0;
printf("\nPlease input a string (end with '#'):\n");
do {
scanf("%c", &ch);
prog[p++] = ch;
} while (ch != '#');
p = 0;
do {
scanner();
switch (syn) {
case 11: // NUM
printf("( %-10d %5d )\n", sum, syn);
break;
case -1: // Error
printf("You have input a wrong string\n");
return 0;
default: // ID, keyword, operators
printf("( %-10s %5d )\n", token, syn);
break;
}
} while (syn != 0);
return 0;
}
void scanner(void) {
sum = 0;
for (m = 0; m < 8; m++) token[m] = '\0';
m = 0;
ch = prog[p++];
// 跳過空白
while (ch == ' ' || ch == '\n') ch = prog[p++];
// 標識符 / 關鍵字
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
token[m++] = ch;
ch = prog[p++];
}
p--; // 退回一個非字母數字字元
token[m] = '\0';
syn = 10; // 預設 ID
// 查保留字表
for (n = 0; n < 6; n++) {
if (strcmp(token, rwtab[n]) == 0) {
syn = n + 1; // 1~6
break;
}
}
}
// 數字常數
else if (ch >= '0' && ch <= '9') {
while (ch >= '0' && ch <= '9') {
sum = sum * 10 + (ch - '0');
ch = prog[p++];
}
p--;
syn = 11; // NUM
}
// 運算子 / 界符
else {
switch (ch) {
case '<':
token[m++] = ch;
ch = prog[p++];
if (ch == '>') { syn = 21; token[m++] = ch; } // <>
else if (ch == '=') { syn = 22; token[m++] = ch; } // <=
else { syn = 20; p--; } // <
break;
case '>':
token[m++] = ch;
ch = prog[p++];
if (ch == '=') { syn = 24; token[m++] = ch; } // >=
else { syn = 23; p--; } // >
break;
case ':':
token[m++] = ch;
ch = prog[p++];
if (ch == '=') { syn = 18; token[m++] = ch; } // :=
else { syn = 17; p--; } // :
break;
case '+': syn = 13; token[m++] = ch; break;
case '-': syn = 14; token[m++] = ch; break;
case '*': syn = 15; token[m++] = ch; break;
case '/': syn = 16; token[m++] = ch; break;
case '(': syn = 27; token[m++] = ch; break;
case ')': syn = 28; token[m++] = ch; break;
case ';': syn = 26; token[m++] = ch; break;
case '#': syn = 0; token[m++] = ch; break;
default: syn = -1; break; // 非法字元
}
}
token[m] = '\0';
}
begin x:=9; if x>9 then x:=2*x+1/3; end #
( begin 1 )
( x 10 )
( := 18 )
( 9 11 )
( ; 26 )
( if 2 )
( x 10 )
( > 23 )
( 9 11 )
( then 3 )
( x 10 )
( := 18 )
( 2 11 )
( * 15 )
( x 10 )
( + 13 )
( 1 11 )
( / 16 )
( 3 11 )
( ; 26 )
( end 6 )
( # 0 )
prog[] 存全程式,p 為讀取指標,token[] 暫存當前 Token 字串。if-else + switch 直接實現 DFA 轉移,無顯式狀態表。p--,使該字元下次掃描重新處理。rwtab 判斷是否為關鍵字。sum = sum*10 + (ch-'0') 邊掃描邊計算整數值。| 局限 | 擴展建議 |
|---|---|
| 固定緩衝區 80 字元 | 動態分配或檔案流式讀取 |
| 僅支援 ASCII 英文關鍵字 | Unicode 字元類別判斷 |
| 無浮點數/字串/字元常數 | 增加狀態識別 .、'、" |
| 無註解處理 | 增加 //、/* */ 狀態 |
| 錯誤處理極簡 | 錯誤恢復:跳至下一行/下一分號 |
| 硬編碼難維護 | 改用 Flex 生成或表驅動 DFA |
這個簡易掃描器雖然功能有限,但完整展示了詞法分析的核心要素:**輸入緩衝、空白跳過、模式識別、屬性計算、Token 輸出**。閱讀並單步除錯此程式碼,能讓初學者建立「字元流如何變為 Token 流」的具體心智模型,為後續學習語法分析、工具生成器打下堅實基礎。