如何让解析器根据上一步中的规则决定使用哪个替代方案

How to make parser decide on which alternative to use, based on the rule in the previous step

我正在使用 ANTLR 4 来解析协议的消息,我们将其命名为 'X'。在提取消息信息之前,我必须检查它是否符合 X 的规则。

假设我们必须解析 X 的 'FOO' 遵循以下规则的消息:

  1. 消息以 'messageIdentifier' 开头,由 3 个字母的保留字 FOO 组成。
  2. 消息包含5个字段,其中前2个是必填项(必须包含),其余3个是可选的(可以不包含)。
  3. 消息的字段由字符“/”分隔。如果字段中没有信息(这意味着该字段是可选的且被省略),则必须保留“/”字符。如果消息中没有报告更多信息,则可以省略消息末尾的可选字段及其关联的字段分隔符“/”。
  4. 一条消息可以展开成多行。每行必须至少有一个非空字段(必填或可选)。此外,每行必须以“/”字符开头,并以跟在“\n”字符后的非空字段结束。例外是第一行始终以保留字 FOO 开头。
  5. 每条消息的字段也有自己的关于接受令牌的规则,这将在下面的语法中显示。

有效 FOO 消息的示例:

  1. FOO/MANDATORY_1/MANDATORY2/OPT 1/HELLO/100\n

  2. FOO/MANDATORY_1/MANDATORY2\n

    /选择 1\n

    /HELLO\n

    /100\n

  3. FOO/MANDATORY_1/MANDATORY2\n

  4. FOO/MANDATORY_1/MANDATORY2//HELLO/100\n

  5. FOO/MANDATORY_1/MANDATORY2///100\n

  6. FOO/MANDATORY_1/MANDATORY2/OPT 1\n

  7. FOO/MANDATORY_1/MANDATORY2 ///100\n

无效 FOO 消息的示例:

  1. FOO\n

    /MANDATORY_1/MANDATORY2/OPT 1/HELLO/100\n

  2. FOO/MANDATORY_1/\n

    MANDATORY2/OPT 1/HELLO/100\n

  3. FOO/MANDATORY_1/MANDATORY2/OPT 1//\n

  4. FOO/MANDATORY_1/MANDATORY2/OPT 1/\n

    /100\n

  5. FOO/MANDATORY_1/MANDATORY2/OPT 1/HELLO/100\n

  6. FOO/MANDATORY_1/MANDATORY2/\n

  7. FOO/MANDATORY_1/MANDATORY2/OPT 1/HELLO/100

下面是上述消息的语法:

grammar Foo_Message


/* Parser Rules */

startRule : 'FOO' mandatoryField_1 ;

mandatoryField_1 : '/' field_1 NL? mandatoryField_2 ;

mandatoryField_2 : '/' field_2 NL? optionalField_3 ;

optionalField_3 : '/' field_3 NL? optionalField_4
                | '/' optionalField_4
                | optionalField_4
                ;

optionalField_4 : '/' field_4 NL? optionalField_5
                | '/' optionalField_5
                | optionalField_5
                ;

optionalField_5 : '/' field_5 NL?
                | NL
                ;

field_1 : (A | N | B | S)+ ;

field_2 : (A | N)+ ;

field_3 : (A | N | B)+ ;

field_4 : A+ ;

field_5 : N+ ;

/* Lexer Rules */

A : [A-Z]+ ;

N : [0-9]+ ;

B : ' ' -> skip ;

