パーサーが何をすべきかわからない場合のデフォルトの動作は、次のようなメッセージを端末に出力することです。
行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ではアクションが「高度」と見なされるようになったので、奇妙な方法で物事を進めているかもしれませんが、この方法から「高度でない」方法がどのようになるかについては検討していません。私たちのニーズにうまく対応しています。
私は既存の2つの答えに少し苦労したので、最終的に解決策を共有したいと思います。
まず、 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);
}
}
DefaultErrorStrategyは後者をキャッチし、独自のコードには到達しないため、ParseCancellationException
の代わりにRecognitionException
を使用することに注意してください。
DefaultErrorStrategyはデフォルトでかなり良いエラーメッセージを生成するため、 Brad Mace のようなまったく新しい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
の機能の詳細については、 here を参照してください。)
これにより、適切な例外の形式でのみ、デフォルトでコンソールに出力されるのと同じエラーメッセージが表示されます。
DefaultErrorStrategy
または BailErrorStrategy
を使用すると、 _ParserRuleContext.exception
_ フィールドが設定されますエラーが発生した解析ツリー内の解析ツリーノード。このフィールドのドキュメントは次のとおりです(追加のリンクをクリックしたくない人向け)。
このルールを強制的に返す例外。ルールが正常に完了した場合、これは
null
です。
Edit:DefaultErrorStrategy
を使用すると、解析コンテキスト例外は呼び出し元のコードにまで伝播されないため、 exception
フィールドを直接調べることができます。 BailErrorStrategy
を使用する場合、getCause()
を呼び出すと、それによってスローされるParseCancellationException
にはRecognitionException
が含まれます。
_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);
_
much文法を非表示にするあいまいさを識別するために使用するエラーリスナーのより複雑な例-SLLは SummarizingDiagnosticErrorListener
のクラス[TestPerformance
)です。
これまでに思いついたのは、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
RecognitionException
で((Parser) re.getRecognizer()).getCurrentToken()
を使用したトークン。
6つのreportX
メソッドをオーバーライドすることで、より良い方法があると思うようになりますが、これがどのように機能するかにかなり満足しています。