Bison编译语法报2个shift/reduce冲突如何修复?可用优先级解决吗?
冲突产生的根本原因
这2个shift/reduce冲突来自语法本身的上下文二义性:当解析器扫描到一个独立的IDENTIFIER,且前瞻符号为<或[时,无法判断当前语法分支的走向:
- 如果把
IDENTIFIER归约为post_expression,后续会走表达式分支:</>对应比较运算符,[对应数组下标访问 - 如果不归约、直接移进
</[,后续会走类型声明分支:</>对应泛型参数列表的包裹符,[对应数组类型标记
你贴出的State 1状态信息已经明确标注了冲突点:解析器同时存在「移进</[匹配类型规则」和「归约IDENTIFIER到post_expression匹配表达式规则」两个可选动作,无法自动决策。
能否通过优先级规则解决
完全不能。
优先级规则的适用场景是:两个冲突动作对应的语法语义有明确的优先级高低(比如四则运算的乘除优先级高于加减、悬挂else优先匹配最近的if),只需要指定优先级就能覆盖所有合法输入。但这里的冲突是语义分支的本质分歧:不管你给</[和对应产生式设置什么优先级,都会必然误判一类合法输入:
- 如果设置优先移进
</[(优先匹配类型规则),所有形如a < b;、arr[0];的普通表达式都会被错误解析为类型开头 - 如果设置优先归约到
post_expression(优先匹配表达式规则),所有形如vector<int> a;、int[] arr;的类型声明都会解析失败
具体修复方案
工业级语法项目通用的稳定解法是在词法层区分类型名和普通标识符,从根源消除二义性:
- 在词法分析阶段维护作用域感知的符号表,提前记录所有已经声明的类型名称,将这类标识符输出为独立的
TYPE_NAMEtoken,和普通变量/函数名对应的IDENTIFIERtoken做区分 - 修改语法中
data_type的产生式,将开头的IDENTIFIER替换为TYPE_NAME,post_expression中仍然保留IDENTIFIER作为普通表达式开头
修改后的核心语法片段如下:
%token NAMESPACE IDENTIFIER TYPE_NAME %start statement %% post_expression : IDENTIFIER | post_expression '[' expression ']' | post_expression '.' IDENTIFIER '(' ')' | post_expression '.' IDENTIFIER ; expression : post_expression | expression '<' post_expression | expression '>' post_expression ; data_type : TYPE_NAME | TYPE_NAME '<' data_type_list '>' | TYPE_NAME '<' '>' | TYPE_NAME '[' ']' ; // 其余statement、initializer、data_type_list产生式保持原有逻辑不变
修改后解析器遇到TYPE_NAME就必然走类型分支,遇到IDENTIFIER就必然走表达式分支,两个shift/reduce冲突会直接消失。C、C++、Java、C#等所有需要同时支持表达式和泛型/数组类型声明的语言,基本都采用这个思路解决同类冲突。
如果不想改动词法层,也可以选择重构纯语法规则,将标识符开头的公共前缀结构合并解析,等扫描到;、=这类明确的语句边界符号后,再在语义分析阶段区分当前节点是表达式还是类型。但这种方案语法规则复杂度会大幅提升,后续语义处理的成本也很高,不适合大型语法项目使用。
内容的提问来源于stack exchange,提问作者Polla Toube
相关产品推荐
相关产品推荐

