当前位置: 首页 > 知识库问答 >
问题:

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

孙经艺
2023-03-14

注意:这是一个自我回答的问题,旨在提供一个关于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;

在这种情况下,ANTLR为什么不将42识别为整数
它应该很好地匹配模式[0-9]+

如果我颠倒定义integeridentifier的顺序,这似乎是可行的,但为什么顺序首先重要呢?

共有1个答案

柳涵映
2023-03-14

在ANTLR中,lexer与解析器是隔离的,这意味着它会根据lexer语法规则将文本拆分为类型化的令牌,解析器对这个过程没有影响(它不能说“现在给我一个integer”)。它自己产生一个令牌流。而且,解析器并不关心令牌文本,它只关心与它的规则匹配的令牌类型。

当几个lexer规则可以匹配相同的输入文本时,这可能很容易成为一个问题。在这种情况下,将根据以下优先规则选择令牌类型:

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

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

在问题的示例中,解析器希望看到以下令牌流以匹配keyvalue解析器规则:identifier'='integer';“其中'=';”是隐式令牌类型。

由于42可以同时匹配integeridentifier并且identifier是首先定义的,解析器将接收以下输入:identifier'='identifier;它将无法与keyvalue规则匹配的'。请记住,解析器不能与lexer通信,它只能从lexer接收数据,因此它不能说“Try to matchintegernext”。

最好将lexer规则的重叠最小化,以限制这种效果的影响。在上面的例子中,我们有几个选项:

  • 标识符重新定义为[A-Za-z][A-Za-z0-9]*(要求它以字母开头)。这完全避免了这个问题,但防止定义以数字开头的标识符名称,因此它改变了语法的意图。
  • 重新排序整数标识符。这解决了大多数情况下的问题,但防止定义完全数值化的标识符,因此它还以一种微妙的、不那么明显的方式改变了语法的意图。
  • 当lexer规则重叠时,使解析器接受两种令牌类型:
    首先,交换integeridentifier以便优先使用integer。然后,定义分析器规则id:IDENTIFIER;然后使用该规则而不是其他分析器规则中的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;

考虑到以下输入:

aaa foo bar baz barz

将从lexer生成以下令牌序列:

identifier'foo'baridentifieridentifierEof

>

  • AAA标识符类型

    只有identifier规则可以与此令牌匹配,没有二义性。

    foo的类型为'foo'

    bar的类型为bar

    此文本与bar规则匹配,该规则在identifier规则之前定义,因此具有优先级。

    BAZ类型为identifier

    该文本与BAZ规则匹配,但也与identifier规则匹配。选择后者,因为它是在bar之前定义的。

    给定语法,BAZ将永远无法匹配,因为Identifier规则已经覆盖了BAZ可以匹配的所有内容。

    barz类型为标识符

    bar规则可以匹配此字符串(bar)的前3个字符,但identifier规则将匹配4个字符。由于identifier匹配较长的子字符串,因此它被选在bar之上。

    EOF文件末尾)是隐式定义的标记类型,它总是出现在输入的末尾。

    作为经验法则,特定的规则应该在更通用的规则之前被定义。如果规则只能匹配先前定义的规则已经覆盖的输入,则永远不会使用该规则。

    隐式定义的规则(如'foo')的作用就好像它们是在所有其他lexer规则之前定义的。由于它们增加了复杂性,因此最好完全避免它们,而声明显式的lexer规则。这种方法的一个显著优点是只在一个地方有一个令牌列表,而不是将它们分散在语法中。

  •  类似资料:
    • 到目前为止,我有这个: 和这个: 当我测试这个时,它不能采取双倍数字,我收到这个消息: 我该如何解决这个问题?

    • 我试图从thymeleaf输入到我的java类中获取一个值。 错误信息 我的应用程序是用Springboot、Java和Thymeleaf创建的。我做错了什么?ModelandView是否可能不能使用PostMapping?我还按照https://spring.io/guides/gs/handling-form-submission/进行了操作,但当我试图遵循逻辑并在项目中实现时,该示例就开始工

    • 我刚刚开始学习ANTLR4 lexer规则。我的目标是为Java属性文件创建一个简单的语法。以下是我目前掌握的信息:

    • 我制作了一个GUI程序,它统计在主文本字段中输入的任何内容的某些元素。如果文本字段为空,则应弹出一条消息,说明用户应在文本字段中输入文本。我做了一个if语句,如果tfMain==null,JOptionPane消息应该会弹出,但由于某些原因它不会弹出。有没有关于为什么它不会弹出的提示? 这是我的代码:

    • 因此,我尝试在单个查询中,仅在行不存在的情况下插入行。 我的疑问如下: 有时(非常罕见,但仍然如此),它会生成以下错误: 违反主键约束“主键用户角色”。无法在对象“dbo”中插入重复键。用户的角色。重复的键值为(29851,1)。 是。下面是表的架构的完整SQL: 背景: 这是由托管在Apache服务器上的PHP脚本执行的,在数百次事件中,“随机”发生一次(很可能与并发相关)。 更多信息: 提供:

    • 我正试图通过Jison为ChucK语言生成JavaScript解析器,并且已经有了一个良好的开端,只是生成的解析器无法处理语言中的歧义。最初的ChucK编译器是由Bison生成的,它必须能够以某种方式解决这些歧义。 出于这个问题的目的,我已经将这个问题简化为一个仅表示一种歧义的解释语法。作为参考,我列出了所有相关文件(包括生成的解析器)的要点。项目结构如下: 语言/词汇。js:lexer 语法本身