注意:这是一个self-answered question,旨在提供有关ANTLR用户最常犯错误之一的参考。
当我测试这个非常简单的语法时:
grammar KeyValues;
keyValueList: keyValue*;
keyValue: key=IDENTIFIER '=' value=INTEGER ';';
IDENTIFIER: [A-Za-z0-9]+;
INTEGER: [0-9]+;
WS: [ \t\r\n]+ -> skip;
使用以下输入:
foo = 42;
我最终遇到以下运行时错误:
第1行:6输入'42'不匹配期望INTEGER
第1:8行不匹配的输入';'期待'='
为什么ANTLR在这种情况下不会将42
识别为INTEGER
?
它应该匹配模式[0-9]+
就好了。
如果我反转定义INTEGER
和IDENTIFIER
的顺序似乎有效,但为什么订单首先起作用?
答案 0 :(得分:8)
在ANTLR中,词法分析器与解析器隔离,这意味着它将根据词法分析器语法规则将文本拆分为类型的标记,并且解析器对此过程没有影响(它不能说“现在给我一个INTEGER
”。它自己生成令牌流。此外,解析器不关心令牌文本,它只关心令牌类型以匹配其规则。
当多个词法分析器规则可以匹配相同的输入文本时,这可能很容易成为问题。在这种情况下,将根据这些优先规则:
选择令牌类型'='
),则使用隐式规则作为标记类型为了有效地使用ANTLR,这些规则非常重要。
在问题的示例中,解析器希望看到以下令牌流与keyValue
解析器规则匹配:IDENTIFIER
'='
INTEGER
';'
其中'='
和';'
是隐式令牌类型。
由于42
可以匹配 INTEGER
和IDENTIFIER
,并且首先定义IDENTIFIER
,解析器将收到以下输入:{ {1}} IDENTIFIER
'='
IDENTIFIER
,它无法与';'
规则匹配。请记住,解析器无法 传播词法分析器,它只能从中接收数据,因此它不能说“尝试匹配keyValue
下一步”
建议最小化词法分析器规则重叠以限制此效果的影响。在上面的示例中,我们有几个选项:
INTEGER
重新定义为IDENTIFIER
(要求以字母开头)。这样可以完全避免这个问题,但会阻止定义以数字开头的标识符名称,因此它会改变语法的意图。[A-Za-z] [A-Za-z0-9]*
和INTEGER
。这解决了大多数情况下的问题,但是阻止了完全数字标识符的定义,因此它也以一种微妙的,不那么明显的方式改变了语法的意图。IDENTIFIER
和INTEGER
以优先考虑IDENTIFIER
。然后,定义解析器规则INTEGER
,然后在其他解析器规则中使用该规则而不是id: IDENTIFIER | INTEGER;
,这会将IDENTIFIER
更改为keyValue
。以下是第二个词法分析行为示例:
以下组合语法:
key=id '=' value=INTEGER ';'
给出以下输入:
grammar LexerPriorityRulesExample;
// Parser rules
randomParserRule: 'foo'; // Implicitly declared token type
// Lexer rules
BAR: 'bar';
IDENTIFIER: [A-Za-z]+;
BAZ: 'baz';
WS: [ \t\r\n]+ -> skip;
将从词法分析器生成以下标记序列:
aaa foo bar baz barz
IDENTIFIER
'foo'
BAR
IDENTIFIER
IDENTIFIER
EOF
的类型为aaa
只有IDENTIFIER
规则可以匹配此令牌,没有歧义。
IDENTIFIER
的类型为foo
解析器规则'foo'
引入了隐式randomParserRule
令牌类型,该类型优先于'foo'
规则。
IDENTIFIER
的类型为bar
此文本与BAR
规则匹配,该规则在 BAR
规则之前定义,因此具有优先权。
IDENTIFIER
的类型为baz
此文本与IDENTIFIER
规则匹配,但也与BAZ
规则匹配。选择后者是因为它在 IDENTIFIER
之前定义。
鉴于语法,BAR
永远无法匹配,因为BAZ
规则已涵盖IDENTIFIER
可以匹配的所有内容。
BAZ
的类型为barz
IDENTIFIER
规则可以匹配此字符串的前3个字符(BAR
),但bar
规则将匹配4个字符。由于IDENTIFIER
匹配较长的子字符串,因此会选择IDENTIFIER
。{/ p>
BAR
(文件末尾)是一种隐式定义的令牌类型,它始终出现在输入的末尾。
根据经验,在更通用的规则之前,应该定义特定规则。如果规则只能匹配先前定义的规则已涵盖的输入,则永远不会使用。
隐式定义的规则(例如EOF
)就像在所有其他词法规则之前定义一样。由于它们增加了复杂性,因此建议完全避免它们并且声明明确的词法规则。只需在一个地方有一个令牌列表,而不是将它们分散在语法中,这是这种方法的一个引人注目的优势。