Antlr4 ANTLR 4:识别和#x27;和';但不是';或';没有空间

Antlr4 ANTLR 4:识别和#x27;和';但不是';或';没有空间,antlr4,Antlr4,我正在使用IntelliJ中的ANTLR 4插件,我有一个最奇怪的bug。我将从相关的解析器/词法分析器规则开始: // Take care of whitespace. WS : [ \r\t\f\n]+ -> skip; OTHER: . -> skip; STRING : '"' [A-z ]+ '"' ; evaluate // starting rule. : textbox? // could be an empty textbox. ; textbox : (r

我正在使用IntelliJ中的ANTLR 4插件,我有一个最奇怪的bug。我将从相关的解析器/词法分析器规则开始:

// Take care of whitespace.
WS : [ \r\t\f\n]+ -> skip;

OTHER: . -> skip;

STRING
: '"' [A-z ]+ '"'
;

evaluate // starting rule.
: textbox? // could be an empty textbox.
;

textbox
: (row '\n')*
;

row
: ability
| ability_list

ability
: activated_ability
| triggered_ability
| static_ability

triggered_ability
: trigger_words ',' STRING 
;

trigger_words
: ('when'|'whenever'|'as') whenever_triggers|'at'
;

whenever_triggers
: triggerer (('or'|'and') triggerer)* // this line has the issue.
;

triggerer
: self

self: '~'
我将此文本传递给它:
无论何时~或
,它在
上失败,说
行1:10输入不匹配”或“预期{'or','and}
。但是,如果每当触发规则的
字符串时(使其成为
或“|”和“
”),我都在
中添加一个空格,那么它就可以正常工作

最奇怪的是,如果我在~and ~
时尝试
,即使规则在
字符串中没有空格,它也可以正常工作。如果我将
和“|”或“
作为lexer规则,这一点不会改变。这很奇怪。我已经确认,在AntlrWorks2中运行“测试平台”时会发生此错误,因此它不仅仅是IntelliJ的东西

这是发生错误时解析树的图像:


好吧,你或多或少都是自己找到答案的,因此我的这个答案将重点解释问题发生的原因

首先,对于每个在这个问题上结结巴巴的人来说,问题是他定义了另一个隐式lexer规则,类似于
”或“
”(注意空格)。将其更改为
”或“
”解决了问题

但为什么会有问题呢?
为了理解这一点,您必须理解如果在一个解析器规则中编写
'
,ANTLR会做什么:编译语法时,它将为每个声明生成一个新的lexer规则。这些lexer规则将在语法中定义的lexer规则之前创建。lexer本身将把给定的输入匹配到令牌中,为此,它一次按声明的顺序处理每个lexer规则。因此,它总是从隐式标记定义开始,然后转到最顶层的“真实”lexer规则。
问题是lexer在这个过程中并不太聪明,这意味着一旦它将一些输入与当前lexer规则匹配,它将创建一个相应的令牌并继续使用后续输入

因此,随后出现的一个lexer规则也将与输入匹配(但作为另一个标记,因为它是一个不同的lexer规则),将被跳过,这样相应的输入可能不会具有预期的标记类型,因为lexer规则本身已过度使用

在您的示例中,自覆盖规则是
'或'
(令牌1)和
'或'
(令牌2)。每个隐式lexer规则声明都会产生不同的lexer规则,当第一个规则匹配时,我假设它是在第二个规则之前声明的。 现在看看你的输入:
无论何时~或~
词法分析器都会开始解释它,它遇到的第一条规则是
”或“
(当然,在匹配开始后),它会匹配输入,因为
之前确实有一个空格。因此,它将匹配它作为令牌1。
另一方面,解析器此时需要一个令牌2,这样它就会抱怨给定的输入(尽管它实际上是在抱怨错误的令牌类型)。只要~或~
将输入更改为
,就会得到正确的解释


这就是为什么不应该在语法中使用隐式标记定义的原因(除非它非常小)。为每个输入创建一个新的lexer规则,并从最具体的规则开始。这意味着匹配特殊字符序列(例如关键字)的规则应该在诸如
ID
STRING
之类的常规lexer规则之前声明。为了防止lexer在无法识别的输入上抛出错误,匹配所有字符的规则必须最后声明,因为它们将覆盖它们之后的每个lexer规则。

好的,您或多或少地自己找到了答案,因此我的这个答案将重点解释为什么问题会在第一次出现地点

首先,对于每个在这个问题上结结巴巴的人来说,问题是他定义了另一个隐式lexer规则,类似于
”或“
”(注意空格)。将其更改为
”或“
”解决了问题

但为什么会有问题呢?
为了理解这一点,您必须理解如果在一个解析器规则中编写
'
,ANTLR会做什么:编译语法时,它将为每个声明生成一个新的lexer规则。这些lexer规则将在语法中定义的lexer规则之前创建。lexer本身将把给定的输入匹配到令牌中,为此,它一次按声明的顺序处理每个lexer规则。因此,它总是从隐式标记定义开始,然后转到最顶层的“真实”lexer规则。
问题是lexer在这个过程中并不太聪明,这意味着一旦它将一些输入与当前lexer规则匹配,它将创建一个相应的令牌并继续使用后续输入

因此,随后出现的一个lexer规则也将与输入匹配(但作为另一个标记,因为它是一个不同的lexer规则),将被跳过,这样相应的输入可能不会具有预期的标记类型,因为lexer规则本身已过度使用

在您的示例中,自覆盖规则是
'或'
(令牌1)和
'或'
(令牌2)。每个隐式lexer规则声明都会产生不同的lexer规则,当第一个规则匹配时,我假设它是在第二个规则之前声明的。 现在看看你的输入:
无论何时~或~
词法分析器都会开始解释它,它遇到的第一条规则是
”或“
(当然在匹配开始后),它会匹配输入,因为在输入之前确实有一个空格