重写Bison+Flex计算器程序:仅用Bison和CC编译及异常排查
问题解析:移除Flex后计算器程序的yyerror异常触发问题
问题背景
你原本有一个基于Bison和Flex的命令行计算器,现在想要移除Flex依赖,直接在Bison代码中实现词法分析逻辑,修改后却遇到了奇怪的现象:运行./fb1-5 2*4时,输出变成了2*4error: � = 8,看起来yyerror在main函数的printf之前就被调用了,但明明yyparse是在printf之后才执行的。
错误原因分析
咱们拆解下问题:
- EOF处理缺失:你的
yylex函数里没有处理getc(fin)返回EOF的情况。当词法分析读到输入字符串的末尾时,getc会返回EOF(值为-1),这个值会进入最后那个“未知字符”分支,触发yyerror(&c),而EOF转成char是不可见的乱码,所以你看到了error: �。 - 输出缓冲顺序问题:
printf输出到stdout是行缓冲模式,而fprintf(stderr)是无缓冲的。虽然你加了fflush(stdout),但yyerror的错误信息是直接输出到stderr的,会先于stdout的内容显示出来,造成“yyerror提前执行”的错觉,实际上代码执行顺序没问题,只是输出顺序乱了。
修复方案
针对这两个问题,咱们做以下修改:
- 在
yylex中添加EOF的处理逻辑,读到EOF时直接返回0(Bison要求词法分析器返回0表示输入结束),不要触发错误。 - 调整yyerror的输出逻辑,仅在遇到真正的未知字符时触发错误。
- 增加内存文件句柄的关闭操作,避免资源泄漏。
修复后的完整代码
修改后的fb1-5.y
/* simplest version of calculator */ %{ # include <stdio.h> FILE * fin; int yylex (void); void yyerror(char *s); %} /* declare tokens */ %token NUMBER %token ADD SUB MUL DIV ABS %token OP CP %% calclist: /* nothing */ | calclist exp { printf("= %d\n", $2); } ; exp: factor | exp ADD exp { $$ = $1 + $3; } | exp SUB factor { $$ = $1 - $3; } | exp ABS factor { $$ = $1 | $3; } ; factor: term | factor MUL term { $$ = $1 * $3; } | factor DIV term { $$ = $1 / $3; } ; term: NUMBER | ABS term { $$ = $2 >= 0? $2 : - $2; } | OP exp CP { $$ = $2; } ; %% #include <ctype.h> #include <string.h> #include <stdlib.h> // 增加fmemopen需要的头文件 int yylex (void) { int c; // 用int存储getc的返回值,避免EOF被截断 /* Skip white space. */ while ((c = getc(fin)) == ' ' || c == '\t'){ continue; } /* 处理EOF,返回0表示输入结束 */ if (c == EOF) { return 0; } /* Process numbers. */ if (isdigit(c)) { ungetc(c, fin); fscanf (fin, "%d", &yylval); return NUMBER; } /* Process addition. */ if (c == '+') { return ADD; } /* Process sub. */ if (c == '-') { return SUB; } /* Process mult. */ if (c == '*') { return MUL; } /* Process division. */ if (c == '/') { return DIV; } /* Process absolute. */ if (c == '|') { return ABS; } /* Process left paren. */ if (c == '(') { return OP; } /* Process right paren. */ if (c == ')') { return CP; } /* 真正的未知字符才触发错误 */ yyerror("Mystery character"); return c; } int main(int argc, char** argv) { int n=1; while (n < argc) { if(argv[n]){ fin = fmemopen(argv[n], strlen(argv[n]), "r"); if (!fin) { perror("fmemopen failed"); return 1; } printf("%s ", argv[n]); fflush(stdout); // 确保stdout内容先输出 yyparse(); fclose(fin); // 关闭内存文件,避免资源泄漏 } n++; } return 0; } void yyerror(char *s) { fprintf(stderr, "error: %s\n", s); }
更新后的Makefile
现在不需要调用Flex了,Makefile简化为:
fb1-5: fb1-5.y bison -d fb1-5.y cc -o $@ fb1-5.tab.c -lm
验证效果
修复后运行命令:
$ ./fb1-5 '1+3' '2*4' 1+3 = 4 2*4 = 8
不会再出现错误信息,输出顺序也完全正常了。
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

