ANTLR4语法配置问题:含'is'的词汇被误识别为命令
问题解析与解决方案
问题背景
需要解析以下日志条目:
openapi is not validspec is not 1234abcapispec is not valid
当前使用的ANTLR4语法如下:
logEntry : logLine+; logLine : logSubject logCommand logNegate? logRequest EOF; logSubject : TEXT+?; logCommand : IS; logNegate : NOT; logRequest : (TEXT|DIGITS)+?; IS : 'is'; NOT : 'not'; TEXT : [a-zA-Z,]+?; DIGITS : [0-9]+; WS : [ \r\n\t]+ -> skip;
解析apispec is not valid时出现异常:apispec中的子串is被误识别为logCommand对应的IS令牌,导致apispec被拆分为多个TEXT片段,无法作为完整的logSubject被识别。
核心原因
ANTLR4词法分析器遵循规则优先级(先定义的规则优先级更高)和最长匹配原则。原语法中IS规则定义在TEXT之前,且未限制is的上下文,导致词法分析器会优先匹配单词中间的is子串为IS令牌,而非将整个字母序列识别为TEXT。
解决方案
方法1:语义谓词约束命令令牌的上下文
通过语义谓词,让IS和NOT仅在前后字符不是字母/数字时才被识别为命令令牌,确保它们是独立单词。修改后的语法如下:
logEntry : logLine+; logLine : logSubject logCommand logNegate? logRequest EOF; logSubject : TEXT+; logCommand : IS; logNegate : NOT; logRequest : (TEXT|DIGITS)+; // 仅当is前后非字母/数字时,才识别为命令令牌 IS : 'is' {!Character.isLetterOrDigit(_input.LA(-1)) && !Character.isLetterOrDigit(_input.LA(1))}?; // 同理约束not NOT : 'not' {!Character.isLetterOrDigit(_input.LA(-1)) && !Character.isLetterOrDigit(_input.LA(1))}?; // 匹配连续字母(含逗号),优先级低于IS/NOT但会优先匹配完整长序列 TEXT : [a-zA-Z,]+; DIGITS : [0-9]+; WS : [ \r\n\t]+ -> skip;
这种方式直接从词法层面限制命令令牌的触发条件,彻底避免子串误匹配问题。
方法2:调整规则顺序并利用最长匹配
如果不想使用语义谓词,可以将TEXT规则移到IS/NOT之前,同时去掉TEXT的非贪婪匹配(+?改为+)。ANTLR的最长匹配原则会优先匹配完整的字母序列,而非中间的is子串:
logEntry : logLine+; logLine : logSubject logCommand logNegate? logRequest EOF; logSubject : TEXT+; logCommand : IS; logNegate : NOT; logRequest : (TEXT|DIGITS)+; TEXT : [a-zA-Z,]+; IS : 'is'; NOT : 'not'; DIGITS : [0-9]+; WS : [ \r\n\t]+ -> skip;
注意:这种方式仅适用于命令令牌(is/not)不会作为TEXT的前缀或后缀的场景,若存在类似isapi的单词,仍会出现IS被优先匹配的问题,因此方法1的语义谓词方案更通用。
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

