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

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%   

错误原因分析

  1. Lex规则顺序错误:Lex按规则顺序匹配,.规则位于IDENTIFIER规则之前,单个字符的标识符(如i)会被.规则优先匹配,输出UnexpChar而非IDENTIFIER,导致Yacc无法识别后续的标识符,触发语法错误。
  2. Yacc语法规则不符合Python语法:forLoopDef规则错误拆分了range函数结构,rangeFunc规则末尾多余了COLON(range是表达式不是语句,不需要冒号),导致规则与实际Python代码结构不匹配。
  3. 未处理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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 07:03:30