You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重写Bison+Flex计算器程序:仅用Bison和CC编译及异常排查

问题解析:移除Flex后计算器程序的yyerror异常触发问题

问题背景

你原本有一个基于Bison和Flex的命令行计算器,现在想要移除Flex依赖,直接在Bison代码中实现词法分析逻辑,修改后却遇到了奇怪的现象:运行./fb1-5 2*4时,输出变成了2*4error: � = 8,看起来yyerror在main函数的printf之前就被调用了,但明明yyparse是在printf之后才执行的。

错误原因分析

咱们拆解下问题:

  1. EOF处理缺失:你的yylex函数里没有处理getc(fin)返回EOF的情况。当词法分析读到输入字符串的末尾时,getc会返回EOF(值为-1),这个值会进入最后那个“未知字符”分支,触发yyerror(&c),而EOF转成char是不可见的乱码,所以你看到了error: �。
  2. 输出缓冲顺序问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:53:21