解析 EOF 时 ANTLR4 中的错误

Ant*_*ony 2 antlr antlr4

我是 ANTLR(任何版本)的新手,我刚刚开始编写我的第一个语法文件。我正在使用带有 ANTLR 插件 (v1.6) 的 IntelliJ IDE。

我的语法是

grammar TestGrammar;

testfile         :       message+ EOF;
message         :       timestamp WS id (NL | EOF);
timestamp       :       NumericLiteral;
id              :       NumericLiteral;
NumericLiteral  :   INTEGER | DECIMAL;

INTEGER         :   [+-]? [0-9]+; 
DECIMAL         :   [+-]? [0-9]* '.' [0-9]+;
EXPONENT        :   [eE] [+-]? [0-9]+; 

WS: (' ' | '\t')+;
NL:  '\r'? '\n';
Run Code Online (Sandbox Code Playgroud)

当我应用简单的测试输入时

123 1231
123 1312
Run Code Online (Sandbox Code Playgroud)

数据已正确解析,但我在 IntelliJ 预览窗口中收到错误消息。“额外的输入 '<EOF>' 期待 {<EOF>, NL}”

我做错了什么?EOF 似乎被正确检测到...如果我在最后一行添加一个 NL,那么该文件将被正确解析,但我需要确保最终的 NL 是可选的。

格式的其他详细信息:

我们正在对数据格式进行逆向工程,所以老实说,我们真的不知道约束是什么!我们目前的理解是:

  • 每条消息都必须在自己的行上
  • 允许消息之间有空行
  • 不需要在文件末尾换行

我们已经看到文件遵循这些模式的证据,因此我们知道它们是有效的输入。

Vin*_*ega 5

在您的语法中,您明确指出“新行”必须结束一行。这里的问题是:message语言的一部分末尾是否有“新行” ?关于空格也会出现同样的问题。它们是语言的一部分吗?如果没有,您可以跳过它们:

WS: (' ' | '\t') -> skip;
NL: '\r'? '\n' -> skip;
Run Code Online (Sandbox Code Playgroud)

然后,您可以简化message规则:

message: timestamp id;
Run Code Online (Sandbox Code Playgroud)

如果您确实需要保留行尾:

NL: '\r'? '\n';
Run Code Online (Sandbox Code Playgroud)

并在message规则末尾添加此令牌作为可选:

message: timestamp id NL?;
Run Code Online (Sandbox Code Playgroud)

这将适用于您的示例,但将失败:

123 1231

123 1312
Run Code Online (Sandbox Code Playgroud)

所述\n两条线之间将产生一个错误。似乎最有希望的解决方案是第一个(跳过NLWS使用简化message规则)但是,此条目将匹配为 OK:

123 1231 123 1312
Run Code Online (Sandbox Code Playgroud)

它将产生两个message规则上下文。

总而言之,在您的示例中,为了为您提供构建语法的最佳方式,我们必须了解输入语言的约束。

< 编辑 >

关于您的意见,有两种解决方案。要么您确定您的文件格式正确,并且想法是在没有限制的情况下提取文件信息,要么您处于动态状态,您必须确保输入文件符合语法(为了也删除“坏文件”) ”)。

我很确定你是第一种情况(正如你所说的你正在执行逆向工程),所以你可能想从你的文件中创建一个 CST 来提取信息。在这种情况下,考虑到您的输入文件总是格式良好,您无需费心检查是否NL存在于末尾messages(通过构造,文件总是message一行一行)。在这种情况下,您可以跳过不需要的所有内容。语法变成了:

grammar TestGrammar;

testfile        :       message+ EOF;
message         :       timestamp id;
timestamp       :       NumericLiteral;
id              :       NumericLiteral;
NumericLiteral  :   INTEGER | DECIMAL;

INTEGER         :   [+-]? [0-9]+; 
DECIMAL         :   [+-]? [0-9]* '.' [0-9]+;
EXPONENT        :   [eE] [+-]? [0-9]+; 

WS: (' ' | '\t')+ -> skip;
NL:  '\r'? '\n' -> skip;
Run Code Online (Sandbox Code Playgroud)

这个语法会识别

 123 1231
 123 1312
Run Code Online (Sandbox Code Playgroud)

123 1231
  (as many as \n you want between them)
123 1312
Run Code Online (Sandbox Code Playgroud)

但是也

123 1231 123 1312  (-> this will produce two messages as expected)
Run Code Online (Sandbox Code Playgroud)

但是,如果您的输入文件格式不正确,使用此语法,您将无法排除它们。如果您需要确保每行只显示一条消息,您应该使用 Raz Friman 在此处提出的语法的稍微修改版本:

grammar TestGrammar;

testfile        :       (message? NL)* message EOF;
message         :       timestamp id;
timestamp       :       NumericLiteral;
id              :       NumericLiteral;

WS: [\t ]+ -> skip;
NL:  '\r'? '\n';

NumericLiteral  :   INTEGER | DECIMAL;

INTEGER         :   [+-]? [0-9]+;
DECIMAL         :   [+-]? [0-9]* '.' [0-9]+;
EXPONENT        :   [eE] [+-]? [0-9]+;
Run Code Online (Sandbox Code Playgroud)

用这个语法:

123 1231
 (as many as \n you want between them)
123 1312
Run Code Online (Sandbox Code Playgroud)

将被承认,而:

123 1231 123 1312
Run Code Online (Sandbox Code Playgroud)

会抛出错误。