系列第五篇,使用 **Bison (GNU yacc)** 定義文法,生成 LALR(1) 解析器,實現一個支持浮點數、四則運算、一元負號的迷你計算器。完整 `.y` 文件與編譯運行步驟均已包含。
calc.y%{
#define YYSTYPE double
#include
#include
int yylex(void);
void yyerror(const char *);
%}
%token NUM
%%
input: /* empty */
| input line
;
line: '\n'
| exp '\n' { printf("\t%.10g\n", $1); }
;
exp: NUM { $$ = $1; }
| exp exp '+' { $$ = $1 + $2; }
| exp exp '-' { $$ = $1 - $2; }
| exp exp '*' { $$ = $1 * $2; }
| exp exp '/' { $$ = $1 / $2; }
| exp 'n' { $$ = -$1; } /* 一元負號,lexer 返回 'n' */
;
%%
/* 詞法分析器 */
int yylex(void) {
int c;
while ((c = getchar()) == ' ' || c == '\t'); // 跳過空白
if (c == '.' || isdigit(c)) {
ungetc(c, stdin);
scanf("%lf", &yylval);
return NUM;
}
if (c == EOF) return 0;
if (c == '-') { /* 區分減號與負號:簡單策略,上一 token 為數字或 ')' 視為減號,否則負號 */
int next = getchar();
if (next == ' ' || next == '\t' || next == '\n' || next == EOF) return 'n'; // 簡化處理
ungetc(next, stdin);
}
return c; // 返回單字符 token (+ - * / \n)
}
void yyerror(const char *msg) {
fprintf(stderr, "Error: %s\n", msg);
}
int main() {
return yyparse();
}
# 1. 安裝工具
sudo apt-get install bison flex gcc
# 2. 生成解析器 C 代碼
bison -d calc.y # 生成 calc.tab.c 和 calc.tab.h
# 3. 編譯鏈接
gcc -o calc calc.tab.c -lfl # -lfl 鏈接 flex 庫 (若 yylex 用 flex 生成則需),純手寫 yylex 可省略
# 4. 運行測試
./calc
# 輸入 (後綴/逆波蘭表達式,因文法為 exp exp op):
1 4 + → 輸出 5
3 10 * → 輸出 30
5 2 - → 輸出 3
6 2 / → 輸出 3
4 n → 輸出 -4 (一元負號)
Ctrl+D 退出
| 項目 | 說明 |
|---|---|
%define YYSTYPE double | 語義值類型為 double,支持浮點運算 |
%token NUM | 聲明終結符 NUM,對應浮點數 |
文法 exp exp '+' | **後綴表達式** (逆波蘭),天然無歧義、無需優先級聲明 |
exp 'n' | 一元負號,lexer 中將負號映射為字符 'n' |
yyerror | 語法錯誤回調,這裡僅打印 |
$$ = $1 + $2 | 語義動作:$1/$2 為左右子節點值,$$ 為本節點值 |
%left/%right 聲明運算符優先級與結合性。%left '+' '-'、%left '*' '/'、%right 'n',文法改為 exp '+' exp 等。ID '=' exp。ID '(' arglist ')',語義動作查表調用 sin/cos/pow 等。lexer.l 替換手寫 yylex,更規範。bison -d 必須加 -d 生成 .h,否則外部調用 yylex 找不到 YYSTYPE 定義。yylex 時,**一定要處理換行符** 作為 line 的終結符,否則解析器永遠不歸約。本篇演示了從 Bison 文法定義到可執行計算器的完整鏈路。掌握 .y 文件結構、YYSTYPE、語義動作、詞法分析器接口,即具備了構建領域特定語言 (DSL) 或完整編譯器前端的基礎能力。下一篇將介紹 AST 構建與中間代碼生成。