2015-05-04 46 views
1

使用语义谓词时,我在错误恢复中遇到了一些奇怪的行为。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 
    ; 

正在运行,以下输入,所得到的分析树是:

  • “AAA;”:(aaaaa (a A) (a A) (a A) ;);
  • “A A A”:(aaaaa (a A) (a A) (a A) <missing ';'>)(这个人在stderr上发出警告:第1行:5''''在'')。

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

现在变的简单语法,引入语义谓词(这一个无关痛痒,但我明白,ANTLR4不 - 不应该 - 评估这一点)在“A”的规则,以使其:

a : 
    {true}? A 
    ; 

通过相同的输入再次运行它: - “AAA;”:(aaaaa (a A) (a A) (a A) ;); - “A A A”:(aaaaa (a A) (a A) (a A))(这个也在stderr:line 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 = 
-  "\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 { 

我已经调试这两个代码。

假设输入 “AAA”(不分号),没有语义谓词版本变为

  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)(向前看)并切换到更高级的预测,其中_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)

+0

我认为恢复被调用,但不考虑令牌注入,因为谓词评估不能被恢复模拟。你有调试过吗?在两种情况下它都进入恢复状态吗?如果是这样,你可以重写你的需求恢复。 – CoronA

+0

@CoronA,我已经编辑了答案,以提供一些有关调试揭示的信息。问题在于'match(SEMICOLON)'没有得到执行的机会,因为'adaptativePredict()'在没有可行的替代方案时抛出异常。我认为口译员正在等待“A”或“;”决定走哪条路;但更简单的前瞻(1)策略不会扼杀失踪的';'并让它的match()正常进行(从而注入缺少的标记)。 – rslemos

+0

我刚刚看到提交[ea4676b18ab40c99b629e078f1addd6605988403](https://github.com/antlr/antlr4/commit/ea4676b18ab40c99b629e078f1addd6605988403),这可能已经解决了这个问题。我会用4.5测试,稍后再回来告诉。 – rslemos

回答

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()。

您可能会明白,处理所有可能的错误形式作为解析的一部分可能会变得复杂。通常,解析器中的自动纠正适用于错误是离散的,定义明确且易于识别的情况。否则,将它们作为语义错误在分析阶段进行修正。