ANTLR词法分析器如何消除其规则的歧义(或者为什么我的解析器会产生“不匹配的输入”错误)?

时间:2017-09-17 19:21:43

标签: parsing antlr antlr4 lexer

  

注意:这是一个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]+就好了。

如果我反转定义INTEGERIDENTIFIER的顺序似乎有效,但为什么订单首先起作用?

1 个答案:

答案 0 :(得分:8)

在ANTLR中,词法分析器与解析器隔离,这意味着它将根据词法分析器语法规则将文本拆分为类型的标记,并且解析器对此过程没有影响(它不能说“现在给我一个INTEGER。它自己生成令牌流。此外,解析器不关心令牌文本,它只关心令牌类型以匹配其规则。

当多个词法分析器规则可以匹配相同的输入文本时,这可能很容易成为问题。在这种情况下,将根据这些优先规则

选择令牌类型
  • 首先,选择与最长输入子字符串匹配的词法分析器规则
  • 如果最长匹配的子字符串等于隐式定义的标记(如'='),则使用隐式规则作为标记类型
  • 如果多个词法规则匹配相同的输入,请根据定义顺序选择 first 一个

为了有效地使用ANTLR,这些规则非常重要。

在问题的示例中,解析器希望看到以下令牌流与keyValue解析器规则匹配:IDENTIFIER '=' INTEGER ';'其中'='';'是隐式令牌类型。

由于42可以匹配 INTEGERIDENTIFIER,并且首先定义IDENTIFIER,解析器将收到以下输入:{ {1}} IDENTIFIER '=' IDENTIFIER,它无法与';'规则匹配。请记住,解析器无法 传播词法分析器,它只能从中接收数据,因此它不能说“尝试匹配keyValue下一步”

建议最小化词法分析器规则重叠以限制此效果的影响。在上面的示例中,我们有几个选项:

  • INTEGER重新定义为IDENTIFIER(要求以字母开头)。这样可以完全避免这个问题,但会阻止定义以数字开头的标识符名称,因此它会改变语法的意图。
  • 重新排序[A-Za-z] [A-Za-z0-9]*INTEGER。这解决了大多数情况下的问题,但是阻止了完全数字标识符的定义,因此它也以一种微妙的,不那么明显的方式改变了语法的意图。
  • 当词法分析器规则重叠时,使解析器接受两种令牌类型:
    首先,交换IDENTIFIERINTEGER以优先考虑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)就像在所有其他词法规则之前定义一样。由于它们增加了复杂性,因此建议完全避免它们并且声明明确的词法规则。只需在一个地方有一个令牌列表,而不是将它们分散在语法中,这是这种方法的一个引人注目的优势。