2015-05-04 62 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()。

您可能會明白,處理所有可能的錯誤形式作爲解析的一部分可能會變得複雜。通常,解析器中的自動糾正適用於錯誤是離散的,定義明確且易於識別的情況。否則,將它們作爲語義錯誤在分析階段進行修正。