Есть ли способ легко адаптировать сообщения об ошибках ANTLR4? - PullRequest
2 голосов
/ 19 сентября 2019

Курс валют Я работаю над своей собственной грамматикой, и я хотел бы, чтобы у меня были конкретные сообщения об ошибках на NoViableAlternative, InputMismatch, UnwantedToken, MissingToken и LexerNoViableAltException.

Я ужерасширен Lexer.class и переопределил notifyListeners, чтобы изменить сообщение об ошибке по умолчанию token recognition error at: на мое собственное.Также я расширил DefaultErrorStrategy и переопределил все методы отчета, такие как reportNoViableAlternative, reportInputMismatch, reportUnwantedToken, reportMissingToken.

Цель всего, что заключается в изменении сообщений, которыебудет передан методу syntaxError() слушателя ANTLRErrorListener.

Вот небольшой пример расширенного Lexer.class:

    @Override
    public void notifyListeners(LexerNoViableAltException lexerNoViableAltException) {
        String text = this._input.getText(Interval.of(this._tokenStartCharIndex, this._input.index()));
        String msg = "Operator " + this.getErrorDisplay(text) + " is unkown.";
        ANTLRErrorListener listener = this.getErrorListenerDispatch();
        listener.syntaxError(this, null, this._tokenStartLine, this._tokenStartCharPositionInLine, msg,
            lexerNoViableAltException);
    }

Или для DefaultErrorStrategy:

    @Override
    protected void reportNoViableAlternative(Parser recognizer, NoViableAltException noViableAltException) {
        TokenStream tokens = recognizer.getInputStream();
        String input;
        if (tokens != null) {
            if (noViableAltException.getStartToken().getType() == -1) {
                input = "<EOF>";
            } else {
                input = tokens.getText(noViableAltException.getStartToken(), noViableAltException.getOffendingToken());
            }
        } else {
            input = "<unknown input>";
        }

        String msg = "Invalid operation " + input + ".";
        recognizer.notifyErrorListeners(noViableAltException.getOffendingToken(), msg, noViableAltException);
    }

Итак, я прочитал эту ветку Обработка ошибок в ANTLR4 , и мне было интересно, нет ли более простого решения, когда дело доходит до настройки?

Ответы [ 2 ]

3 голосов
/ 20 сентября 2019

Моя стратегия улучшения сообщений об ошибках ANTLR4 немного отличается.Я использую переопределение syntaxError в моих прослушивателях ошибок (у меня есть как для лексера, так и для анализатора).Используя заданные значения и некоторые другие вещи, такие как LL1Analyzer, вы можете создавать довольно точные сообщения об ошибках.Обработчик ошибок lexer работает довольно просто (надеюсь, код C ++ вам понятен):

void LexerErrorListener::syntaxError(Recognizer *recognizer, Token *, size_t line,
                                     size_t charPositionInLine, const std::string &, std::exception_ptr ep) {
  // The passed in string is the ANTLR generated error message which we want to improve here.
  // The token reference is always null in a lexer error.
  std::string message;
  try {
    std::rethrow_exception(ep);
  } catch (LexerNoViableAltException &) {
    Lexer *lexer = dynamic_cast<Lexer *>(recognizer);
    CharStream *input = lexer->getInputStream();
    std::string text = lexer->getErrorDisplay(input->getText(misc::Interval(lexer->tokenStartCharIndex, input->index())));
    if (text.empty())
      text = " "; // Should never happen.

    switch (text[0]) {
      case '/':
        message = "Unfinished multiline comment";
        break;
      case '"':
        message = "Unfinished double quoted string literal";
        break;
      case '\'':
        message = "Unfinished single quoted string literal";
        break;
      case '`':
        message = "Unfinished back tick quoted string literal";
        break;

      default:
        // Hex or bin string?
        if (text.size() > 1 && text[1] == '\'' && (text[0] == 'x' || text[0] == 'b')) {
          message = std::string("Unfinished ") + (text[0] == 'x' ? "hex" : "binary") + " string literal";
          break;
        }

        // Something else the lexer couldn't make sense of (likely there is no rule that accepts this input).
        message = "\"" + text + "\" is no valid input at all";
        break;
    }
    owner->addError(message, 0, lexer->tokenStartCharIndex, line, charPositionInLine,
                    input->index() - lexer->tokenStartCharIndex);
  }
}

