当前位置: 首页 > 知识库问答 >
问题:

ANTLR4语义谓词给错误恢复带来了麻烦。为什么?

仲孙诚
2023-03-14

在使用语义谓词时,我在错误恢复中面临着一个有点奇怪的行为。

我需要错误恢复(特别是单标记插入),因为我将解析的文本有许多“单标记缺失”错误。

让语法(非常简单,它匹配任意数量的“a”,用空格分隔,以分号结尾):

grammar AAAGrammar;

WS : ' '+ -> channel(HIDDEN);
A : 'A';
SEMICOLON : ';';


aaaaa : 
    a* ';'
  ;

a :
    A
  ;

运行以下输入,得到的解析树为:

  • “A A A A;”:(aaaaa(Aa)(Aa)(Aa)(Aa)(Aa);)
  • “A A A”:(aaaa(A)(A)(A)(A)(A) ) (此命令在stderr上发出警告:line 1:5 missing’;'at')。

现在对语法做了一个简单的更改,在“a”规则中引入了一个语义谓词(这一个无伤大雅,但我理解ANTLR4没有--也不应该--对此进行评估),以使其:

a :
    {true}? A
  ;

在相同的输入上再次运行它:-“A A A A;”:(aaaa(A)(A)(A)(A)(A)(A);-“A A A A”:(aaaaa(aa)(aa)(aa)(aa))(该命令还在stderr上发出警告:第1行:5 input''处没有可行的替代方案)。

所以语义谓词完全搞砸了标记注入。

这是意料之中的吗?

为什么?

是否有任何ANTLR4语法技巧可以在不删除sempred的情况下恢复错误?

--- withoutsempred.java 2015-05-04 09:39:22.644069398 -0300
+++ withsempred.java    2015-05-04 09:39:13.400046354 -0300
@@ -56,22 +56,24 @@
    public final AaaaaContext aaaaa() throws RecognitionException {
        AaaaaContext _localctx = new AaaaaContext(_ctx, getState());
        enterRule(_localctx, 0, RULE_aaaaa);
-       int _la;
        try {
+           int _alt;
            enterOuterAlt(_localctx, 1);
            {
            setState(7);
            _errHandler.sync(this);
-           _la = _input.LA(1);
-           while (_la==A) {
-               {
-               {
-               setState(4); a();
-               }
+           _alt = getInterpreter().adaptivePredict(_input,0,_ctx);
+           while ( _alt!=2 && _alt!=-1 ) {
+               if ( _alt==1 ) {
+                   {
+                   {
+                   setState(4); a();
+                   }
+                   } 
                }
                setState(9);
                _errHandler.sync(this);
-               _la = _input.LA(1);
+               _alt = getInterpreter().adaptivePredict(_input,0,_ctx);
            }
            setState(10); match(SEMICOLON);
            }
@@ -101,7 +103,9 @@
        try {
            enterOuterAlt(_localctx, 1);
            {
-           setState(12); match(A);
+           setState(12);
+           if (!( true )) throw new FailedPredicateException(this, " true ");
+           setState(13); match(A);
            }
        }
        catch (RecognitionException re) {
@@ -115,12 +119,25 @@
        return _localctx;
    }

+   public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) {
+       switch (ruleIndex) {
+       case 1: return a_sempred((AContext)_localctx, predIndex);
+       }
+       return true;
+   }
+   private boolean a_sempred(AContext _localctx, int predIndex) {
+       switch (predIndex) {
+       case 0: return  true ;
+       }
+       return true;
+   }
+
    public static final String _serializedATN =
-       "\3\uacf5\uee8c\u4f5d\u8b0d\u4a45\u78bd\u1b2f\u3378\3\5\21\4\2\t\2\4\3"+
-       "\t\3\3\2\7\2\b\n\2\f\2\16\2\13\13\2\3\2\3\2\3\3\3\3\3\3\2\4\2\4\2\2\17"+
-       "\2\t\3\2\2\2\4\16\3\2\2\2\6\b\5\4\3\2\7\6\3\2\2\2\b\13\3\2\2\2\t\7\3\2"+
-       "\2\2\t\n\3\2\2\2\n\f\3\2\2\2\13\t\3\2\2\2\f\r\7\5\2\2\r\3\3\2\2\2\16\17"+
-       "\7\4\2\2\17\5\3\2\2\2\3\t";
+       "\3\uacf5\uee8c\u4f5d\u8b0d\u4a45\u78bd\u1b2f\u3378\3\5\22\4\2\t\2\4\3"+
+       "\t\3\3\2\7\2\b\n\2\f\2\16\2\13\13\2\3\2\3\2\3\3\3\3\3\3\3\3\2\4\2\4\2"+
+       "\2\20\2\t\3\2\2\2\4\16\3\2\2\2\6\b\5\4\3\2\7\6\3\2\2\2\b\13\3\2\2\2\t"+
+       "\7\3\2\2\2\t\n\3\2\2\2\n\f\3\2\2\2\13\t\3\2\2\2\f\r\7\5\2\2\r\3\3\2\2"+
+       "\2\16\17\6\3\2\2\17\20\7\4\2\2\20\5\3\2\2\2\3\t";
    public static final ATN _ATN =
        ATNSimulator.deserialize(_serializedATN.toCharArray());
    static {

假设输入“A A A A”(不带分号),不带语义谓词的版本为

            while (_la==A) {
                {
                {
                setState(4); a();
                }
                }
                setState(9);
                _errHandler.sync(this);
                _la = _input.LA(1);
            }

此块3次,然后继续到

            setState(10); match(SEMICOLON);

匹配(分号)插入缺少的标记。

现在请注意,带有语义谓词的版本去掉了_la=_input.la(1)(lookahead),并切换到更高级的预测_alt=getInterpreter().adaptivePredict(_input,0,_ctx)

对于相同的输入,带有语义谓词的版本如下:

            _alt = getInterpreter().adaptivePredict(_input,0,_ctx);
            while ( _alt!=2 && _alt!=-1 ) {
                if ( _alt==1 ) {
                    {
                    {
                    setState(4); a();
                    }
                    } 
                }
                setState(9);
                _errHandler.sync(this);
                _alt = getInterpreter().adaptivePredict(_input,0,_ctx);
            }

这个块3次,但它并不例外地离开块。最后一个_alt=getInterpreter().adaptivepredict(_input,0,_ctx)抛出org.antlr.v4.runtime.noviablealtexception,完全跳过匹配(分号)

共有1个答案

贡英华
2023-03-14

了解DefaultErrorStrategy采用了一种简单的方法来识别解析异常的规则和来源。

特别是,计算错误恢复例程范围内的谓词非常困难,因此不将其作为DefaultErrorStrategy处理的一部分进行。

考虑一下测试语法的以下变体:

aaaaa   : a* SEMI EOF           ;
a       : ( { true }? B )? A    ;
A   : 'A';
B   : 'B';
SEMI: ';';
WS  : ' '+ -> channel(HIDDEN) ;
line 1:5 no viable alternative at input '<EOF>'
([] ([4] A) ([4] A) ([4] A))

当然,您可以扩展DefaultErrorStrategy,以解决特定于您的语法或比默认策略所能处理的更复杂的问题。

结合扩展DefaultErrorStrategy,考虑扩展RecognitionException以更好地理解起源异常发生的确切位置和方式-请注意getExpectedTokens()方法。

您可能会意识到,作为解析的一部分,处理所有可能的错误形式可能会变得复杂。通常,在解析器中自动更正错误是离散的、定义良好的并且容易识别的。否则,将它们视为语义错误,以便在分析阶段进行纠正。

 类似资料:
  • 我有一个非常简单的语法,如下所示: (我需要使用语义谓词,因为我需要解析关键字可以用作标识符的语言)。 参考:https://github.com/antlr/antlr4/blob/master/doc/predicates.md

  • 我开始为我朋友的服务器创建自己的Discord bot,我一直在处理同样的问题。 我写了一个简单的东西,在我和机器人之间进行第一次交互: 这应该在控制台中写入在bot连接的discord服务器上发送的任何消息的内容(它只连接到我的测试服务器) 我不明白为什么这不起作用,所以我开始寻找它并使用“调试”事件。 当运行时,控制台告诉我: 然后它不断发送心跳并承认它,直到我停止它。 告诉我事情不对劲的是:

  • 关于antlr4的几个问题使用了书中没有提到的lexer谓词,例如28730446使用了head(String),42058127使用了getCharPositionInLine(),23465358使用了_input.la(1)等。是否有可用的lexer谓词列表及其文档?

  • 在ANTLR v3中,句法谓词可以用来解决例如悬空的else问题。ANTLR4似乎接受具有类似歧义的语法,但在解析过程中它会报告这些歧义(例如,“line 2:29 reportAmbiguity d=0(e):ambigalts={1,2},input=...”)。它生成一个解析树,尽管存在这些歧义(根据文档,通过选择第一个备选方案)。但如果我想让它选择其他选择,我能做什么呢?换句话说,我如何显