FLEX/YACC多行输入解析异常:换行符识别及文件EOF处理问题
基于FLEX/YACC的表达式解析器多行文件解析问题排查
问题描述
开发了一款基于FLEX/YACC的简单表达式解析器,可解析表达式并计算数值结果。单行输入运行正常,但处理多行文件时出现以下问题:
- 初始版本无法匹配
STMTS与'\n',提示“无效字符”; - 修改语法规则后,手动输入可正常运行,但读取文件时遇到EOF仍触发无效字符错误。
相关代码
Flex代码
//lex %{ #include "kfloat.tab.h" %} %option nounput %option noinput %% [ \t] ; [0-9]+ { yylval.d=atof(yytext); return NUMBER; } "sqrt" {return iSQRT; } "log" {return iLOG; } "+" { return OPLUS; } "-" { return OMINUS; } "*" { return OMULT; } "/" { return ODIV; } "(" { return LPAR; } ")" { return RPAR; } "\n" { yylineno++; return yytext[0]; } ";" { return EOL;} . return yytext[0]; %%
YACC代码
//yacc %{ #include <math.h> #include <stdio.h> #include <stdlib.h> #define YYDEBUG 1 void yyerror(char *); int yylex(void); extern int yylineno; %} %union { double d;} %token <d> NUMBER %token EOL %type <d> EXP STMT %left OPLUS OMINUS %left OMULT ODIV %left iLOG iSQRT %left LPAR RPAR %% STMTS : STMTS STMT EOL {printf("END of deriv :2 and MORE statements- now EOF and next line\n");} //edit | STMT EOL {printf("END of deriv :ONE statement - now EOF and next line\n");} //edit | STMTS '\n' //edit ; STMT : EXP | ; EXP : EXP OPLUS EXP {$$=$1+$3; printf("ENDdddd1111: %f\n",$1);} | EXP OMINUS EXP {$$=$1-$3;} | EXP OMULT EXP {$$=$1*$3;} | EXP ODIV EXP {if ($3==0) {yyerror("Zatim nelze delit nulou"); exit(0);} else {$$=$1/$3;}} | LPAR EXP RPAR { ;} | iSQRT EXP {$$=sqrt($2);} | iLOG EXP {$$=log10($2);} | OPLUS EXP {$$= $2;} | OMINUS EXP {$$= -$2;} | NUMBER {$$= $1;} ; %% /* lineno do erroru printf*/ int main(void) { #if YYDEBUG yydebug = 1; #endif if(!yyparse ()) printf("OK\n"); return 0; } void yyerror (char *s) { printf("Syntakticka chyba: %s na radku: %i\n",s,yylineno); }
运行日志
修改前日志
Stack now 0 8 17 Reducing stack by rule 2 (line 28): $1 = nterm STMT () $2 = token EOL () **-> $$ = nterm STMTS () Entering state 7 Stack now 0 7 Reading a token Next token is token "invalid token" ()** Error: popping nterm STMTS () Stack now 0 Cleanup: discarding lookahead token "invalid token" () Stack now 0 ENDdddd1111: 5.000000 ENDddd333: 10.000000 Syntakticka chyba: syntax error na radku: 1
修改后日志
// 读取文件时的日志 END of deriv :ONE statement - now EOF and next line -> $$ = nterm STMTS () Entering state 7 Stack now 0 7 Reading a token Next token is token "invalid token" () // 期望是'\n' Syntaktick▒ chyba Error: popping nterm STMTS () Stack now 0 Cleanup: discarding lookahead token "invalid token" () Stack now 0 // 手动输入相同内容时的日志 END of deriv :ONE statement - now EOF and next line -> $$ = nterm STMTS () Entering state 7 Stack now 0 7 Reading a token Next token is token '\n' () Shifting token '\n' () Entering state 16 Stack now 0 7 16 Reducing stack by rule 3 (line 26): $1 = nterm STMTS () $2 = token '\n' () -> $$ = nterm STMTS () Entering state 7 Stack now 0 7 Reading a token // 输入文件内容 (+12-9); sqrt(16);
问题根源分析
- EOF处理缺失:读取文件到末尾时,Flex返回0(EOF标记)而非
'\n',但当前STMTS规则未定义EOF作为合法终止条件,导致解析器将EOF识别为无效字符。 - 语法规则不完整:修改后的规则允许
STMTS匹配换行,但未支持输入直接以EOF结束,而手动输入时用户会主动输入换行,这造成了两种输入场景的行为差异。 - EXP规则的逻辑漏洞:
LPAR EXP RPAR规则未设置$$ = $2,导致括号包裹的表达式无法正确传递计算结果。
修复方案
1. 更新YACC语法规则,支持EOF终止
修改STMTS规则,添加空规则以接受EOF作为输入结束标记:
STMTS : STMTS STMT EOL {printf("END of deriv :2 and MORE statements- now EOF and next line\n");} | STMT EOL {printf("END of deriv :ONE statement - now EOF and next line\n");} | STMTS '\n' | /* 空规则,允许输入以EOF结束 */ ;
2. 修复EXP规则的括号处理
修正LPAR EXP RPAR规则的语义动作,确保结果正确传递:
| LPAR EXP RPAR { $$ = $2; }
3. 完善STMT规则(可选)
为STMT的EXP分支添加结果输出,空分支保留以支持空行:
STMT : EXP { printf("计算结果: %f\n", $1); } | /* 空行,无处理 */ ;
验证效果
修改后,无论是手动输入多行内容,还是读取不带末尾换行的文件,解析器都能正常处理所有表达式,遇到EOF时会正常终止,不再触发“无效字符”错误。
内容的提问来源于stack exchange,提问作者Mimoa
相关产品推荐
相关产品推荐

