ANTLR4 में हैंडलिंग त्रुटियाँ


83

जब पार्सर को पता नहीं होता है कि डिफ़ॉल्ट व्यवहार टर्मिनल पर संदेशों को प्रिंट करने के लिए क्या करना है:

लाइन 1:23 '' 'पर DECIMAL लापता

यह एक अच्छा संदेश है, लेकिन गलत जगह है। मैं इसे अपवाद के रूप में प्राप्त करना चाहता हूं।

मैं का उपयोग करने की कोशिश की है BailErrorStrategy, लेकिन यह ParseCancellationExceptionएक संदेश के बिना फेंकता है (एक के कारण)InputMismatchException , एक संदेश के बिना भी)।

क्या कोई ऐसा तरीका है जो मुझे संदेश में उपयोगी जानकारी को बनाए रखते हुए अपवादों के माध्यम से त्रुटियों की रिपोर्ट करने के लिए मिल सकता है?


यहाँ मैं वास्तव में क्या कर रहा हूँ - मैं आमतौर पर एक वस्तु बनाने के लिए नियमों में क्रियाओं का उपयोग करता हूँ:

dataspec returns [DataExtractor extractor]
    @init {
        DataExtractorBuilder builder = new DataExtractorBuilder(layout);
    }
    @after {
        $extractor = builder.create();
    }
    : first=expr { builder.addAll($first.values); } (COMMA next=expr { builder.addAll($next.values); })* EOF
    ;

expr returns [List<ValueExtractor> values]
    : a=atom { $values = Arrays.asList($a.val); }
    | fields=fieldrange { $values = values($fields.fields); }
    | '%' { $values = null; }
    | ASTERISK { $values = values(layout); }
    ;

जब मैं पार्सर का आह्वान करता हूं तो मैं ऐसा कुछ करता हूं:

public static DataExtractor create(String dataspec) {
    CharStream stream = new ANTLRInputStream(dataspec);
    DataSpecificationLexer lexer = new DataSpecificationLexer(stream);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    DataSpecificationParser parser = new DataSpecificationParser(tokens);

    return parser.dataspec().extractor;
}

मैं वास्तव में चाहता हूँ

  • dataspec()जब इनपुट को पार्स नहीं किया जा सकता है तो कॉल को अपवाद (आदर्श रूप से एक चेक किया गया) को फेंकने के लिए
  • उस अपवाद के लिए एक उपयोगी संदेश है और लाइन नंबर और स्थिति तक पहुंच प्रदान करें जहां समस्या पाई गई थी

फिर मैं उस अपवाद को कॉलस्टैक तक पहुंचा दूंगा जहां से उपयोगकर्ता के लिए एक उपयोगी संदेश प्रस्तुत करना सबसे उपयुक्त होगा - उसी तरह मैं एक गिरा हुआ नेटवर्क कनेक्शन संभालूंगा, एक भ्रष्ट फ़ाइल पढ़ना, आदि।

मैंने देखा कि एएनटीएलआर 4 में अब क्रियाओं को "उन्नत" माना जाता है, इसलिए शायद मैं अजीब तरीके से चीजों के बारे में जा रहा हूं, लेकिन मैंने इस पर ध्यान नहीं दिया कि ऐसा करने के लिए "गैर-उन्नत" तरीका क्या होगा? हमारी जरूरतों के लिए अच्छा काम कर रहा है।

जवाबों:


98

चूंकि मेरे पास दो मौजूदा उत्तरों के साथ थोड़ा संघर्ष है, इसलिए मैं उस समाधान को साझा करना चाहता हूं जिसे मैंने समाप्त किया है।

सबसे पहले मैं एक ErrorListener की मेरी अपना संस्करण बनाया तरह सैम हार्वेल सुझाव:

public class ThrowingErrorListener extends BaseErrorListener {

   public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener();

   @Override
   public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e)
      throws ParseCancellationException {
         throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg);
      }
}

