ANTLR4语义谓词混乱了错误恢复。为什么呢?

时间:2015-05-04 01:48:34

标签: antlr4

在使用语义谓词时,我在错误恢复方面遇到了一些奇怪的行为。

我需要错误恢复(特别是单个令牌插入),我要解析的文本有很多"单个丢失的令牌"错误。

我还需要语义谓词,因为像ANTLR4: Matching all input alternatives exaclty once这样的东西(第二种选择)。

但似乎这两个人并没有很好地融合(我之前见过这个并向SO求助:ANTLR4 DefaultErrorStrategy fails to inject missing token;然后我找到了答案;现在我不知道了

让语法(如此简单,它匹配任意数量的" A",用空格分隔,以分号结尾):

grammar AAAGrammar;

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


aaaaa : 
    a* ';'
  ;

a :
    A
  ;

运行以下输入,生成的解析树为:

  • " A A A;":(aaaaa (a A) (a A) (a A) ;);
  • &#34; A A A&#34; :(aaaaa (a A) (a A) (a A) <missing ';'>)(此问题会在stderr上发出警告:第1:5行缺失&#39;;&#39; at&#39;&#39;)。

这就是我的期望,正是我想要的(第二个输入的缺失分号被正确注入)。

现在简单地改变语法,引入一个语义谓词(这个无关紧要,但我知道ANTLR4不会 - 也不应该 - 评估这个)在&#34; a&#34;规则,做到:

a :
    {true}? A
  ;

在相同的输入上再次运行它: - &#34; A A A;&#34;:(aaaaa (a A) (a A) (a A) ;); - &#34; A A A&#34; :(aaaaa (a A) (a A) (a A))(这个也会对stderr发出警告:第1:5行在输入中没有可行的选择&#39;&#39;)。

所以语义谓词完全搞砸了丢失的令牌注入。

这是预期的吗?

为什么吗

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

编辑(回复@CoronA评论)

这里是生成的解析器之间的diff -u(没有和带有语义谓词):

--- 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 {

我已经调试了两个代码。

假设输入&#34; A A A&#34; (没有分号),没有语义谓词的版本

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

此块3次,然后进入

            setState(10); match(SEMICOLON);

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,完全跳过match(SEMICOLON)

1 个答案:

答案 0 :(得分:1)

理解DefaultErrorStrategy采用天真的方法来识别解析异常的规则和来源。

特别是,在错误恢复例程范围内评估谓词是非常困难的,因为它不是作为DefaultErrorStrategy处理的一部分完成的。

考虑测试语法的这种变体:

aaaaa   : a* SEMI EOF           ;
a       : ( { true }? B )? A    ;
A   : 'A';
B   : 'B';
SEMI: ';';
WS  : ' '+ -> channel(HIDDEN) ;

在输入AAA上,打印的错误消息为

line 1:5 no viable alternative at input '<EOF>'
([] ([4] A) ([4] A) ([4] A))

即使谓词B是可选的,也没有简单的方法可以确保谓词无关紧要。并没有简单的方法来重新运行谓词以在错误恢复操作的上下文中评估其输出。这里唯一有效的运行时结论是错误不能被识别为只存在于一个规则(或子规则)中。

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

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

您可能会意识到,处理所有可能的错误表单作为解析的一部分可能会变得复杂。通常,解析器内的自动校正适用于错误是离散的,定义良好且易于识别的情况。否则,将它们视为语义错误,以便在分析阶段进行纠正。