ANTLR4语法解析异常:函数调用未被正确识别问题咨询
我正在用ANTLR4开发f1vae解释器,当前定义的Expr语法规则如下:
grammar Expr; // parser rules prog : ((decl_list expr_seq | expr_seq) ';' NEWLINE?)*; decl_list : decl (decl)*; expr_seq : expr (expr)*; decl : DEF fun var_list '=' expr ENDEF | DEF fun '=' expr ENDEF ; var_list : param (param)*; expr_list : expr ',' expr; expr : LET var '=' expr IN expr # assignExpr | fun CALL # callExpr | fun '(' expr_list ')' # callExpr | expr ('*'|'/') expr # infixExpr | expr ('+'|'-') expr # infixExpr | '(' expr ')' # parensExpr | num # numberExpr | '~' expr # negateExpr | var # varExpr ; num : '-'? INT | '-'? FLOAT ; fun : STR (('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9')* STR)*; var : STR (('_' | '-') STR)*; param : STR ('_' STR)*; // lexer rules NEWLINE: [\r\n]+ ; INT: [0-9]+ ; // should handle negatives FLOAT: [0-9]+'.'[0-9]* ; // should handle signs(+/-) WS: [ \t\r\n]+ -> skip; DEF: 'def'; ENDEF: 'endef'; LET: 'let'; IN: 'in'; CALL: '()'; STR: [a-zA-Z]+;
当输入g(1,x);时,解析结果为:
(prog (expr_seq (expr (var g)) (expr ( (expr 1 ,) x ))) ; \n)
尽管fun '(' expr_list ')'规则在var之前,但解析器未将其识别为函数调用,反而将g判定为变量,原因如下:
核心原因分析
1. 标识符规则的歧义冲突
fun和var都是解析器规则,且两者的匹配范围存在重叠:单个STR(比如g)既符合fun的规则(仅匹配开头的STR,后续无数字或额外STR),也符合var的规则(仅匹配开头的STR,后续无_/-加STR)。
当解析器尝试匹配expr时,虽会优先尝试fun '(' expr_list ')'分支,但如果该分支在后续匹配中出现阻碍(比如expr_list的匹配问题),解析器会回溯并选择更简单的var分支,将g识别为变量,随后把(1,x)当作独立的parensExpr处理。
2. expr_list的定义过于局限
当前expr_list仅支持expr ',' expr(恰好两个参数),这不仅限制了多参数函数调用的功能,还可能导致解析器在匹配时,若参数场景不符合预期就触发回溯,最终放弃函数调用分支。
3. 解析器的回溯行为
ANTLR4的LL(*)解析器会尝试所有可能的匹配路径,当某条路径出现无法匹配的节点(比如expr_list中的逗号处理),就会回溯到之前的决策点,选择下一个可行分支。在这个案例中,解析器因expr_list的匹配问题放弃函数调用分支,转而选择变量分支。
解决方案
合并标识符规则:消除
fun和var的歧义,统一使用id规则,通过上下文区分函数调用与变量引用:expr : LET id '=' expr IN expr # assignExpr | id CALL # callExpr | id '(' expr_list ')' # callExpr | expr ('*'|'/') expr # infixExpr | expr ('+'|'-') expr # infixExpr | '(' expr ')' # parensExpr | num # numberExpr | '~' expr # negateExpr | id # varExpr ; id : STR (('0'..'9' | '_' | '-') STR)*; // 支持字母、数字、下划线、连字符组合的标识符 param : id;扩展
expr_list支持多参数:expr_list : expr (',' expr)*; // 支持1个或多个参数
这样修改后,解析器会根据id后的(来识别函数调用,无(时则识别为变量引用,彻底解决歧义问题。
内容的提问来源于stack exchange,提问作者Gamja

