ANTLR为何不遵循语法规则顺序?词法匹配异常求助
解决ANTLR词法匹配优先级与解析器规则冲突问题
问题场景
定义了如下ANTLR语法片段:
zLine : Z fahrtnummer verwaltung takanzahl? taktZeitInMinuten? COMMENT ANYTHING NL ; fahrtnummer : INT ; verwaltung : ASCII ;
输入行示例:
*Z 00006 003849 % 00006 003849 00
期望00006匹配INT(作为fahrtnummer),003849匹配ASCII(作为verwaltung),但实际运行中:
- 原语法中
INT定义在ASCII前,003849被识别为INT,导致解析器报错missing ASCII - 调换
INT与ASCII的定义顺序后,所有字母数字串都被识别为ASCII,fahrtnummer等位置也无法匹配INT,同样不符合需求
完整语法、输入文件及测试输出如下:
完整ANTLR语法
grammar FPLAN3; fplan : zLine*; zLine : Z fahrtnummer verwaltung takanzahl? taktZeitInMinuten? COMMENT ANYTHING NL ; fahrtnummer : INT ; verwaltung : ASCII ; takanzahl : INT ; taktZeitInMinuten : INT ; Z: '*Z'; INT: [0-9]+; ASCII: [0-9a-zA-Z]+; //ASCII: [\P{Cc}\P{Cn}\P{Cs}]+; COMMENT: '%'; ANYTHING: .*?; NL: '\r'? '\n' | '\r'; IGNORE_SPACE : [ ] -> skip ;
输入文件
*Z 00006 003849 % 00006 003849 00 *Z 00007 003849 % 00007 003849 00 *Z 00008 003849 % 00008 003849 00
测试输出
[@0,0:1='*Z',<'*Z'>,1:0] [@1,3:7='00006',<INT>,1:3] [@2,9:14='003849',<INT>,1:9] [@3,58:58='%',<'%'>,1:58] [@4,60:64='00006',<INT>,1:60] [@5,66:71='003849',<INT>,1:66] [@6,76:77='00',<INT>,1:76] [@7,78:79='\r\n',<NL>,1:78] [@8,80:81='*Z',<'*Z'>,2:0] [@9,83:87='00007',<INT>,2:3] [@10,89:94='003849',<INT>,2:9] [@11,138:138='%',<'%'>,2:58] [@12,140:144='00007',<INT>,2:60] [@13,146:151='003849',<INT>,2:66] [@14,156:157='00',<INT>,2:76] [@15,158:159='\r\n',<NL>,2:78] [@16,160:161='*Z',<'*Z'>,3:0] [@17,163:167='00008',<INT>,3:3] [@18,169:174='003849',<INT>,3:9] [@19,218:218='%',<'%'>,3:58] [@20,220:224='00008',<INT>,3:60] [@21,226:231='003849',<INT>,3:66] [@22,236:237='00',<INT>,3:76] [@23,238:237='<EOF>',<EOF>,3:78] line 1:9 missing ASCII at '003849' line 1:60 mismatched input '00006' expecting ANYTHING line 2:9 missing ASCII at '003849' line 2:60 mismatched input '00007' expecting ANYTHING line 3:9 missing ASCII at '003849' line 3:60 mismatched input '00008' expecting ANYTHING (fplan (zLine *Z (fahrtnummer 00006) (verwaltung <missing ASCII>) (takanzahl 003849) % 00006 003849 00 \r\n) (zLine *Z (fahrtnummer 00007) (verwaltung <missing ASCII>) (takanzahl 003849) % 00007 003849 00 \r\n) (zLine *Z (fahrtnummer 00008) (verwaltung <missing ASCII>) (takanzahl 003849) % 00008 003849 00))
核心原因
ANTLR的词法分析遵循最长匹配优先原则,当多个词法规则匹配同一输入时,会选择匹配长度最长的规则;若长度相同,则遵循定义顺序优先,即先定义的规则优先匹配。
你定义的INT和ASCII规则存在重叠:纯数字串同时满足两个规则,因此词法分析阶段会直接按优先级生成对应token,完全独立于解析器的规则顺序——解析器只能使用词法阶段生成的token,无法强制改变词法匹配结果。
此外,ANYTHING: .*?的非贪婪匹配会导致它提前终止,无法正确匹配COMMENT到NL之间的所有内容,进一步引发匹配错误。
解决方案
根据需求选择以下两种方案:
方案1:合并词法规则,用解析器规则区分位置/语义
将重叠的词法规则合并为一个通用规则,在解析器层通过位置或语义谓词区分不同用途的token,适用于verwaltung允许为纯数字的场景:
grammar FPLAN3; fplan : zLine*; zLine : Z fahrtnummer verwaltung takanzahl? taktZeitInMinuten? COMMENT ANYTHING NL ; // 仅匹配纯数字的TOKEN作为fahrtnummer fahrtnummer : TOKEN { $TOKEN.text.matches("\\d+") }? ; // 直接取第二个TOKEN作为verwaltung(允许纯数字) verwaltung : TOKEN ; // 仅匹配纯数字的TOKEN作为takanzahl takanzahl : TOKEN { $TOKEN.text.matches("\\d+") }? ; // 仅匹配纯数字的TOKEN作为taktZeitInMinuten taktZeitInMinuten : TOKEN { $TOKEN.text.matches("\\d+") }? ; Z: '*Z'; // 合并原INT和ASCII规则 TOKEN: [0-9a-zA-Z]+; COMMENT: '%'; // 修改ANYTHING为匹配换行前的所有字符 ANYTHING: ~[\r\n]*; NL: '\r'? '\n' | '\r'; IGNORE_SPACE : [ ] -> skip ;
方案2:修改ASCII规则,消除与INT的重叠
如果verwaltung必须包含字母(不能是纯数字),可调整ASCII规则,确保它仅匹配包含字母的字符串,避免与INT规则冲突:
// 修改词法规则部分 INT: [0-9]+; // 要求至少包含一个字母,避免纯数字匹配ASCII ASCII: [a-zA-Z][0-9a-zA-Z]*; COMMENT: '%'; // 修复ANYTHING规则 ANYTHING: ~[\r\n]*; NL: '\r'? '\n' | '\r';
效果验证
修改后,输入中的00006会被正确识别为fahrtnummer,003849被识别为verwaltung,COMMENT后的内容也能完整匹配ANYTHING,不再出现缺失或不匹配的错误。
内容的提问来源于stack exchange,提问作者BetaRide
相关产品推荐
相关产品推荐

