Antlr4语义谓词与错误恢复混乱.为什么



使用语义谓词时,我在错误恢复时面临着某种奇怪的行为。

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

我还需要语义谓词,因为对于antlr4:匹配所有输入替代方案的东西一次(第二个替代方案)。

但似乎两个人都不好吗(我以前已经看过这个并寻求帮助:antlr4 defaulterrorstrategy没有注入失踪的令牌;然后我找到了答案;现在我没有)。

让语法(如此简单,它与任何数量的" A"匹配,用空格分开,由半柱终止):

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

aaaaa : 
    a* ';'
  ;
a :
    A
  ;

通过以下输入运行,由此产生的解析树是:

  • " a a;": (aaaaa (a A) (a A) (a A) ;);
  • " a a a": (aaaaa (a A) (a A) (a A) <missing ';'>)(这是在stderr上发出警告:1:5丢失';'at'')。

这就是我所期望的,正是我想要的(第二个输入的缺失半柱已正确注入)。

现在,对语法进行了简单的更改,引入了语义谓词(这是一个无害的语法,但我知道Antlr4在" A"规则中没有 - 不应该 - 不应该 - 评估这一点),以实现它:

a :
    {true}? A
  ;

通过相同的输入再次运行它: - " a a;": (aaaaa (a A) (a A) (a A) ;); - " a a a": (aaaaa (a A) (a A) (a A))(这也在stderr上发出警告:1:5在输入中没有可行的替代方案')。

因此,语义谓词完全混乱而缺少令牌注入。

这是预期的吗?

为什么?

是否有任何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 =
-       "3uacf5uee8cu4f5du8b0du4a45u78bdu1b2fu3378352142t243"+
-       "t33272bn2f216213132323233333324242217"+
-       "2t322241632226b5432763222b133222t732"+
-       "22tn3222nf322213t3222fr7522r332221617"+
-       "742217532223t";
+       "3uacf5uee8cu4f5du8b0du4a45u78bdu1b2fu3378352242t243"+
+       "t33272bn2f21621313232323333333324242"+
+       "2202t322241632226b5432763222b133222t"+
+       "73222tn3222nf322213t3222fr7522r3322"+
+       "2161763221720742220532223t";
    public static final ATN _ATN =
        ATNSimulator.deserialize(_serializedATN.toCharArray());
    static {

我已经调试了两个代码。

假设输入" a a"(无半柱),没有语义谓词的版本

            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)

了解,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是可选的,也没有简单的方法可以确保谓词无关紧要。并且没有简单的方法可以在错误恢复操作的上下文中重新运行谓词来评估其输出。然后,这里唯一有效的运行时结论是,仅在一个规则(或子规则)中无法将错误识别为现有的。

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

结合扩展Defaulterrorsrategy的结合,考虑扩展识别率感受以对确切的何处以及如何发生源形式进行更深入的了解 - 请注意该方法getExpectedTokens()。

您可能会欣赏,以解析的一部分来处理所有可能的错误形式可能会变得复杂。通常,解析器内的自动校正是适当的,而错误是离散,定义良好且易于识别的。否则,将它们视为在分析阶段要纠正的语义错误。

最新更新