Luc*_*ski 7 parsing antlr lexer antlr4
注意:这是一个自我回答的问题,旨在提供有关ANTLR用户最常犯错误之一的参考.
当我测试这个非常简单的语法时:
grammar KeyValues;
keyValueList: keyValue*;
keyValue: key=IDENTIFIER '=' value=INTEGER ';';
IDENTIFIER: [A-Za-z0-9]+;
INTEGER: [0-9]+;
WS: [ \t\r\n]+ -> skip;
Run Code Online (Sandbox Code Playgroud)
通过以下输入:
foo = 42;
Run Code Online (Sandbox Code Playgroud)
我最终得到以下运行时错误:
第1行:第6行不匹配输入'42'期望INTEGER
第1行:8输入不匹配';' 期待'='
为什么不承认ANTLR 42作为INTEGER在这种情况下?
它应该匹配模式[0-9]+就好了.
如果我颠倒了定义的顺序INTEGER并且IDENTIFIER定义它似乎有效,但为什么顺序首先重要?
Luc*_*ski 10
在ANTLR中,词法分析器与解析器隔离,这意味着它将根据词法分析器语法规则将文本拆分为类型化的标记,并且解析器对此过程没有影响(例如,它不能说" INTEGER现在给我一个") .它自己生成令牌流.此外,解析器不关心令牌文本,它只关心与其规则匹配的令牌类型.
当几个词法分析器规则可以匹配相同的输入文本时,这可能很容易成为问题.在这种情况下,将根据这些优先级规则选择令牌类型:
'='),则使用隐式规则作为标记类型为了有效地使用ANTLR,这些规则非常重要.
在该问题的示例中,解析器期望看到以下标记流与keyValue解析器规则匹配:IDENTIFIER '=' INTEGER ';'where '='和';'是隐式标记类型.
由于42可以搭配既 INTEGER和IDENTIFIER,并IDENTIFIER首先定义,解析器会收到以下输入: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 ';'.这是第二个lexer行为示例总结:
以下结合语法:
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;
Run Code Online (Sandbox Code Playgroud)
鉴于以下输入:
aaa foo bar baz barz
Run Code Online (Sandbox Code Playgroud)
将从词法分析器生成以下标记序列:
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
该BAR规则可以匹配该字符串(的前3个字符bar),但IDENTIFIER规则将匹配4个字符.作为IDENTIFIER一个较长的字符串相匹配,则选择了BAR.
EOF(文件结尾)是一个隐式定义的标记类型,它始终出现在输入的末尾.
根据经验,应在更通用的规则之前定义特定规则.如果规则只能匹配先前定义的规则已涵盖的输入,则永远不会使用该规则.
隐式定义的规则,例如'foo'它们就像在所有其他词法分析规则之前定义一样.由于它们增加了复杂性,因此建议完全避免它们并且声明明确的词法规则.只需在一个地方拥有一个令牌列表而不是将它们分散在语法中就是这种方法的一个引人注目的优势.