ANTLR4 是否仍然支持无扫描器解析器语法?

时间:2021-06-04 01:22:28

标签: java parsing antlr4

我有一个使用 CharsAsTokens 人造词法分析器的无扫描器语法分析器,它为 ANTLR4 版本到 4.6 生成一个可用的 Java 分析器类。但是当更新到 ANTLR 4.7.2 到 4.9.3-SNAPSHOT 时,该工具会从同一个语法文件中生成产生数十个编译错误的代码,详情如下。

我的问题很简单:是否不再支持无扫描器解析器语法,或者必须在 4.7 及更高版本中以不同方式指定基于字符的终端?

更新:

很遗憾,我无法在此处发布我的完整语法,因为它源自 FOUO 安全标记指南,美国政府限制访问该指南(我是 DoD/IC 承包商)。

但是,不兼容的升级问题完全可以通过 Ter 在 CSQL.g4 的第 5.6 节中提到的 The Definitive ANTLR 4 Reference 无扫描仪解析器语法示例重现。

与我的语法一样,CSQL 示例使用 CharsAsTokens.java 作为其标记器,并使用 CharVocab.tokens 作为其标记词汇。

请注意,每个令牌名称都由其 ASCII 字符-文字等效项指定,如下所示:

'\*'=42
'+'=43

并且解析器语法直接在其规则中引用带引号的标记名称,如:

star: '*' ws? ;
plus: '+' ws? ;

这里的问题是,使用 ANTLR4 版本 4.2 到 4.6 会根据这些语法生成可编译的解析器类,而 ANTLR v4.7.2 及更高版本会生成带有大量错误的 Java 代码。

这是来自 ANTLR v4.6 生成的可用 CSQL Java 类定义的片段:

 public static class ArgsContext extends ParserRuleContext {
      public List<ArgContext> arg() {
          return getRuleContexts(ArgContext.class);
      }
      public ArgContext arg(int i) {
          return getRuleContext(ArgContext.class,i);
      }
      public ArgsContext(ParserRuleContext parent, int invokingState) {
          super(parent, invokingState);
      }
      @Override public int getRuleIndex() { return RULE_args; }
      @Override
      public void enterRule(ParseTreeListener listener) {
          if ( listener instanceof CSQLListener ) ((CSQLListener)listener).enterArgs(this);
      }
      @Override
      public void exitRule(ParseTreeListener listener) {
          if ( listener instanceof CSQLListener ) ((CSQLListener)listener).exitArgs(this);
      }
 }

这是ANTLR v4.7.2生成的相应但现在已损坏的代码:

 public static class ArgsContext extends ParserRuleContext {
      public List<ArgContext> arg() {
          return getRuleContexts(ArgContext.class);
      }
      public ArgContext arg(int i) {
          return getRuleContext(ArgContext.class,i);
      }
      public List<TerminalNode> ','() { return getTokens(CSQL.','); }   // line 446
      public TerminalNode ','(int i) {                                  // line 447
          return getToken(CSQL.',', i);                                 // line 448
      }                                                                 // line 449
      public ArgsContext(ParserRuleContext parent, int invokingState) {
          super(parent, invokingState);
      }
      @Override public int getRuleIndex() { return RULE_args; }
      @Override
      public void enterRule(ParseTreeListener listener) {
          if ( listener instanceof CSQLListener ) ((CSQLListener)listener).enterArgs(this);
      }
      @Override
      public void exitRule(ParseTreeListener listener) {
          if ( listener instanceof CSQLListener ) ((CSQLListener)listener).exitArgs(this);
      }
 }

上面的编号行仅由较新的 ANTLR 工具生成(没有添加注释),编译后结果为:

Syntax error on token "','", Identifier expected  CSQL.java     /CSQL/generated-sources  line 446  Java Problem
Syntax error on token "','", delete this token    CSQL.java     /CSQL/generated-sources  line 447  Java Problem
CSQL cannot be resolved to a variable   CSQL.java /CSQL/generated-sources     line 448  Java Problem
Syntax error on token ".", , expected   CSQL.java /CSQL/generated-sources     line 448  Java Problem

那么为什么 ANTLR v4.7+ 中有向后不兼容的变化,我应该如何最好地解决它?

1 个答案:

答案 0 :(得分:1)

尝试定义 GrammarLexer.g4 文件而不是 GrammarLexer.tokens 文件。 (如果您创建 GrammarLexer.tokens 文件,您仍然会像使用 options: { tokenVocab = GrammarLexer; } 一样使用它}它可以很简单:

T1 : ' ';
T2 : '\n';
T3 : '\r';
T4 : 'a';
T5 : 'b';

这将为您创建令牌名称。 Antlr 将允许您在解析器语法规则中使用 'a''\n' 等,但会将它们与词法分析器语法中的词法规则名称匹配并使用该名称(例如:{{ 1}} 当您的规则中有 T4 时,而 'a' 当您有 T2) 以便编译干净。只要您的 '\n' 产生相同的标记值,您就不必使用词法分析器。 (不过,仔细想想,这个杠杆可能相当于您正在使用的 CharsAsTokens 标记器,并且可以保证标记号匹配。)

这似乎仍然可以实现您的目标,即令牌只是一个字符流,并处理解析器规则中的所有内容。 (并且不会比生成 *.tokens 文件更麻烦。两者都需要是所有有效字符的详尽列表。)