ANTLR4.7:规则 XXX 包含一个闭包,该闭包至少有一个可以匹配空字符串的替代项'

ANTLR4.7: rule XXX contains a closure with at least one alternative that can match an empty string'

我正在尝试创建一个语法来匹配如下内容:

(要重现此问题的简单语法,请参阅 ADD 1

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = WebServer
  FILE_GUID                      = 99E87DCF-6162-40c5-9FA1-32111F5197F7
  MODULE_TYPE                    = SEC
  UEFI_SPECIFICATION_VERSION     = 0x00010005

UEFI_SPECIFICATION_VERSION = 0x00010005部分是可选的。

(为了简洁,我省略了一些语法)。

我的语法 1 是这样的:

defines : '[Defines]'
         define_statement+
         ;

define_statement  : 'INF_VERSION' EQ SpecVersion_VersionVal 
                  | 'BASE_NAME' EQ BaseName
                  | 'FILE_GUID' EQ RegistryFormatGUID
                  | 'MODULE_TYPE' EQ Edk2ModuleType
                  | ('UEFI_SPECIFICATION_VERSION' EQ SpecVersion_VersionVal)?
                  ;

ANTLR 4.7 报告此错误:

message: 'rule defines contains a closure with at least one alternative that can match an empty string'

但是如果我像这样更改语法:

defines : '[Defines]'
         define_statement+
         | ('UEFI_SPECIFICATION_VERSION' EQ SpecVersion_VersionVal)? // <<< HERE
         ;

define_statement  : 'INF_VERSION' EQ SpecVersion_VersionVal
                  | 'BASE_NAME' EQ BaseName
                  | 'FILE_GUID' EQ RegistryFormatGUID
                  | 'MODULE_TYPE' EQ Edk2ModuleType

错误消失了。

我的问题是,closure 是什么意思? closure 是哪一部分? define_statement?

在我移动可能为空的备选方案后,defines 规则可以在 '[Defines]' define_statement+('UEFI_SPECIFICATION_VERSION' EQ SpecVersion_VersionVal)? 之间交替,这意味着 defines 仍然可以 匹配空字符串。错误怎么会消失?

添加 1

为了让事情更清楚,我用简化的语法重现这个错误:

grammar test;

rule : alternate+; // <<<<< HERE
alternate : '1'?;

如果我在HERE处使用+*,ANTLR将报告一个错误:

'rule rule contains a closure with at least one alternative that can match an empty string'

如果我在 HERE 处使用 ?,ANTLR 将报告一个 警告:

'rule rule contains an optional block with at least one alternative that can match an empty string'

我仍然不确定为什么。

添加 2

alternate中的每一个都将是rule的子节点,所以如果alternate可以是空字符串,那么逻辑上可能会导致[=无限的子节点=28=]。所以我想这可以解释为什么 ANTLR 禁止我使用 alternate+alternate* 来做这件事。但是如果是alternate?,最多也就一个个子节点。这只是一个性能问题。所以 ANTLR 只是生成一个警告。

让我们从警告开始。该应用程序只是提醒您空字符串可以匹配某些内容。这是一个警告,因为大多数时候,您不希望标记与空字符串匹配。

defines : '[Defines]'
         define_statement+
         ;

define_statement  : 'INF_VERSION' EQ SpecVersion_VersionVal 
                  | 'BASE_NAME' EQ BaseName
                  | 'FILE_GUID' EQ RegistryFormatGUID
                  | 'MODULE_TYPE' EQ Edk2ModuleType
                  | ('UEFI_SPECIFICATION_VERSION' EQ SpecVersion_VersionVal)?
                  ;

因为('UEFI_SPECIFICATION_VERSION' EQ SpecVersion_VersionVal)是可选的(后面跟?,所以可以不用任何东西代替,像这样:

define_statement  : 'INF_VERSION' EQ SpecVersion_VersionVal 
                  | 'BASE_NAME' EQ BaseName
                  | 'FILE_GUID' EQ RegistryFormatGUID
                  | 'MODULE_TYPE' EQ Edk2ModuleType
                  | 
                  ;

最后一个 | 本身意味着规则不能匹配任何内容或空字符串。于是关于警告的谜团就解开了。他们称之为闭包,但您可以将其视为“令牌绑定”或“匹配”。我不认为术语在实际意义上那么重要。

如果您删除替代项,错误就会消失,因为为了清楚起见,再次重写,我们有:

define_statement  : 'INF_VERSION' EQ SpecVersion_VersionVal 
                  | 'BASE_NAME' EQ BaseName
                  | 'FILE_GUID' EQ RegistryFormatGUID
                  | 'MODULE_TYPE' EQ Edk2ModuleType
                  ;

那里没有什么可选的。其中之一必须匹配。

您已经在您的评论中提到,您理解为什么将规则移动到它自己的规则——它可能匹配无​​限数量的空字符串——是一个坏主意,所以我不会赘述就是这里。

但是为什么当您这样做时错误消失了?因为

defines : '[Defines]'
         define_statement+
         | ('UEFI_SPECIFICATION_VERSION' EQ SpecVersion_VersionVal)? // <<< HERE
         ;

保证匹配 something,即使它只是标记 [Defines] ,这是一个隐式词法分析器标记。所以即使 UEFI 是空字符串,仍然有 something 需要解析。在我们检查的第一个版本中情况并非如此;事实上,整个 define_statement 规则可能是一个空字符串。从解析的角度来看,这有很大的不同。

现在有个大问题:[Defines] 部分真的是可选的吗?只有你能回答。但如果是,也许您应该将其重新编码为:

defines : ('[Defines]' define_statement+)?

define_statement  : 'INF_VERSION' EQ SpecVersion_VersionVal 
                  | 'BASE_NAME' EQ BaseName
                  | 'FILE_GUID' EQ RegistryFormatGUID
                  | 'MODULE_TYPE' EQ Edk2ModuleType
                  | 'UEFI_SPECIFICATION_VERSION' EQ SpecVersion_VersionVal

这使它完全可选。同样,只有您可以决定这对您的语法和预期输入是否有效。

有道理吗?希望对你有所帮助!

编辑 1

为了消除错误,试试这个语法(我为测试值做了显式标记以使其达到 运行):

grammar Uefi;
defines : '[Defines]' statement+ ;
statement : define_statement | uefi_statement ;      
uefi_statement : 'UEFI_SPECIFICATION_VERSION' EQ SpecVersion_VersionVal ;
define_statement  : 'INF_VERSION' EQ SpecVersion_VersionVal 
                  | 'BASE_NAME' EQ BaseName
                  | 'FILE_GUID' EQ RegistryFormatGUID
                  | 'MODULE_TYPE' EQ Edk2ModuleType
                  ;
// DUMMY VALUES               
SpecVersion_VersionVal : '0x00010005';
BaseName : 'WebServer';
RegistryFormatGUID : '99E87DCF-6162-40c5-9FA1-32111F5197F7';
Edk2ModuleType : 'SEC';
EQ : '=';
WS : [ \t\r\n]+ -> skip;

只需添加我的解决方案。感谢@JLH。

重要的是要为具有不同性质.

的行设置2条单独的规则
  • linesGroup1_Defines
  • linesGroup2_Defines.

这样,一行的optional nature可以通过|(选择)而不是?(可选)到达optional nature

grammar inf;

start : configSections;

configSections: configSection+
                EOF;

configSection: section_Defines
             | bSection
             ;

section_Defines : '[Defines]'
                 sectionLine_Defines*;

sectionLine_Defines  : linesGroup1_Defines | linesGroup2_Defines;

linesGroup1_Defines : 'INF_VERSION' EQ SpecVersion_VersionVal 
           | 'BASE_NAME' EQ BaseName
           | 'FILE_GUID' EQ RegistryFormatGUID
           | 'MODULE_TYPE' EQ Edk2ModuleType
           ;
linesGroup2_Defines : 'UEFI_SPECIFICATION_VERSION' EQ SpecVersion_VersionVal
              | 'PI_SPECIFICATION_VERSION' EQ SpecVersion_VersionVal
              ;


bSection : '[b]'
           SectionLine_b+;

(为了简洁省略了一些必要的令牌定义)

加 1

再想一想,通过上述解决方案,我没有涵盖 linesGroup1_Deines 是强制性的而 linsGroup2_Defines 是可选的语义。 实际上现在两者都是可选的。它可以接受只有可选行的输入,如下所示:

[Defines]
  UEFI_SPECIFICATION_VERSION     = 0x00010005

我不确定这个语义 can/should 是否包含在语法中。也许我需要进一步完善它。