
本文介绍一种实用且符合 Yacc 设计哲学的方案:通过词法分析器(lexer)动态注入伪终结符(如 TABLE_HEADING、TABLE_BODY、STATEMENT),使语法分析器能根据输入上下文自动识别并切换三种解析模式(语句模式、表头模式、表行模式),无需多语法或危险的“lexical tie-ins”。
本文介绍一种实用且符合 yacc 设计哲学的方案:通过词法分析器(lexer)动态注入伪终结符(如 `table_heading`、`table_body`、`statement`),使语法分析器能根据输入上下文自动识别并切换三种解析模式(语句模式、表头模式、表行模式),无需多语法或危险的“lexical tie-ins”。
在 Yacc(或兼容工具如 Bison)中处理具有显式“模式切换”的文本格式(如 Markdown 风格的分隔线驱动表格),核心挑战在于:Yacc 本身是上下文无关语法解析器,不直接支持状态机式的模式跳转。但可通过“ lexer-driven mode signaling ”优雅解决——即让词法分析器(通常是 Lex/Flex 编写的 .l 文件)根据当前解析状态和输入行内容,主动返回语义化伪终结符,而非仅原始 token(如 IDENTIFIER 或 STRING)。
具体实现分为两步:
-
Lexer 维护内部模式状态
在 Flex 规则中定义一个全局变量(如 int current_mode = MODE_STATEMENT;),初始为 MODE_STATEMENT。每当读取一行(yyinput() 或 %option yylineno 配合 input()),检查该行是否全由 '-' 组成(可正则匹配 ^[-]+\n?$)。根据当前模式与连字符行出现次数,更新 current_mode 并返回对应伪 token:%% ^[-]+\n? { switch (current_mode) { case MODE_STATEMENT: current_mode = MODE_TABLE_HEADING; return TABLE_HEADING; case MODE_TABLE_HEADING: current_mode = MODE_TABLE_BODY; return TABLE_BODY; case MODE_TABLE_BODY: current_mode = MODE_STATEMENT; return TABLE_END; } } [^\n]+ { /* 普通行内容,按当前模式归类 */ if (current_mode == MODE_STATEMENT) return STATEMENT_LINE; else if (current_mode == MODE_TABLE_HEADING) return TABLE_HEADING_LINE; else return TABLE_ROW_LINE; } \n { /* 行结束,不返回 token */ } %% -
Yacc 语法定义响应式规则
在 .y 文件中声明这些伪终结符,并构建清晰的层级结构。例如:%token TABLE_HEADING TABLE_BODY TABLE_END STATEMENT_LINE TABLE_HEADING_LINE TABLE_ROW_LINE %start document %% document: /* empty */ | document block ; block: statement_block | table_block ; statement_block: STATEMENT_LINE { /* 处理单条语句 */ } | statement_block STATEMENT_LINE { /* 连续语句 */ } ; table_block: TABLE_HEADING table_heading TABLE_END { /* 表头结束 */ } | TABLE_HEADING table_heading TABLE_BODY table_rows TABLE_END { /* 完整表 */ } ; table_heading: TABLE_HEADING_LINE { /* 解析表头字段 */ } | table_heading ',' TABLE_HEADING_LINE ; table_rows: TABLE_ROW_LINE { /* 解析一行数据 */ } | table_rows TABLE_ROW_LINE ;
⚠️ 关键注意事项:
- 避免在语法中嵌入状态逻辑:不要尝试用 %union 或 $1 传递模式变量,这会破坏 LR(1) 分析器的确定性;状态应完全由 lexer 控制并以 token 形式暴露。
- 连字符行必须独占一行:确保 lexer 正则精确匹配 ^[-]+\n?,防止误触发(如 text----more 被截断识别)。
- 错误恢复需谨慎:若意外收到 TABLE_HEADING 但当前不在 MODE_STATEMENT,应在 lexer 中报错(yyerror("Unexpected table heading")),而非静默忽略。
这种设计将模式切换逻辑下沉至 lexer,保持 yacc 语法简洁、可验证、易维护,同时完全规避了文档所警告的 “lexical tie-ins” —— 即在 yacc 动作中调用 lexer 函数强行修改其内部状态的反模式做法。它本质上是一种“事件驱动语法”,是处理类似 reStructuredText、Org-mode 等结构化文本的标准工程实践。











