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
'Programing' 카테고리의 다른 글
CSS 목록 스타일 이미지 크기 (0) | 2020.11.11 |
---|---|
API 레벨은 무엇을 의미합니까? (0) | 2020.11.11 |
ITMS-90535 최신 Google 로그인 SDK로 iOS 앱을 게시 할 수 없습니다. (0) | 2020.11.11 |
git 병합을 수행 할 때 특정 커밋을 제외 할 수 있습니까? (0) | 2020.11.10 |
어레이의 중복 여부를 어떻게 확인합니까? (0) | 2020.11.10 |