ParseCancellationExceptionबजाए RecognitionExceptionDefaultErrorStrategy के बाद के उपयोग को नोट करें, यह बाद को पकड़ लेगा और यह आपके अपने कोड तक कभी नहीं पहुंचेगा।

ब्रैड गदा की तरह एक पूरी नई ErrorStrategy बनाना आवश्यक नहीं है क्योंकि डिफ़ॉल्ट रूप से बहुत अच्छे त्रुटि संदेश उत्पन्न करता है।

फिर मैं अपने पार्सिंग फ़ंक्शन में कस्टम ErrorListener का उपयोग करता हूं:

public static String parse(String text) throws ParseCancellationException {
   MyLexer lexer = new MyLexer(new ANTLRInputStream(text));
   lexer.removeErrorListeners();
   lexer.addErrorListener(ThrowingErrorListener.INSTANCE);

   CommonTokenStream tokens = new CommonTokenStream(lexer);

   MyParser parser = new MyParser(tokens);
   parser.removeErrorListeners();
   parser.addErrorListener(ThrowingErrorListener.INSTANCE);

   ParserRuleContext tree = parser.expr();
   MyParseRules extractor = new MyParseRules();

   return extractor.visit(tree);
}

(क्या MyParseRulesकरता है, इस बारे में अधिक जानकारी के लिए , यहां देखें ।)

यह आपको वही त्रुटि संदेश देगा जो डिफ़ॉल्ट रूप से कंसोल पर मुद्रित होगा, केवल उचित अपवादों के रूप में।


3
मैंने यह कोशिश की और मैं पुष्टि करता हूं कि इसने अच्छा काम किया। मुझे लगता है कि यह 3 प्रस्तावित समाधानों में सबसे आसान है।
कामी

1
यह जाने का सही तरीका है। जाने का सबसे सरल तरीका। लेसर में "समस्या" होती है और यह सही तब रिपोर्ट करने के लिए समझ में आता है और अगर यह महत्वपूर्ण है कि इनपुट पार्स करने का प्रयास करने से पहले मान्य हो। ++
रबरडक

क्या ThrowingErrorListenerसिंग्लटन के रूप में कक्षा का उपयोग करने का कोई विशेष कारण है ?
रॉनीहे

@RonyHe नहीं, यह सैम हारवेल्स कोड का सिर्फ एक रूपांतरण है ।
मौगिप

इस समाधान ने मेरे लिए एक चेतावनी के साथ काम किया - हम SLL का उपयोग करके पार्स करने की कोशिश कर रहे हैं और फिर वापस LL पर गिर रहे हैं, और यह पता चला है कि ऐसा करने से फ़ॉलबैक पार्सिंग करते समय कोई त्रुटि नहीं होती है। वर्कअराउंड पार्सर को रीसेट करने के बजाय दूसरे प्रयास के लिए एक नए पार्सर का निर्माण करना था - स्पष्ट रूप से पार्सर को रीसेट करना कुछ महत्वपूर्ण स्थिति को रीसेट करने में विफल रहता है।
तर्जका

51

जब आप DefaultErrorStrategyया का उपयोग करते हैं BailErrorStrategy, तो ParserRuleContext.exceptionफ़ील्ड परिणामी पार्स ट्री में किसी भी पार्स ट्री नोड के लिए सेट की जाती है जहां एक त्रुटि हुई। इस फ़ील्ड के लिए दस्तावेज़ पढ़ता है (ऐसे लोगों के लिए जो अतिरिक्त लिंक पर क्लिक नहीं करना चाहते हैं):

अपवाद जिसने इस नियम को वापस लौटने के लिए मजबूर किया। यदि नियम सफलतापूर्वक पूरा हुआ, तो यह है null

संपादित करें: यदि आप उपयोग करते हैं DefaultErrorStrategy, तो पार्स संदर्भ अपवाद कॉलिंग कोड के लिए सभी तरह से प्रचारित नहीं किया जाएगा, इसलिए आप exceptionसीधे क्षेत्र की जांच कर पाएंगे । यदि आप उपयोग करते हैं BailErrorStrategy, तो ParseCancellationExceptionइसके द्वारा फेंके जाने पर RecognitionExceptionआप कॉल करेंगे getCause()

