ANTLR 4:识别 'and' 但不识别 'or' 没有 space
ANTLR 4: Recognises 'and' but not 'or' without a space
我在 IntelliJ 中使用 ANTLR 4 插件,我遇到了最奇怪的错误。我将从相关的 parser/lexer 规则开始:
// 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: '~'
我将此文本传递给它:whenever ~ or ~
,它在 or
上失败,说 line 1:10 mismatched input ' or' expecting {'or', 'and'}
。但是,如果我将 space 添加到 whenever_triggers
规则的 or
字符串(使其成为 ' or'|'and'
),它就可以正常工作。
最奇怪的是,如果我尝试 whenever ~ and ~
,即使 and
字符串中没有包含 space 的规则,它也能正常工作。如果我将 'and'|'or'
设为词法分析器规则,这也不会改变。这太奇怪了。我已经确认在 Antlrworks 2 中 运行 'test rig' 时会发生此错误,因此这不仅仅是 IntelliJ 的问题。
这是错误发生时解析树的图像:
好吧,你已经或多或少地自己找到了答案,所以我的这个答案我将重点解释问题发生的原因。
首先 - 对于遇到这个问题的每个人 - 问题是他定义了另一个隐式词法分析器规则,看起来像这样 ' or'
(注意白色 space)。将其更改为 'or'
解决了问题。
但为什么这是个问题?
为了理解,如果您在其中一个解析器规则中编写 '<something>'
,您必须了解 ANTLR 会做什么:编译语法时,它将为每个声明生成一个新的词法分析器规则。这些词法分析器规则将在语法中定义的词法分析器规则之前创建。词法分析器本身会将给定的输入匹配到标记中,为此它会按照声明的顺序一次处理每个词法分析器规则。因此,它将始终从隐式标记定义开始,然后移动到最顶层的“真实”词法分析器规则。
问题是 lexer 对这个过程不太聪明,这意味着一旦它匹配了一些输入与当前的 lexer 规则,它将创建一个相应的标记并继续尾随输入。
因此,随后出现的词法分析器规则也将与输入匹配(但作为另一个标记,因为它是不同的词法分析器规则)将被跳过,因此相应的输入可能没有预期的标记类型因为词法分析器规则已经覆盖了自己。
在您的示例中,自我覆盖规则是 ' or'
(令牌 1)和 'or'
(令牌 2)。这些隐式词法分析器规则声明中的每一个都将导致不同的词法分析器规则,并且当第一个匹配时,我假设它是在第二个之前声明的。
现在看看你的输入: whenever ~ or ~
词法分析器将开始解释它,它遇到的第一个规则是 ' or'
(当然是在开始匹配之后)它会匹配输入,因为确实有一个space 在 or
之前。因此它将匹配它作为令牌 1.
另一方面,解析器此时期望令牌 2,以便它会抱怨给定的输入(尽管它实际上是在抱怨错误的令牌类型)。将输入更改为 whenever ~or ~
将得到正确的解释。
这正是您不应该在语法中使用隐式标记定义的原因(除非它真的很小)。为每个输入创建一个新的词法分析器规则,并从最具体的规则开始。这意味着匹配特殊字符序列(例如关键字)的规则应该在像 ID
或 STRING
之类的一般词法分析器规则之前声明。为了防止词法分析器在无法识别的输入时抛出错误而匹配所有字符的规则必须最后声明,因为它们会覆盖它们之后的每个词法分析器规则。
我在 IntelliJ 中使用 ANTLR 4 插件,我遇到了最奇怪的错误。我将从相关的 parser/lexer 规则开始:
// 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: '~'
我将此文本传递给它:whenever ~ or ~
,它在 or
上失败,说 line 1:10 mismatched input ' or' expecting {'or', 'and'}
。但是,如果我将 space 添加到 whenever_triggers
规则的 or
字符串(使其成为 ' or'|'and'
),它就可以正常工作。
最奇怪的是,如果我尝试 whenever ~ and ~
,即使 and
字符串中没有包含 space 的规则,它也能正常工作。如果我将 'and'|'or'
设为词法分析器规则,这也不会改变。这太奇怪了。我已经确认在 Antlrworks 2 中 运行 'test rig' 时会发生此错误,因此这不仅仅是 IntelliJ 的问题。
这是错误发生时解析树的图像:
好吧,你已经或多或少地自己找到了答案,所以我的这个答案我将重点解释问题发生的原因。
首先 - 对于遇到这个问题的每个人 - 问题是他定义了另一个隐式词法分析器规则,看起来像这样 ' or'
(注意白色 space)。将其更改为 'or'
解决了问题。
但为什么这是个问题?
为了理解,如果您在其中一个解析器规则中编写 '<something>'
,您必须了解 ANTLR 会做什么:编译语法时,它将为每个声明生成一个新的词法分析器规则。这些词法分析器规则将在语法中定义的词法分析器规则之前创建。词法分析器本身会将给定的输入匹配到标记中,为此它会按照声明的顺序一次处理每个词法分析器规则。因此,它将始终从隐式标记定义开始,然后移动到最顶层的“真实”词法分析器规则。
问题是 lexer 对这个过程不太聪明,这意味着一旦它匹配了一些输入与当前的 lexer 规则,它将创建一个相应的标记并继续尾随输入。
因此,随后出现的词法分析器规则也将与输入匹配(但作为另一个标记,因为它是不同的词法分析器规则)将被跳过,因此相应的输入可能没有预期的标记类型因为词法分析器规则已经覆盖了自己。
在您的示例中,自我覆盖规则是 ' or'
(令牌 1)和 'or'
(令牌 2)。这些隐式词法分析器规则声明中的每一个都将导致不同的词法分析器规则,并且当第一个匹配时,我假设它是在第二个之前声明的。
现在看看你的输入: whenever ~ or ~
词法分析器将开始解释它,它遇到的第一个规则是 ' or'
(当然是在开始匹配之后)它会匹配输入,因为确实有一个space 在 or
之前。因此它将匹配它作为令牌 1.
另一方面,解析器此时期望令牌 2,以便它会抱怨给定的输入(尽管它实际上是在抱怨错误的令牌类型)。将输入更改为 whenever ~or ~
将得到正确的解释。
这正是您不应该在语法中使用隐式标记定义的原因(除非它真的很小)。为每个输入创建一个新的词法分析器规则,并从最具体的规则开始。这意味着匹配特殊字符序列(例如关键字)的规则应该在像 ID
或 STRING
之类的一般词法分析器规则之前声明。为了防止词法分析器在无法识别的输入时抛出错误而匹配所有字符的规则必须最后声明,因为它们会覆盖它们之后的每个词法分析器规则。