S : [*&@#-_<>?!]+ ;

NL : '\r'? '\n' ;

以上语法可以正确解析任何符合 FOO 消息规则的输入。 问题在于解析以“/”字符结尾的行,根据协议的 FOO 消息规则,该行是无效输入。 我知道规则 'optionalField_3'、'optionalField_4' 和 'optionalField_5' 的第二种选择会导致这种行为,但我不知道如何为此制定规则。 不知何故,我需要解析器记住他在看到前一个规则中的一个非遗漏字段后进入 'optionalField_5' 规则,如果我没记错的话,在 ANTLR 中无法完成,因为我无法检查上一个规则的替代方案我达到了当前规则。

有没有办法通过一些明确的选项规则使解析器 'remember' 如此?或者我的语法是否需要重新排列,如果是的话如何?

此语法接受所有示例,字符对字符 copied/pasted 来自您的 post,并标记所有“non-valid FOO 消息”的解析错误。

grammar X;
file_ : s* EOF ;
s : FOO '/' f1 '/' f2 (
    | NL? '/' f3
    | NL? ('/' f3 NL? | '/' ) '/' f4
    | NL? ('/' f3 NL? | '/' ) ('/' f4 NL? | '/') '/' f5
 ) NL;
f1 : (A | N | B | S)+ ;
f2 : (A | N | B)+ ;
f3 : (A | N | B)+ ;
f4 : A+ ;
f5 : N+ ;
FOO: 'FOO';
A : [A-Z]+ ;
N : [0-9]+ ;
B : ' ';
S : [*&@#\-_<>?!]+ ;
NL : '\r'? '\n' ;

可以通过折叠和分组轻松重构它。

在您之前的语法中,词法分析器符号 B 被标记为“跳过”。跳过的符号不会出现在任何标记流中,并且不应直接在解析器规则的 right-hand 端使用它们(请参阅原始语法中的 field_1 )。它是无害的,因为它与其他符号一起改变,即 field_3:(A|N|B)+; 将与 field_3:(A|N)+; 相同,但规则 field_3:(A|N|B)+; 可能会误导其他人,因为 B 永远不会出现在解析树中。我觉得您想在字段中包含空格,因为您可能想要计算字段的文本。因此,我将 B 的规则更改为作为标记出现。

“non-valid FOO 消息”中的#5 与“有效 FOO 消息”中的#1 字符完全相同,您可以在此处查看:

#1: FOO/MANDATORY_1/MANDATORY2/OPT 1/HELLO/100\n
#5: FOO/MANDATORY_1/MANDATORY2/OPT 1/HELLO/100\n

我不明白你的评论“这允许 FOO 消息的可选字段以任何顺序出现”。这里的语法和我之前在注释中提到的语法强制field3出现在field4之前,field4出现在field5之前。 field5 不可能出现在 field3 之前:所需数量的“/”必须出现在 field5 之前。字段可以为空(参见“有效 FOO 消息”的#4)。为了处理这个问题,指定的字段是一个分组,例如 ('/' f3 NL? | '/' )。对于这个分组,唯一的句子形式是“/”、“/f3”、“/f3\n”。注意,这种分组只能出现在后面的字段中,所以两个“\n”是不可能相邻的。

解决这个问题的另一种方法是在整个解析之后使用语义谓词或评估语义等式。

如果还有更多字段,那么您可能不想为 f6f7、....、f10000 添加替代项。在那种情况下,我建议您允许解析中每个字段的任意类型:

s : FOO '/' f1 '/' f2 (
    | NL? ('/' f NL? | '/' )* '/' f
 ) NL;

然后验证语义。

解决方案是重构我的语法以包含 filledFieldemptyField 的规则。

被标记为答案,因为它有助于解决问题。

重构语法:

grammar Foo_Message


/* Parser Rules */

startRule : 'FOO' mandatoryField_1 endRule ;

mandatoryField_1 : '/' field_1 NL? mandatoryField_2 ;

mandatoryField_2 : '/' field_2 NL? (filledOptionalField_3 | emptyOptionalField_3 )? ;

filledOptionalField_3 : '/' field_3 NL? (filledOptionalField_4 | emptyOptionalField_4)? ;
emptyOptionalField_3 : '/' (filledOptionalField_4 | emptyOptionalField_4) ;

filledOptionalField_4 : '/' field_4 NL? filledOptionalField_5? ;
emptyOptionalField_4 : '/' filledOptionalField_5 ;

filledOptionalField_5 : '/' field_5 ;

endRule : NL;

field_1 : (A | N | B | S)+ ;

field_2 : (A | N)+ ;

field_3 : (A | N | B)+ ;

field_4 : A+ ;

field_5 : N+ ;

/* Lexer Rules */

A : [A-Z]+ ;

N : [0-9]+ ;

B : ' ' -> skip ;

S : [*&@#-_<>?!]+ ;

NL : '\r'? '\n' ;