我可以强制 ANTL4 读取预期的标记而不是让它猜测它可能是哪种标记吗?

Can I force ANTL4 to read expected tokens instead of letting it guessing what kind of token it may be?

我尝试编写一个简单的ANTLR4 语法来解析SRT 字幕文件。我认为这将是一项简单的介绍性任务,但我想我一定漏掉了一些要点。但首先要做的是语法:

grammar Srt;

file    :   subtitle (NL NL subtitle)* EOF;

subtitle:   SUBNO NL
            TSTAMP ' --> ' TSTAMP NL
            LINE (NL LINE)*;

TSTAMP  :   I99 ':' I59 ':' I59 ',' I999;
SUBNO   :   D09+;
NL      :   '\r'? '\n';
LINE    :   ~('\r'|'\n')+;

fragment I999   :   D09 D09 D09;
fragment I99    :   D09 D09;
fragment I59    :   D05 D09;
fragment D09    :   [0-9];
fragment D05    :   [0-5];

这是问题所在的 SRT 文件的开头:

1
00:00:20,000 --> 00:00:26,000

我得到的错误是:

line 2:0 mismatched input '00:00:20,000 --> 00:00:26,000' expecting TSTAMP

所以看起来第二行应用于词法分析器规则 LINE(因为这是它可能匹配的最长标记),但是我期望匹配规则 TSTAMP (这就是为什么它在语法中的 LINE 规则之前定义)。在这一点上,我的 ANTLR4 知识薄弱,无法以某种方式调整语法,词法分析器可能会根据 parser 规则中的当前位置尝试匹配标记上的子集。我打算实现的是匹配 TSTAMP 而不是 LINE,因为 TSTAMP 实际上是预期的输入。也许我可以用一些词法分析器模式来欺骗它,但我很难相信它不能用更简单的方式编写。可以吗?


正如 CoronA 所建议的那样,技巧是将 LINE 规则的决定推迟到解析器,这就是线索。我稍微修改了语法,现在可以顺利解析字幕了:

grammar Srt;

file    :   subtitle (NL NL subtitle)* EOF;

subtitle:   SUBNO NL
            TSTAMP ' --> ' TSTAMP NL
            lines;

lines   :   line (NL line)*;
line    :   (LINECHAR | SUBNO | TSTAMP)*;

TSTAMP  :   I99 ':' I59 ':' I59 ',' I999;
SUBNO   :   D09+;
NL      :   '\r'? '\n';
LINECHAR:   ~[\r\n];

fragment I999   :   D09 D09 D09?;
fragment I99    :   D09 D09;
fragment I59    :   D05 D09;
fragment D09    :   [0-9];
fragment D05    :   [0-5];

您对令牌的定义 LINE 包含所有内容:

LINE    :   ~('\r'|'\n')+;

每个 TSTAMP 也是一个 LINE 但一行可以匹配更长的词法。正如您所看到的那样。 ANTLR 更喜欢最长的匹配。

为了使您的语法正常工作,请将决定什么行是从词法分析器转移到解析器:

subtitle:   SUBNO NL
            TSTAMP ' --> ' TSTAMP NL
            line*;

line:   (LINECHAR | TSTAMP | SUBNO)* NL?;

...

LINECHAR    :   ~('\r'|'\n' ) ; //remove the '+'

您可以看到一行可能包含任何 LINE_CHAR,但也可能包含 TSTAMPSUBNO