能否禁止语法规则自递归?如何让表达式解析优先匹配重复结构?
这问题我太熟了!你遇到的核心问题是左递归语法规则让解析器优先选择递归嵌套,而不是线性匹配连续的运算符和操作数。想要生成你期望的扁平语法树(比如expression(1, =, 1, =, 1)),完全可以通过调整语法规则或者解析器配置实现,具体方案如下:
1. 重构语法,用重复匹配替代左递归(最直接的方法)
你原来的左递归规则expression : ... | expression (relative_operator expression)+ | ...会让解析器不断递归调用自身,自然生成嵌套结构。我们可以把它拆分成基础表达式单元+重复匹配运算符+基础单元的结构,彻底消除左递归:
# 先定义基础表达式单元(包含所有非递归的表达式类型) primary_expression : NUMBER | '(' expression ')' | /* 其他基础表达式,比如变量、字面量 */ ; # 再用重复匹配替代左递归 expression : primary_expression (relative_operator primary_expression)* ;
这样解析器会先匹配一个primary_expression,然后迭代匹配所有后续的relative_operator + primary_expression组合,自然就能收集到所有连续的操作数和运算符,生成你想要的扁平语法树结构。比如解析1=1=1时,会依次收集1、=、1、=、1,直接组合成扁平的表达式节点。
2. 利用解析器工具的运算符结合性配置(适合用生成器的场景)
如果你用的是ANTLR、YACC/Bison这类解析器生成工具,可以直接给relative_operator设置左结合性,强制解析器按照左到右的顺序处理连续运算:
比如在Bison里可以这样配置:
%left relative_operator # 指定运算符左结合
然后保留类似的递归规则:
expression : expression relative_operator expression | primary_expression ;
这种情况下解析器会生成左结合的嵌套语法树(比如((1=1)=1)),如果想要扁平结构,你可以在语义动作里把嵌套的节点合并成一个包含所有操作数和运算符的扁平节点。
3. 关于“禁止自递归”的需求
其实你不需要完全禁止所有自递归,只需要消除左递归即可。左递归是导致嵌套解析的根源,而改成重复匹配的结构后,语法规则就不再依赖自递归,而是通过迭代完成连续匹配,完全符合你的“优先遵循重复匹配再尝试递归”的需求。
内容的提问来源于stack exchange,提问作者Unlocked

