基於 EBNF 文法 編寫遞歸下降解析器,是編譯原理教學的經典實踐。本實現支援浮點數、括號、四則運算、空白字符跳過,代碼簡潔且可直接擴展為腳本引擎前端。
::= { ('+' | '-') }
::= { ('*' | '/') }
::= '(' ')' |
::= 浮點數字面值 (含可選符號、小數點、科學記數法)
#include
#include
#include
char token; // 當前看頭符號
double exp(void); // 前向聲明
double term(void);
double factor(void);
char get_token(void) {
char c;
do c = getchar(); while (isspace(c)); // 跳過空白
return c;
}
void match(char expected) {
if (token == expected) token = get_token();
else { fprintf(stderr, "Syntax error: expected '%c'\n", expected); exit(1); }
}
double exp(void) {
double result = term();
while (token == '+' || token == '-') {
if (token == '+') { match('+'); result += term(); }
else { match('-'); result -= term(); }
}
return result;
}
double term(void) {
double result = factor();
while (token == '*' || token == '/') {
if (token == '*') { match('*'); result *= factor(); }
else { match('/'); result /= factor(); }
}
return result;
}
double factor(void) {
double result;
if (token == '(') {
match('(');
result = exp();
match(')');
} else if (isdigit(token) || token == '.' || token == '-') {
ungetc(token, stdin); // 退回首字符
scanf("%lf", &result); // 交給 scanf 解析浮點數
token = get_token(); // 讀取下一符號
} else {
fprintf(stderr, "Syntax error: unexpected '%c'\n", token);
exit(1);
}
return result;
}
int main(void) {
puts("Expression Calculator (enter 'q' to quit)");
for (;;) {
token = get_token();
if (token == 'q') break;
double result = exp();
if (token == '\n') printf("Result: %g\n", result);
else { fprintf(stderr, "Trailing garbage\n"); exit(1); }
}
return 0;
}
$ ./calc
Expression Calculator (enter 'q' to quit)
3.14 * (2 + 1.5) / 0.5
Result: 21.98
(1 + 2) * 3 - 4 / 2
Result: 7
q
Bye
| 技術點 | 實現細節 |
|---|---|
| 看頭符號 | 全局 token 存儲下一未處理字符,get_token 跳過空白 |
| 匹配消耗 | match() 驗證並推進 token |
| 浮點數解析 | ungetc 退回首字符 → scanf("%lf") 利用庫函數完整解析 |
| 錯誤處理 | 語法錯誤即刻 exit(1),生產環境可改為 longjmp 或異常結構 |
| 左遞歸消除 | EBNF 用 `{...}` 迭代替代左遞歸,自然映射為 while 循環 |
map,factor 識別標識符。factor 檢測 identifier '(' 進入參數列表解析。Node* 而非 double,構建語法樹供下一階段優化/代碼生成。match 失敗時同步至下一分隔符 (如 ;、\n) 繼續解析。遞歸下降解析器將 EBNF 文法一對一映射為函數,代碼結構清晰、易於擴展。本計算器以 80 行代碼實現浮點四則運算,展示了「文法驅動代碼」的工程美感。掌握此模式,即可為 DSL、配置語言、腳本引擎快速構建前端解析器。