ANTLR 4.5 - 输入不匹配,期望 'x' 却得到了 'x'。

52

我一直在开始使用ANTLR,并注意到它对其词法分析规则非常挑剔。一个极其令人沮丧的例子如下:

grammar output;

test: FILEPATH NEWLINE TITLE ;

FILEPATH: ('A'..'Z'|'a'..'z'|'0'..'9'|':'|'\\'|'/'|' '|'-'|'_'|'.')+ ;
NEWLINE: '\r'? '\n' ;
TITLE: ('A'..'Z'|'a'..'z'|' ')+ ;

这个语法将不会匹配像这样的内容:

c:\test.txt
x

奇怪的是,如果我将 TITLE 改为 TITLE: 'x' ;,它仍然失败了,但这次会给出一个错误消息,说“mismatched input 'x' expecting 'x'”,这让人非常困惑。更奇怪的是,如果我在 test 中用 FILEPATH 替换 TITLE,整个事情就能正常工作(尽管 FILEPATH 匹配的内容比我要匹配的要多,所以通常对我来说,并不是一个有效的解决方案)。

我非常困惑为什么 ANTLR 会给出如此极端奇怪的错误,而在乱搞一番后突然又能正常工作,原因似乎毫无头绪。

3个回答

90

这似乎是对 ANTLR 的一个常见误解:

ANTLR 中的语言处理:

语言处理分为两个严格分离的阶段:

  • 词法分析,即将文本划分为标记
  • 语法分析,即从标记构建解析树

由于词法分析必须在语法分析之前进行,因此有一个结果:词法分析器独立于语法分析器,语法分析器不能影响词法分析

词法分析

ANTLR 中的词法分析工作如下:

  • 所有以大写字母开头的规则都是词法分析规则
  • 词法分析器从开头开始,并尝试找到与当前输入最匹配的规则
  • 最佳匹配是指具有最大长度的匹配,即将下一个输入字符附加到最大长度匹配中得出的标记不被任何词法分析规则匹配
  • 从匹配生成标记:
    • 如果只有一条规则匹配最大长度匹配,则相应的标记将被推送到标记流中
    • 如果多个规则匹配了最大长度匹配,则语法中定义的第一个标记将被推送到标记流中

示例:您的语法有什么问题

您的语法有两个关键规则:

FILEPATH: ('A'..'Z'|'a'..'z'|'0'..'9'|':'|'\\'|'/'|' '|'-'|'_'|'.')+ ;
TITLE: ('A'..'Z'|'a'..'z'|' ')+ ;

通过TITLE匹配的每个匹配项也将通过FILEPATH进行匹配。并且在TITLE之前定义了FILEPATH:所以您期望作为标题的每个标记都将是一个FILEPATH。

有两个提示:

  • 保持词法分析器规则不相交(没有一个标记应该与另一个超集匹配)。
  • 如果您的标记故意匹配相同的字符串,则将它们放入正确的顺序中(在您的情况下,这将足够)。
  • 如果您需要一个由解析器驱动的词法分析器,则必须切换到另一个解析器生成器:PEP-Parsers或GLR-Parsers将执行此操作(但当然,这可能会产生其他问题)。

7
现在我明白了,感谢您的回复!不过,如果能有更有帮助的错误信息就好了,但我知道这可能很困难或不切实际。 - Chiune Sugihara
在运行时,解析器必须假设用户已经了解其行为。然而,如果两个词法分析规则重叠,发出警告也是可以的。 - CoronA
2
ANTLR参考文献总结得非常好! - Cody
我曾经遇到过同样的问题,但原因不同。解析器和词法分析器中的令牌常量不同步,导致在两者中“x”的数字不同。这些标记被正确识别,但解析器无法匹配。清理项目有所帮助。 - avidD

7

这不是OP的直接问题,但是对于那些遇到相同错误信息的人,这里有一些你可以检查的内容。


当我引入一个新的关键字时,我遇到了相同的Mismatched Input 'x' expecting 'x'模糊的错误信息。原因是我将新的关键字放在了我的VARNAME词法规则之后,它被分配为变量名而不是作为新的关键字。我通过将关键字放在VARNAME规则之前来解决这个问题。


0
任何对TITLE的输入都会与FILEPATH标记匹配。语言处理器在处理输入时会停止在FILEPATH上做出选择,没有机会到达TITLE标记。这导致了问题的出现。
解决方法是将TITLE放在FILEPATH标记之前(或者将FILEPATH放在TITLE标记之后)。例如:
grammar output;

test: FILEPATH NEWLINE TITLE ;

NEWLINE: '\r'? '\n' ;
TITLE: ('A'..'Z'|'a'..'z'|' ')+ ;
FILEPATH: ('A'..'Z'|'a'..'z'|'0'..'9'|':'|'\\'|'/'|' '|'-'|'_'|'.')+ ;

P.S. 这个解决方案适用于类似的输入。
c:\test.txt
x

如果您输入的是没有文件扩展名或文件夹名称的简单文件名,您将遇到相同的问题。
test
x

所以我会考虑对 FILEPATH 使用一些限制,使其与 TITLE 不同。例如使用下一个正则表达式 [A-Za-z][:][\\/][A-Za-z0-9]+'.'[A-Za-z0-9]+ 来匹配 FILEPATH(不确定,因为我对您的所有情况都不清楚)。 所以最终的解决方案可能是这样的:

grammar output;

test: FILEPATH NEWLINE TITLE ;

fragment FILENAME: TITLE DOT EXTENSION;
fragment LETTER: [a-zA-Z] ;
fragment DIGIT: [0-9] ;
fragment UNDERSCORE: '_' ;
fragment SPACE: ' ' ;
fragment ESCAPE: '\\' ;
fragment SLASH: '/' ;
fragment QUOTE: '"' ;
fragment PLUS: '+';
fragment MINUS: '-';
fragment COLON: ':' ;
fragment DOT: '.';

EXTENSION: DOT (LETTER | DIGIT)+;
SEPARATOR: ESCAPE | SLASH;
DISC: LETTER COLON;
TITLE: (LETTER | DIGIT | UNDERSCORE | MINUS)+ ;
FILEPATH: DISC?(SEPARATOR TITLE)+ EXTENSION ;
NEWLINE: '\r'? '\n' ;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接