近日在 VC 知識庫中看到一個 Pascal 語言的詞法分析程式,代碼結構較為複雜。其實詞法分析器的核心原理大同小異,只要掌握了簡單語言的詞法分析方法,擴展到複雜語言只需增加關鍵字與條件判斷。詞法分析是編譯器的基礎階段,也是最容易入手的部分。本文將展示一個針對簡單語言的詞法分析器完整實現過程。
目標語言的文法規則如下(採用 BNF 範式):
G[<程序>]:
<程序> ::= <程序首部> ; <分程序> .
<程序首部> ::= program <標識符>
<分程序> ::= <復合語句>
<復合語句> ::= begin <語句序列> end
<語句序列> ::= <語句> { ; <語句> }
<語句> ::= <賦值語句> | <復合語句> | <條件語句>
<賦值語句> ::= <標識符> := <表達式>
<條件語句> ::= if <布爾表達式> then <語句> else <語句>
<表達式> ::= <項> { (+|-) <項> }
<項> ::= <因式> { (*|/) <因式> }
<因式> ::= <標識符> | <無正負號常量> | '(' <表達式> ')'
<布爾表達式> ::= <表達式> <關系運算符> <表達式>
<關系運算符> ::= = | < | <= | > | >= | <>
<標識符> ::= <字母> { <字母> | <數字> }
<無正負號常量> ::= <數字> { <數字> } [ . <數字> { <數字> } ]
<字母> ::= a | b | c | ... | z
<數字> ::= 0 | 1 | 2 | ... | 9
輸入以 "#" 作為結束符。
根據文法提取關鍵字、運算符、界符,建立單詞符號表:
| 單詞符號 | 類別編號 |
|---|---|
| 標識符 | 1 |
| 常數 | 2 |
| if | 3 |
| then | 4 |
| else | 5 |
| program | 6 |
| begin | 7 |
| end | 8 |
| + | 9 |
| - | 10 |
| * | 11 |
| / | 12 |
| ( | 13 |
| ) | 14 |
| > | 15 |
| >= | 16 |
| < | 17 |
| <= | 18 |
| <> | 19 |
| := | 20 |
| ; | 21 |
| . | 22 |
| , | 23 |
詞法分析器的核心是 parse 函數,負責從輸入文件讀取字符流,識別單詞並將結果寫入輸出文件。主要邏輯包含:
void parse(FILE* fpin, FILE* fpout) {
char arr[MAXBUF];
int i = 0, j = 0;
while (1) {
fscanf(fpin, "%c", &ch);
if (ch == ' ' || ch == '\t') continue;
else if (ch == '\n') lineno++;
else if (IsDigit(ch)) {
while (IsDigit(ch)) { arr[j++] = ch; fscanf(fpin, "%c", &ch); }
fseek(fpin, -1L, SEEK_CUR);
char* temp = (char*)malloc(j + 1);
memcpy(temp, arr, j); temp[j] = '\0'; j = 0;
fprintf(fpout, "%s\t\t%d\n", temp, 2); free(temp);
}
else if (IsAlpha(ch)) {
while (IsAlpha(ch) || IsDigit(ch)) { arr[i++] = ch; fscanf(fpin, "%c", &ch); }
fseek(fpin, -1L, SEEK_CUR);
char* temp = (char*)malloc(i + 1);
memcpy(temp, arr, i); temp[i] = '\0'; i = 0;
if (FindOK(temp)) fprintf(fpout, "%s\t\t%d\n", temp, FindOK(temp));
else fprintf(fpout, "%s\t\t%d\n", temp, 1);
free(temp);
}
else if (ch == ':') { /* 處理 := */ }
else if (ch == '>') { /* 處理 > 和 >= */ }
else if (ch == '<') { /* 處理 <、<=、<> */ }
else {
switch (ch) {
case '-': fprintf(fpout, "-\t\t10\n"); break;
case ';': fprintf(fpout, ";\t\t21\n"); break;
case '+': fprintf(fpout, "+\t\t9\n"); break;
case '*': fprintf(fpout, "*\t\t11\n"); break;
case '/': fprintf(fpout, "/\t\t12\n"); break;
case '(': fprintf(fpout, "(\t\t13\n"); break;
case ')': fprintf(fpout, ")\t\t14\n"); break;
case '.': fprintf(fpout, ".\t\t22\n"); break;
case ',': fprintf(fpout, ",\t\t23\n"); break;
case '#': return;
default: fprintf(fpout, "error in compileing %d lines unknown character %c\n", lineno, ch);
}
}
}
}
本程式展示了詞法分析器的基本框架,代碼結構清晰,註釋詳細。雖然針對的是極簡語言,但擴展性良好:只需在關鍵字表中添加新保留字,在符號處理邏輯中補充新運算符即可支援更豐富的語言特性。歡迎各位指正交流。作者郵箱:brilliant_zhang@21cn.com,QQ:110902663。
本文配套完整源碼可供下載參考。