ANTLR词法分析器如何消除其规则(或为什么我的分析器产生“不匹配的input”错误)?
注意:这是一个自我回答的问题 ,旨在提供有关ANTLR用户最常犯的错误之一的参考。
当我testing这个非常简单的语法时:
grammar KeyValues; keyValueList: keyValue*; keyValue: key=IDENTIFIER '=' value=INTEGER ';'; IDENTIFIER: [A-Za-z0-9]+; INTEGER: [0-9]+; WS: [ \t\r\n]+ -> skip;
通过以下input:
foo = 42;
我结束了以下运行时错误:
第1行:6个不匹配的input“42”,期望INTEGER
第1行:8个不匹配的input';' 期待'='
为什么在这种情况下,ANTLR不认为42
是INTEGER
?
它应该匹配模式[0-9]+
就好了。
如果我将INTEGER
和IDENTIFIER
的定义顺序颠倒INTEGER
,似乎可行,但为什么顺序是重要的呢?
在ANTLR中,词法分析器与parsing器是分离的,这就意味着它会根据词法分析器的语法规则将文本分成types化的标记,parsing器对这个过程没有影响(不能说“现在就给我一个INTEGER
” )。 它自己产生一个令牌stream 。 此外,parsing器不关心标记文本,它只关心标记types以匹配其规则。
当几个词法分析规则可以匹配相同的input文本时,这可能很容易成为一个问题。 在这种情况下,将根据这些优先规则select令牌types:
- 首先,select匹配最长input子string的词法分析规则
- 如果最长匹配的子string等于隐式定义的标记(如
'='
),则使用隐式规则作为标记types - 如果几个词法规则匹配相同的input,请根据定义顺序select第一个
为了有效地使用ANTLR,这些规则非常重要。
在这个问题的例子中,parsing器期望看到下面的标记stream匹配keyValue
parsing器规则: IDENTIFIER
'='
INTEGER
';'
where '='
和';'
是隐式标记types。
由于42
可以匹配INTEGER
和IDENTIFIER
,并且IDENTIFIER
被首先定义,parsing器将接收以下input: IDENTIFIER
'='
IDENTIFIER
';'
它将无法匹配keyValue
规则。 请记住,parsing器不能与词法分析器通信,只能从它接收数据,因此不能说“尝试匹配下一个INTEGER
” 。
build议尽量减less词法分析规则的重叠,以限制这种影响。 在上面的例子中,我们有几个选项:
- 将
IDENTIFIER
重新定义为[A-Za-z] [A-Za-z0-9]*
(要求以字母开头)。 这完全避免了这个问题,但是防止了以数字开始的标识符名称被定义,所以它改变了语法的意图。 - 对
INTEGER
和IDENTIFIER
重新sorting。 这在大多数情况下解决了这个问题,但是阻止了完全数字标识符的定义,因此它也以一种微妙而不明显的方式改变了语法的意图。 - 词法分析器规则重叠时,使parsing器接受两种标记types:
首先,交换INTEGER
和IDENTIFIER
为了优先INTEGER
。 然后,定义一个parsing器规则id: IDENTIFIER | INTEGER;
id: IDENTIFIER | INTEGER;
然后在其他parsing器规则中使用该规则而不是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;
鉴于以下input:
aaa foo bar baz barz
将从词法分析器产生以下令牌序列:
IDENTIFIER
'foo'
BAR
IDENTIFIER
IDENTIFIER
EOF
-
aaa
是IDENTIFIER
types只有
IDENTIFIER
规则可以匹配这个标记,没有歧义。 -
foo
是'foo'
types'foo'
parsing器规则
randomParserRule
引入了隐式的'foo'
标记types,它比IDENTIFIER
规则优先。 -
bar
是types的BAR
该文本与在
IDENTIFIER
规则之前定义的BAR
规则匹配,因此具有优先权。 -
baz
是一个types的IDENTIFIER
该文本与
BAZ
规则相匹配,但也与IDENTIFIER
规则相匹配。 后者是在BAR
之前定义的。鉴于语法,
BAZ
将永远无法匹配,因为IDENTIFIER
规则已经涵盖了BAZ
可以匹配的所有内容。 -
barz
是IDENTIFIER
types的BAR
规则可以匹配该string(bar
)的前3个字符,但是IDENTIFIER
规则将匹配4个字符。 由于IDENTIFIER
匹配较长的子string,因此selectBAR
。 -
EOF
( 文件结束 )是一种隐式定义的标记types,始终出现在input的末尾。
作为一个经验法则,应该在更通用的规则之前定义特定的规则。 如果一个规则只能匹配一个已经被之前定义的规则覆盖的input,那么它将永远不会被使用。
隐式定义的规则(如'foo'
就好像在所有其他词法分析规则之前定义的一样 。 由于它们增加了复杂性,build议完全避免它们,而是声明明确的词法分析规则。 只要将一个令牌列表放在一个地方,而不是将它们分散在文法中,这种方法就是一个很有吸引力的优势。