90

パーサーが何をすべきかわからない場合のデフォルトの動作は、次のようなメッセージを端末に出力することです。

1:23 行目の '}' に DECIMAL がありません

これは良いメッセージですが、場所が間違っています。これは例外として受け取りたいと思います。

を使用してみましたBailErrorStrategyが、これはメッセージなしで a をスローしParseCancellationExceptionます ( a が原因で、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で「高度」と見なされるようになったので、奇妙な方法で物事に取り組んでいる可能性がありますが、これを行うための「高度ではない」方法が何であるかを調べていません私たちのニーズに対してうまく機能しています。

4

5 に答える 5

105

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

ParseCancellationExceptionDefaultErrorStrategy は a の代わりに a を使用することに注意してくださいRecognitionException。これは、DefaultErrorStrategy が後者をキャッチし、独自のコードに到達しないためです。

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は、こちらを参照してください。)

これにより、デフォルトでコンソールに出力されるのと同じエラー メッセージが、適切な例外の形式でのみ表示されます。

于 2014-10-26T13:00:30.337 に答える
51

DefaultErrorStrategyまたはを使用すると、結果の解析ツリーでエラーが発生した任意の解析ツリー ノードにフィールドが設定されますBailErrorStrategyParserRuleContext.exceptionこのフィールドのドキュメントには次のように書かれています (余分なリンクをクリックしたくない人向け):

このルールを強制的に戻す例外。ルールが正常に完了した場合、これはnullです。

編集:を使用すると、解析コンテキストの例外は呼び出し元のコードにまで伝播されないため、フィールドを直接DefaultErrorStrategy調べることができます。exceptionを使用する場合BailErrorStrategyParseCancellationExceptionそれによってスローされるには、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

于 2013-08-08T22:31:45.047 に答える
1

興味のある方は、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);
于 2020-12-10T20:58:33.010 に答える