if (pce.getCause() instanceof RecognitionException) {
    RecognitionException re = (RecognitionException)pce.getCause();
    ParserRuleContext context = (ParserRuleContext)re.getCtx();
}

संपादित करें 2: आपके अन्य उत्तर के आधार पर, यह प्रतीत होता है कि आप वास्तव में अपवाद नहीं चाहते हैं, लेकिन जो आप चाहते हैं वह त्रुटियों की रिपोर्ट करने का एक अलग तरीका है। उस स्थिति में, आप ANTLRErrorListenerइंटरफ़ेस में अधिक रुचि लेंगे । आप parser.removeErrorListeners()कंसोल पर लिखने वाले डिफ़ॉल्ट श्रोता को हटाने के लिए कॉल करना चाहते हैं, और फिर parser.addErrorListener(listener)अपने विशेष श्रोता के लिए कॉल करें । मैं अक्सर निम्न श्रोता का उपयोग प्रारंभिक बिंदु के रूप में करता हूं, क्योंकि इसमें संदेशों के साथ स्रोत फ़ाइल का नाम शामिल है।

public class DescriptiveErrorListener extends BaseErrorListener {
    public static DescriptiveErrorListener INSTANCE = new DescriptiveErrorListener();

    @Override
    public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,
                            int line, int charPositionInLine,
                            String msg, RecognitionException e)
    {
        if (!REPORT_SYNTAX_ERRORS) {
            return;
        }

        String sourceName = recognizer.getInputStream().getSourceName();
        if (!sourceName.isEmpty()) {
            sourceName = String.format("%s:%d:%d: ", sourceName, line, charPositionInLine);
        }

        System.err.println(sourceName+"line "+line+":"+charPositionInLine+" "+msg);
    }
}

उपलब्ध इस वर्ग के साथ, आप इसका उपयोग करने के लिए निम्नलिखित का उपयोग कर सकते हैं।

lexer.removeErrorListeners();
lexer.addErrorListener(DescriptiveErrorListener.INSTANCE);
parser.removeErrorListeners();
parser.addErrorListener(DescriptiveErrorListener.INSTANCE);

त्रुटि श्रोता का एक और अधिक जटिल उदाहरण जो मैं अस्पष्टताओं की पहचान करने के लिए उपयोग करता हूं जो एक व्याकरण गैर-एसएलएल को प्रस्तुत करता है वह SummarizingDiagnosticErrorListenerकक्षा हैTestPerformance


ठीक है ... हालांकि मैं उसका उपयोग कैसे करूँ? क्या मुझे ((InputMismatchException) pce.getCause()).getCtx().exceptionउपयोगी त्रुटि संदेश प्राप्त करने के लिए कुछ उपयोग करना चाहिए ?
ब्रैड मेस

1
मैंने त्रुटि श्रोता से अपवाद को फेंकने के साथ थोड़ा प्रयोग किया, लेकिन अपवाद कभी नहीं दिखा। मैं मैच के असफल होने के कारण व्याकरण की क्रियाओं से NPEs के साथ समाप्त हो गया। मैंने कुछ बैकस्टोरी को इस प्रश्न के साथ जोड़ा है क्योंकि यह प्रतीत होता है कि मैं वर्तमान के खिलाफ तैर सकता हूं।
ब्रैड मेस

आपको बस "लाइन", "कॉलम", और "संदेश" को वापस करने के लिए एक उपयोगिता वर्ग लिखना चाहिए RecognitionException। जो जानकारी आप चाहते हैं वह उस अपवाद में उपलब्ध है जो पहले से ही फेंक दी गई है।
सैम हैरवेल

कोमल पाठक, यदि आप मेरी तरह हैं, तो आप सोच रहे हैं कि REPORT_SYNTAX_ERRORS आखिर क्या है। यहाँ उत्तर है: stackoverflow.com/questions/18581880/handling-errors-in-antlr-4
james.garriss

