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

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

나는 행동이 이제 ANTLR4에서 "고급"으로 간주되는 것을 보았습니다. 그래서 이상한 방식으로 일을 진행하고 있을지도 모르지만, 이렇게하기위한 "고급이 아닌"방법이 이런 식으로 무엇인지 살펴 보지 않았습니다. 우리의 필요에 잘 맞았습니다.

답변:


98

두 가지 기존 답변에 약간의 어려움이 있었기 때문에 결국 해결 방법을 공유하고 싶습니다.

우선 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가 후자를 잡아 내고 절대 자신의 코드에 도달하지 않을 것입니다.

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수행 하는지에 대한 자세한 내용 은 여기를 참조 하십시오 .)

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


3
나는 이것을 시도했고 그것이 잘 작동하는지 확인했습니다. 제안 된 3 가지 솔루션 중 가장 쉬운 방법이라고 생각합니다.
Kami

1
이것이 올바른 방법입니다. 가장 간단한 방법입니다. "문제"는 어휘 분석기에서 발생하며 구문 분석을 시도하기 전에 입력이 유효한지 여부가 중요한 경우 즉시보고하는 것이 좋습니다. ++
RubberDuck 2015

ThrowingErrorListener클래스를 Singleton으로 사용하는 특별한 이유가 있습니까?
RonyHe dec

@RonyHe 아니요, 이것은 Sam Harwells 코드 의 개작 일뿐입니다 .
Mouagip

이 솔루션은 한 가지주의 사항으로 저에게 도움이되었습니다. SLL을 사용하여 구문 분석 한 다음 LL로 폴백하려고하는데, 그렇게하면 폴백 구문 분석을 수행 할 때 오류가 발생하지 않는 것으로 나타났습니다. 해결 방법은 파서를 재설정하는 대신 두 번째 시도를 위해 완전히 새로운 파서를 구성하는 것이 었습니다. 파서를 재설정하면 중요한 상태를 재설정하지 못하는 것 같습니다.
Trejkaz

51

당신이 사용하는 경우 DefaultErrorStrategy또는을 BailErrorStrategyParserRuleContext.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);

많이 나는 문법이 아닌 SLL 렌더링 모호성을 식별하는 데 사용하는 오류 청취자의 더 복잡한 예는이다 SummarizingDiagnosticErrorListener의 클래스TestPerformance .


좋아 .. 그래도 어떻게 사용합니까? ((InputMismatchException) pce.getCause()).getCtx().exception유용한 오류 메시지를 얻는 것과 같은 것을 사용해야 합니까?
Brad Mace

1
오류 리스너에서 예외를 던지는 데 약간의 실험을했지만 예외가 나타나지 않는 것 같습니다. 나는 실패한 일치로 인해 문법 작업에서 NPE로 끝났습니다. 나는 현재에 대항하여 수영하고있는 것처럼 보이기 때문에 질문에 배경 이야기를 추가했습니다.
Brad Mace

에서 "line", "column"및 "message"를 반환하는 유틸리티 클래스를 작성해야합니다 RecognitionException. 원하는 정보는 이미 발생한 예외에서 사용할 수 있습니다.
Sam Harwell 2013-08-09

Gentle Reader, 당신이 저와 같다면 REPORT_SYNTAX_ERRORS가 무엇인지 궁금합니다. 답은 다음과 같습니다. stackoverflow.com/questions/18581880/handling-errors-in-antlr-4
james.garriss

이 예는 정말 유용합니다. 공식 문서 어딘가에 있어야한다고 생각 하는데 오류 처리를위한 페이지가 부족한 것 같습니다. 적어도 오류 리스너를 언급하는 것이 좋습니다.
geekley

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재정의하는 여섯 가지 방법이 있으면 더 나은 방법이 있다고 생각합니다.


C #에서 더 잘 작동하고, 수락되고 가장 많이 뽑힌 답변은 C #에서 컴파일 오류, 제네릭 인수 IToken 대 int의 일부 비 호환성
sarh

0

관심있는 사람을 위해 Sam Harwell의 답변에 해당하는 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);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.