Programing

ANTLR4에서 오류 처리

crosscheck 2020. 11. 11. 08:00
반응형

ANTLR4에서 오류 처리


파서가 무엇을 해야할지 모를 때의 기본 동작은 다음과 같이 터미널에 메시지를 인쇄하는 것입니다.

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()입력을 해석 할 수없는 경우는 호출 예외 (이상적으로는 하나의 체크) 던져
  • 해당 예외에 유용한 메시지가 있고 문제가 발견 된 줄 번호 및 위치에 대한 액세스를 제공하기 위해

그런 다음 해당 예외가 사용자에게 유용한 메시지를 제공하는 데 가장 적합한 곳으로 호출 스택을 버블 링하도록 할 것입니다. 네트워크 연결이 끊어진 경우, 손상된 파일을 읽는 것과 같은 방식입니다.

나는 행동이 이제 ANTLR4에서 "고급"으로 간주되는 것을 보았습니다. 그래서 나는 이상한 방식으로 일을 진행할 것입니다. 그러나 이것을 수행하는 "고급이 아닌"방법이 이런 식으로 무엇인지 살펴 보지 않았습니다. 우리의 필요에 잘 맞았습니다.


두 가지 기존 답변에 약간의 어려움을 겪었으므로 결국 해결 방법을 공유하고 싶습니다.

우선 Sam Harwell이 제안한 것과 같은 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);
      }
}

a ParseCancellationException대신 a 사용 RecognitionException하면 DefaultErrorStrategy가 후자를 포착하고 자신의 코드에 도달하지 않기 때문에 a 사용 합니다.

Brad Mace가 제안한 것과 같은 완전히 새로운 ErrorStrategy를 만드는 것은 DefaultErrorStrategy가 기본적으로 꽤 좋은 오류 메시지를 생성하기 때문에 필요하지 않습니다.

그런 다음 내 구문 분석 기능에서 사용자 정의 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여기를 참조 하십시오 .)

이렇게하면 기본적으로 콘솔에 인쇄되는 것과 동일한 오류 메시지가 적절한 예외 형식으로 만 제공됩니다.


When you use the DefaultErrorStrategy or the BailErrorStrategy, the ParserRuleContext.exception field is set for any parse tree node in the resulting parse tree where an error occurred. The documentation for this field reads (for people that don't want to click an extra link):

The exception which forced this rule to return. If the rule successfully completed, this is null.

Edit: If you use DefaultErrorStrategy, the parse context exception will not be propagated all the way out to the calling code, so you'll be able to examine the exception field directly. If you use BailErrorStrategy, the ParseCancellationException thrown by it will include a RecognitionException if you call getCause().

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

Edit 2: Based on your other answer, it appears that you don't actually want an exception, but what you want is a different way to report the errors. In that case, you'll be more interested in the ANTLRErrorListener interface. You want to call parser.removeErrorListeners() to remove the default listener that writes to the console, and then call parser.addErrorListener(listener) for your own special listener. I often use the following listener as a starting point, as it includes the name of the source file with the messages.

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);
    }
}

With this class available, you can use the following to use it.

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

A much more complicated example of an error listener that I use to identify ambiguities which render a grammar non-SLL is the SummarizingDiagnosticErrorListener class in TestPerformance.


What I've come up with so far is based on extending DefaultErrorStrategy and overriding it's reportXXX methods (though it's entirely possible I'm making things more complicated than necessary):

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());
    }
}

This throws exceptions with useful messages, and the line and position of the problem can be gotten from either the offending token, or if that's not set, from the current token by using ((Parser) re.getRecognizer()).getCurrentToken() on the RecognitionException.

I'm fairly happy with how this is working, though having six reportX methods to override makes me think there's a better way.

참고URL : https://stackoverflow.com/questions/18132078/handling-errors-in-antlr4

반응형