यह उदाहरण वास्तव में उपयोगी है। मुझे लगता है कि यह आधिकारिक दस्तावेज में कहीं होना चाहिए , यह त्रुटि से निपटने के लिए एक पृष्ठ की कमी लगती है। कम से कम त्रुटि श्रोताओं का उल्लेख करना अच्छा होगा।
गीकले

10

अब तक मैं जो कुछ भी ले आया हूं, DefaultErrorStrategyवह विस्तार करने और इसे लागू करने के reportXXXतरीकों पर आधारित है (हालांकि यह पूरी तरह से संभव है कि मैं चीजों को आवश्यकता से अधिक जटिल बना रहा हूं:)

public class ExceptionErrorStrategy extends DefaultErrorStrategy {

    @Override
    public void recover(Parser recognizer, RecognitionException e) {
        throw e;
    }

    @Override
    public void reportInputMismatch(Parser recognizer, InputMismatchException e) throws RecognitionException {
        String msg = "mismatched input " + getTokenErrorDisplay(e.getOffendingToken());
        msg += " expecting one of "+e.getExpectedTokens().toString(recognizer.getTokenNames());
        RecognitionException ex = new RecognitionException(msg, recognizer, recognizer.getInputStream(), recognizer.getContext());
        ex.initCause(e);
        throw ex;
    }

    @Override
    public void reportMissingToken(Parser recognizer) {
        beginErrorCondition(recognizer);
        Token t = recognizer.getCurrentToken();
        IntervalSet expecting = getExpectedTokens(recognizer);
        String msg = "missing "+expecting.toString(recognizer.getTokenNames()) + " at " + getTokenErrorDisplay(t);
        throw new RecognitionException(msg, recognizer, recognizer.getInputStream(), recognizer.getContext());
    }
}

यह उपयोगी संदेश के साथ अपवाद फेंकता है, और लाइन और समस्या की स्थिति या तो से मिल जा सकता है offendingटोकन, या यदि वह स्थापित नहीं किया गया, से currentका उपयोग करके टोकन ((Parser) re.getRecognizer()).getCurrentToken()पर RecognitionException

मैं काफी खुश हूं कि यह कैसे काम कर रहा है, हालांकि reportXओवरराइड करने के छह तरीके होने से मुझे लगता है कि एक बेहतर तरीका है।


सी # के लिए बेहतर काम करता है, स्वीकार किए जाते हैं और शीर्ष-मतदान जवाब में सी # में संकलन त्रुटियां थीं, जेनेरिक तर्क के कुछ असंगतता IToken बनाम int
sarh

0

रुचि रखने वाले किसी व्यक्ति के लिए, यहाँ ANTLR4 C # सैम हैरवेल के उत्तर के बराबर है:

using System; using System.IO; using Antlr4.Runtime;
public class DescriptiveErrorListener : BaseErrorListener, IAntlrErrorListener<int>
{
  public static DescriptiveErrorListener Instance { get; } = new DescriptiveErrorListener();
  public void SyntaxError(TextWriter output, IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) {
    if (!REPORT_SYNTAX_ERRORS) return;
    string sourceName = recognizer.InputStream.SourceName;
    // never ""; might be "<unknown>" == IntStreamConstants.UnknownSourceName
    sourceName = $"{sourceName}:{line}:{charPositionInLine}";
    Console.Error.WriteLine($"{sourceName}: line {line}:{charPositionInLine} {msg}");
  }
  public override void SyntaxError(TextWriter output, IRecognizer recognizer, Token offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e) {
    this.SyntaxError(output, recognizer, 0, line, charPositionInLine, msg, e);
  }
  static readonly bool REPORT_SYNTAX_ERRORS = true;
}
lexer.RemoveErrorListeners();
lexer.AddErrorListener(DescriptiveErrorListener.Instance);
parser.RemoveErrorListeners();
parser.AddErrorListener(DescriptiveErrorListener.Instance);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.