Lex/Yacc解析Python代码时触发语法错误,请求排查
Yacc解析Python代码时
for后报语法错误的排查与修复 问题背景
自学Lex/Yacc构建Python解析器,解析config.in中的冒泡排序代码时,Yacc在第二行for关键字之后抛出语法错误,Lex调试输出显示识别到for后就触发报错。
相关代码
Lex程序(lexer.l)
%{ #include "y.tab.h" %} %option nounput yylineno %% "+" { return '+'; } "-" { return '-'; } "*" { return '*'; } "/" { return '/'; } "%" { return '%'; } "[" { return LBRACE; } "]" { return RBRACE; } "(" { return LPAREN; } ")" { return RPAREN; } "," { return COMMA; } "." { return PERIOD; } ":" { return COLON; } ":=" { return WALRUS; } "=" { return ASSIGN; } "==" { return EQL; } "!=" { return NEQ; } "<" { return LSS; } ">" { return GTR; } "<=" { return LEQ; } ">=" { return GEQ; } "#" { return COMMENT1; } \"{3} { return COMMENT2; } "True" { return TRUE; } "False" { return FALSE; } "class" { return CLASS; } "def" { return DEF; } "return" { return RETURN; } "if" { return IF; } "elif" { return ELIF; } "else" { return ELSE; } "try" { return TRY; } "except" { return EXCEPT; } "raise" { return RAISE; } "finally" { return FINALLY; } "for" { return FOR; } "in" { return IN; } "is" { return IS; } "not" { return NOT; } "from" { return FROM; } "import" { return IMPORT; } "global" { return GLOBAL; } "lambda" { return LAMBDA; } "nonlocal" { return NONLOCAL; } "pass" { return PASS; } "while" { return WHILE; } "break" { return BREAK; } "continue" { return CONTINUE; } "and" { return AND; } "with" { return WITH; } "as" { return AS; } "yield" { return YIELD; } "del" { return DEL; } "or" { return OR; } "assert" { return ASSERT; } "None" { return NONE; } "print" { return PRINT; } "abs" { return ABS; } "all" { return ALL; } "any" { return ANY; } "range" { return RANGE; } "len" { return LEN; } [\n]+ { ; } [\t]+ { return TAB; } [ ] { ; } . { printf("UnexpChar\n"); } [a-zA-Z][_a-zA-Z0-9]* { yylval.id = yytext[0]; return IDENTIFIER; } [0-9]+ { yylval.num = atoi(yytext); return INTEGER; } %% int yywrap(void) { return 1; }
Yacc程序(parser.y)
%{ #include <stdio.h> int yylex(); void yyerror (char const *s) { fprintf(stderr, "%s\n", s); } %} %union { int num; char id; } %left '+' '-' '*' '/' '%' %token <id> TRUE %token <id> FALSE %token <id> CLASS %token <id> DEF %token <id> RETURN %token <id> IF %token <id> ELIF %token <id> ELSE %token <id> TRY %token <id> EXCEPT %token <id> RAISE %token <id> FINALLY %token <id> FOR %token <id> IN %token <id> IS %token <id> NOT %token <id> FROM %token <id> IMPORT %token <id> GLOBAL %token <id> LAMBDA %token <id> NONLOCAL %token <id> PASS %token <id> WHILE %token <id> BREAK %token <id> CONTINUE %token <id> AND %token <id> WITH %token <id> AS %token <id> YIELD %token <id> DEL %token <id> OR %token <id> ASSERT %token <id> NONE %token <id> LBRACE %token <id> RBRACE %token <id> LPAREN %token <id> RPAREN %token <id> COMMA %token <id> WALRUS %token <id> EQL %token <id> NEQ %token <id> LSS %token <id> GTR %token <id> LEQ %token <id> GEQ %token <id> ASSIGN %token <id> COLON %token <id> TAB %token <id> NEWLINE %token <id> PERIOD %token <id> COMMENT1 %token <id> COMMENT2 %token <id> PRINT %token <id> ABS %token <id> ALL %token <id> ANY %token <id> RANGE %token <id> LEN %token <num> INTEGER %token <id> IDENTIFIER %type <id> funcDef %type <id> parameter %type <id> BIFunc //Built-In Function %type <id> rangeFunc lenFunc %type <id> forLoopDef forLoopBody %% code : funcDef | forLoopDef | forLoopBody | BIFunc ; funcDef : DEF IDENTIFIER LPAREN parameter RPAREN COLON ; forLoopDef : FOR IDENTIFIER IN rangeFunc LPAREN parameter RPAREN COLON ; forLoopBody : forLoopDef code ; parameter : IDENTIFIER | BIFunc ; BIFunc : rangeFunc | lenFunc ; rangeFunc : RANGE LPAREN parameter RPAREN COLON //stop | RANGE LPAREN parameter COMMA parameter RPAREN COLON //start, stop | RANGE LPAREN parameter COMMA parameter COMMA parameter RPAREN COLON //start, stop, step ; lenFunc : LEN LPAREN parameter RPAREN ; %% int main(void) { yyparse(); }
执行命令与输出
lex -d lexer.l yacc --debug --verbose parser.y gcc -ll lex.yy.c y.tab.c ./a.out < config.in
输出:
--(end of buffer or a NUL) --accepting rule at line 36 ("def") --accepting rule at line 76 (" ") --accepting rule at line 79 ("bubblesort") --accepting rule at line 17 ("(") --accepting rule at line 79 ("list") --accepting rule at line 18 (")") --accepting rule at line 21 (":") --accepting rule at line 74 (" ") def bubblesort(list): --accepting rule at line 76 (" ") --accepting rule at line 76 (" ") --accepting rule at line 76 (" ") --accepting rule at line 76 (" ") --accepting rule at line 45 ("for") syntax error for%
错误原因分析
- Lex规则顺序错误:Lex按规则顺序匹配,
.规则位于IDENTIFIER规则之前,单个字符的标识符(如i)会被.规则优先匹配,输出UnexpChar而非IDENTIFIER,导致Yacc无法识别后续的标识符,触发语法错误。 - Yacc语法规则不符合Python语法:
forLoopDef规则错误拆分了range函数结构,rangeFunc规则末尾多余了COLON(range是表达式不是语句,不需要冒号),导致规则与实际Python代码结构不匹配。 - 未处理Python缩进逻辑:Python依赖缩进表示代码块,当前Yacc规则未处理函数定义、循环后的缩进代码,无法识别
for循环属于函数体的一部分。
修复方案
1. 调整Lex规则顺序
将.规则移至所有规则最后,确保IDENTIFIER和INTEGER能被正确匹配:
// 移动后的Lex规则顺序 [a-zA-Z][_a-zA-Z0-9]* { yylval.id = yytext[0]; return IDENTIFIER; } [0-9]+ { yylval.num = atoi(yytext); return INTEGER; } . { printf("UnexpChar\n"); }
2. 修正Yacc语法规则
重新定义符合Python语法的规则:
// 修改rangeFunc,移除末尾COLON rangeFunc : RANGE LPAREN parameter RPAREN | RANGE LPAREN parameter COMMA parameter RPAREN | RANGE LPAREN parameter COMMA parameter COMMA parameter RPAREN ; // 修正forLoopDef规则,匹配Python for循环结构 forLoopDef : FOR IDENTIFIER IN expression COLON ; // 添加表达式规则,覆盖基础表达式类型 expression : IDENTIFIER | INTEGER | BIFunc ; // 调整code规则,支持多语句代码块 code : funcDef | forLoopDef | forLoopBody | statement ; // 处理函数体的缩进与多语句 funcDef : DEF IDENTIFIER LPAREN parameter RPAREN COLON indent code+ ; // 处理for循环体 forLoopBody : forLoopDef indent code+ ; // 定义缩进规则(假设用TAB表示缩进) indent : TAB+ ; // 基础语句示例 statement : PASS ;
3. 重新编译测试
执行原编译命令,此时单个字符标识符会被正确识别,for循环规则匹配Python语法,缩进逻辑也能被解析。
内容的提问来源于stack exchange,提问作者Bach
相关产品推荐
相关产品推荐

