
本文介绍一种实用的 Yacc 多模式解析方案:通过词法分析器(lexer)动态注入伪终结符(如 TABLE_HEADING、TABLE_BODY、STATEMENT),使语法层无需显式维护状态机,从而清晰分离模式识别与语法规则。
本文介绍一种实用的 yacc 多模式解析方案:通过词法分析器(lexer)动态注入伪终结符(如 `table_heading`、`table_body`、`statement`),使语法层无需显式维护状态机,从而清晰分离模式识别与语法规则。
在传统 Yacc/Bison 解析器中,语法定义是静态的,无法直接表达“根据上下文切换语法规则”的需求。面对类似 Markdown 或自定义表格格式(如题中三段式分隔线 ---- 切换 Statement → Table Heading → Table Row → Statement)的解析任务,硬编码状态机到语法中既繁琐又易错。推荐做法是将模式识别逻辑下沉至词法分析器,让 lexer 主动控制解析流。
具体实现思路如下:
-
Lexer 负责状态跟踪与伪标记生成
在 lexer(如 Flex)中维护一个内部状态变量(例如 mode = MODE_STATEMENT),逐行读取输入,根据当前行内容更新模式:- 遇到首条 ---- 行 → 切换为 MODE_TABLE_HEADING,返回 TABLE_HEADING 伪终结符;
- 遇到第二条 ---- 行 → 切换为 MODE_TABLE_BODY,返回 TABLE_BODY;
- 遇到第三条 ---- 行 → 切换回 MODE_STATEMENT,返回 STATEMENT;
- 其他非分隔行内容,在对应模式下作为 sentences(或 text、cell 等)返回。
Yacc 语法定义聚焦结构而非状态
语法规则不再包含状态转移,而是直接匹配 lexer 提供的伪符号,结构清晰且可读性强:
%token TABLE_HEADING TABLE_BODY STATEMENT
%token sentences table_heading_columns table_body_columns
%%
input: /* empty */
| input line
;
line: TABLE_HEADING sentences ',' table_heading_columns ',' sentences
{ printf("Parsed table heading: %s\n", $2); }
| TABLE_BODY table_body_columns
{ printf("Parsed table body with %d rows\n", count_rows($2)); }
| STATEMENT sentences
{ printf("Parsed statement: %s\n", $2); }
;-
关键注意事项
- ✅ 伪终结符必须在 %token 中声明,否则 yacc 会报未定义符号错误;
- ✅ lexer 返回的 TABLE_HEADING 等应为整型 token 值(如 return TABLE_HEADING;),不可混用字符串字面量;
- ⚠️ 避免在语法动作中修改 lexer 状态——状态管理应严格限定在 lexer 层,保证 parser 的纯函数性;
- ? 若需支持嵌套或更复杂模式(如多级表格),可在 lexer 中扩展状态栈,但核心思想不变:语法描述“是什么”,lexer 决定“现在是什么模式”。
该方案规避了 Yacc 本身不支持多语法/动态切换的限制,也比“Lexical Tie-ins”(即 lexer 中调用 yyparse() 或修改 yylval 的隐式耦合方式)更安全、可测试、易维护。对于初学者而言,这是平衡表达力与工程稳健性的最佳实践。


















