带参数与嵌入动作的ANTLR左递归规则报错原因及修复方案
问题背景
我们有两个用于描述int a, b, c这类变量声明的ANTLR语法文件:
无报错的原始版本VarsDecl.g4
grammar VarsDecl; decl : type vars ; type : 'int' # IntType | 'float' # FloatType ; vars : left = vars ',' ID # VarsList | ID # VarsID ; ID : [a-z]+ ; WS : [ \t\r\n]+ -> skip ;
添加参数与嵌入动作后报错的版本VarsDeclAG.g4
grammar VarsDeclAG; decl : type vars[$type.text] ; type : 'int' | 'float' ; vars[String typeStr] : left = vars[$typeStr] ',' ID { System.out.println($ID.text + " : " + $typeStr); } | ID { System.out.println($ID.text + " : " + $typeStr); } ; ID : [a-z]+ ; WS : [ \t\r\n]+ -> skip ;
报错情况
ANTLR 4的IntelliJ插件(版本4.1.11)提示:in VarsDeclAG.g4: rule vars is left recursive but doesn't conform to a pattern ANTLR can handle,但原始版本无报错。
需求
请问报错原因是什么?如何修复?不想将vars规则改写为vars : ID (',' ID)*的形式。
报错原因
ANTLR 4支持直接左递归,但对带参数的左递归规则有严格格式限制:左递归调用必须传递完全相同的参数,且递归分支内不能直接嵌入动作代码。
在VarsDeclAG.g4的vars规则中,虽然递归调用时传递了相同的$typeStr参数,但递归分支里直接写入了{ System.out.println(...) }动作,破坏了ANTLR能处理的左递归模式——ANTLR需要左递归规则结构足够规整,才能自动将其转换为等价的循环实现,嵌入动作会干扰这种转换逻辑。而原始版本的vars规则没有参数和嵌入动作,符合ANTLR的直接左递归识别标准,因此无报错。
修复方法
可以把嵌入动作移到规则的后处理块(使用after{...}语法),同时保留左递归结构,既满足ANTLR的左递归格式要求,又能实现输出变量类型的需求:
grammar VarsDeclAGFixed; decl : type vars[$type.text] ; type : 'int' | 'float' ; vars[String typeStr] : left = vars[$typeStr] ',' ID # VarsList | ID # VarsID after { System.out.println($ID.text + " : " + $typeStr); } ; ID : [a-z]+ ; WS : [ \t\r\n]+ -> skip ;
如果不想用后处理块,也可以通过ANTLR的访问者/监听器模式来处理动作逻辑,但上述方案是最直接保留嵌入动作写法的修复方式。
内容的提问来源于stack exchange,提问作者hengxin
相关产品推荐
相关产品推荐