Этот код показывает, что мы вообще не используем исходное сообщениеи вместо этого изучите текст токена, чтобы увидеть, что не так.Здесь мы в основном имеем дело с незамкнутыми строками:

enter image description here

Слушатель ошибок синтаксического анализатора намного сложнее и слишком велик для размещения здесь.Это комбинация различных источников для создания фактического сообщения об ошибке:

  • Parser.getExpectedTokens(): использует LL1Analyzer для получения следующих возможных токенов лексера из заданной позиции в ATN (так называемое следующее-установлен).Тем не менее, он просматривает предикаты, что может быть проблемой (если вы их используете).

  • Идентификаторы и ключевые слова: часто определенные ключевые слова допускаются в качестве обычных идентификаторов в определенных ситуациях, что создает следующиенаборы со списком ключевых слов, которые фактически должны быть идентификаторами, поэтому требуется дополнительная проверка, чтобы избежать их отображения в виде ожидаемых значений:

enter image description here

  • Стек вызовов правила синтаксического анализатора, во время вызова слушателя ошибок анализатор имеет текущий контекст правила синтаксического анализатора (Parser.getRuleContext()), который можно использовать для перехода по стеку вызовов и для поиска правила.контексты, которые дают вам более конкретную информацию о местонахождении ошибки (например, переход от соответствия * к гипотетическому правилу expr говорит о том, что на самом деле ожидается выражение в этой точке).

  • Данное исключение: если это значение равно NULL, ошибка связана с отсутствующим или нежелательным одиночным токеном, который довольно легко обрабатывается.Если исключение имеет значение, вы можете проверить его для получения более подробной информации.Здесь стоит упомянуть, что содержимое исключения не используется (и в любом случае довольно редко), вместо этого мы используем значения, которые были собраны ранее.Наиболее распространенными типами исключений являются NoViableAlt и InputMismatch, которые можно перевести как «вход не завершен», когда ошибочная позиция равна EOF, или как «ввод недопустим в этой позиции».И то, и другое может быть улучшено ожиданием, построенным из стека вызовов правил и / или последующего набора, как упомянуто (и показано на рисунке) выше.

0 голосов
/ 26 сентября 2019

После некоторых исследований я нашел другое решение.В книге «Полное руководство по ANTLR4» в главе 9.4 они объясняют, как использовать альтернативы ошибок:

fcall
: ID '(' expr ')'
| ID '(' expr ')' ')' {notifyErrorListeners("Too many parentheses");}
| ID '(' expr         {notifyErrorListeners("Missing closing ')'");}
;

Эти альтернативы ошибок могут заставить синтаксический анализатор, созданный ANTLR, работать немного сложнее, выбирая между альтернативами, но они никоим образом не путают парсер.

Я адаптировал это к своей грамматике и расширил BaseErrorListener.Переданное исключение для notifyErrorListener равно нулю (от Parser.class):

public final void notifyErrorListeners(String msg) {
    this.notifyErrorListeners(this.getCurrentToken(), msg, (RecognitionException)null);
}

Так обработано это в расширении BaseErrorListener, например:

if (recognitionException instanceof LexerNoViableAltException) {
    message = handleLexerNoViableAltException((Lexer) recognizer);
} else if (recognitionException instanceof InputMismatchException) {
    message = handleInputMismatchException((CommonToken) offendingSymbol);
} else if (recognitionException instanceof NoViableAltException) {
    message = handleNoViableAltException((CommonToken) offendingSymbol);
} else if (Objects.isNull(recognitionException)) {
// Handle Errors specified in my grammar
    message = msg;
} else {
    message = "Can't be resolved";
}

Iнадеюсь, что это поможет немного

...