1. 为什么需要自己实现四则运算编译器
第一次接触编译原理时,很多人都会被那些晦涩难懂的概念吓退。抽象语法树、词法分析、语法分析...这些术语听起来就让人头大。但当我真正用Flex和Bison实现了一个简单的四则运算编译器后,才发现编译原理并没有想象中那么可怕。
四则运算编译器是学习编译原理的绝佳切入点。它足够简单,不需要处理复杂的语言特性;又足够完整,涵盖了编译器工作的全流程。通过这个项目,你能真正理解代码是如何从文本变成可执行指令的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境准备
2.1 工具链安装
在Ubuntu系统上安装Flex和Bison非常简单:
bash复制sudo apt-get update
sudo apt-get install flex bison
对于Windows用户,推荐使用MinGW或Cygwin环境。安装时记得将bin目录添加到系统PATH中,这样才能在命令行直接调用flex和bison。
2.2 验证安装
安装完成后,可以通过以下命令检查版本:
bash复制flex --version
bison --version
3. 词法分析器实现
3.1 Flex基础语法
Flex文件通常以.l为后缀,由三个部分组成:
code复制定义段
%%
规则段
%%
用户代码段
定义段用于声明正则表达式和初始状态;规则段定义了模式匹配时的动作;用户代码段可以包含自定义函数。
3.2 四则运算的词法规则
创建一个calc.l文件,定义词法规则:
flex复制%{
#include "calc.tab.h"
%}
%%
[0-9]+ { yylval = atoi(yytext); return NUMBER; }
"+" { return PLUS; }
"-" { return MINUS; }
"*" { return TIMES; }
"/" { return DIVIDE; }
"(" { return LPAREN; }
")" { return RPAREN; }
[ \t\n] ; /* 忽略空白字符 */
. { printf("非法字符: %s\n", yytext); }
%%
这个规则可以识别数字、运算符和括号。当遇到数字时,会将其转换为整数值存储在yylval中。
4. 语法分析器实现
4.1 Bison基础语法
Bison文件通常以.y为后缀,结构类似Flex:
code复制定义段
%%
规则段
%%
用户代码段
定义段包含token声明、优先级定义等;规则段使用BNF范式描述语法规则。
4.2 四则运算的语法规则
创建calc.y文件:
bison复制%{
#include <stdio.h>
int yylex();
void yyerror(const char *s);
%}
%token NUMBER
%token PLUS MINUS TIMES DIVIDE
%token LPAREN RPAREN
%left PLUS MINUS
%left TIMES DIVIDE
%%
input: /* 空 */
| input line
;
line: '\n'
| exp '\n' { printf("= %d\n", $1); }
;
exp: NUMBER { $$ = $1; }
| exp PLUS exp { $$ = $1 + $3; }
| exp MINUS exp { $$ = $1 - $3; }
| exp TIMES exp { $$ = $1 * $3; }
| exp DIVIDE exp { $$ = $1 / $3; }
| LPAREN exp RPAREN { $$ = $2; }
;
%%
void yyerror(const char *s) {
fprintf(stderr, "错误: %s\n", s);
}
int main() {
return yyparse();
}
这里定义了运算符的优先级和结合性,%left表示左结合,后面的运算符优先级更高。
5. 编译与测试
5.1 生成解析器代码
依次执行以下命令:
bash复制flex calc.l
bison -d calc.y
gcc lex.yy.c calc.tab.c -o calc -lfl
这会生成可执行文件calc。如果遇到找不到fl库的错误,可以尝试改为-ll。
5.2 测试编译器
运行生成的calc程序,输入表达式测试:
code复制1 + 2 * 3
= 7
(1 + 2) * 3
= 9
10 / (2 + 3)
= 2
6. 常见问题排查
6.1 移位/归约冲突
这是Bison最常见的警告,通常是由于语法存在二义性。可以通过明确定义运算符优先级来解决。
6.2 内存泄漏
Flex生成的词法分析器可能产生内存泄漏。可以在main函数结束时调用yylex_destroy()来释放资源。
6.3 调试技巧
使用bison的-v选项可以生成.output文件,查看详细的状态机信息:
bash复制bison -v calc.y
7. 项目扩展方向
这个基础版本还可以进一步扩展:
- 支持浮点数运算
- 添加变量赋值功能
- 实现函数定义和调用
- 增加条件判断语句
每个扩展都是学习编译器新特性的好机会。比如实现变量需要符号表,函数调用涉及栈帧管理,这些都是编译原理的核心概念。
