Antlr4 сбрасывает оставшиеся токены вместо спасения

Я использую Antlr4, и вот упрощенная грамматика, которую я написал:

grammar BooleanExpression;

/*******************************
 *      Parser Rules
 *******************************/
booleanTerm
    : booleanLiteral (KW_OR booleanLiteral)+
    | booleanLiteral
    ;

id
    : IDENTIFIER
    ;

booleanLiteral
    : KW_TRUE
    | KW_FALSE
    ;

/*******************************
 *         Lexer Rules
 *******************************/
KW_TRUE
    : 'true'
    ;

KW_FALSE
    : 'false'
    ;

KW_OR
    : 'or'
    ;   

IDENTIFIER
    : (SIMPLE_LATIN)+
    ;

fragment 
SIMPLE_LATIN
    : 'A' .. 'Z'
    | 'a' .. 'z'
    ;

WHITESPACE
    : [ \t\n\r]+ -> skip
    ;

Я использовал BailErrorStategy и BailLexer, как показано ниже:

public class BailErrorStrategy extends DefaultErrorStrategy {
    /**
     * Instead of recovering from exception e, rethrow it wrapped in a generic
     * IllegalArgumentException so it is not caught by the rule function catches.
     * Exception e is the "cause" of the IllegalArgumentException.
     */

    @Override
    public void recover(Parser recognizer, RecognitionException e) {
        throw new IllegalArgumentException(e);
    }

    /**
     * Make sure we don't attempt to recover inline; if the parser successfully
     * recovers, it won't throw an exception.
     */
    @Override
    public Token recoverInline(Parser recognizer) throws RecognitionException {
        throw new IllegalArgumentException(new InputMismatchException(recognizer));
    }

    /** Make sure we don't attempt to recover from problems in subrules. */
    @Override
    public void sync(Parser recognizer) {
    }

    @Override
    protected Token getMissingSymbol(Parser recognizer) {
        throw new IllegalArgumentException(new InputMismatchException(recognizer));
    }
}



 public class BailLexer extends BooleanExpressionLexer {
    public BailLexer(CharStream input) {
        super(input);
        //removeErrorListeners();
        //addErrorListener(new ConsoleErrorListener());
    }

    @Override
    public void recover(LexerNoViableAltException e) {
        throw new IllegalArgumentException(e); // Bail out
    }

    @Override
    public void recover(RecognitionException re) {
        throw new IllegalArgumentException(re); // Bail out
    }
}

Все работает нормально, кроме одного случая. Я попробовал следующее выражение:

true OR false

Я ожидаю, что это выражение будет отклонено, и будет выдано исключение IllegalArgumentException, потому что токен «или» должен быть в нижнем регистре, а не в верхнем. Но оказалось, что Antlr4 не отклонил это выражение, и выражение токенизировано в «KW_TRUE IDENTIFIER KW_FALSE» (что и ожидалось, верхний регистр «ИЛИ» будет рассматриваться как IDENTIFIER), но синтаксический анализатор не выдал ошибку во время обрабатывает этот поток токенов и анализирует его в дерево, содержащее только «true», и отбрасывает оставшиеся токены «IDENTIFIER KW_FALSE». Я пробовал разные режимы прогнозирования, но все они работали, как указано выше. Я понятия не имею, почему это работает так, и провел некоторую отладку, и в итоге это привело к этому фрагменту кода в Antlr:

ATNConfigSet reach = computeReachSet(previous, t, false);

if ( reach==null ) {
    // if any configs in previous dipped into outer context, that
    // means that input up to t actually finished entry rule
    // at least for SLL decision. Full LL doesn't dip into outer
    // so don't need special case.
    // We will get an error no matter what so delay until after
    // decision; better error message. Also, no reachable target
    // ATN states in SLL implies LL will also get nowhere.
    // If conflict in states that dip out, choose min since we
    // will get error no matter what.
    int alt = getAltThatFinishedDecisionEntryRule(previousD.configs);
    if ( alt!=ATN.INVALID_ALT_NUMBER ) {
        // return w/o altering DFA
        return alt;
    }
    throw noViableAlt(input, outerContext, previous, startIndex);
}  

Код "int alt = getAltThatFinishedDecisionEntryRule(previousD.configs);" вернул вторую альтернативу в booleanTerm (поскольку «true» соответствует второй альтернативе «booleanLiteral»), но, поскольку она не равна ATN.INVALID_ALT_NUMBER, noViableAlt не выбрасывается немедленно. Согласно комментариям к Java: «Мы получим ошибку, несмотря ни на что, поэтому отложите до принятия решения», но, похоже, в конечном итоге ошибка не возникла.

Я действительно понятия не имею, как заставить Antlr сообщать об ошибке в этом случае, может ли кто-нибудь пролить на это свет? Любая помощь приветствуется, спасибо.


person nybon    schedule 28.02.2013    source источник
comment
Может не все токены расходуются? Что произойдет, если вы заставите синтаксический анализатор выполнять синтаксический анализ до конца ввода: parse : booleanTerm EOF;   -  person Bart Kiers    schedule 28.02.2013
comment
Почему вы не используете BailErrorStrategy?   -  person Sam Harwell    schedule 28.02.2013


Ответы (1)


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

Следующее правило start заставит его анализировать всю входную последовательность как один booleanTerm.

start : booleanTerm EOF;

Кроме того, BailErrorStrategy предоставляется ANTLR 4. время выполнения и выдает более информативный ParseCancellationException, чем тот, который показан в вашем примере.

person Sam Harwell    schedule 28.02.2013
comment
Бесконечно благодарен. Это действительно решение проблемы, с которой я столкнулся, и я провел еще несколько поисков и нашел эту вики для Antlr 3, antlr.org/wiki/pages/viewpage.action?pageId=4554943, в котором описана точно такая же проблема. - person nybon; 01.03.2013
comment
Я думаю, проблема в том, что на самом деле нет официальной документации, описывающей это. Я читал как онлайн-документацию Antlr 4 (не так много), так и Окончательный справочник ANTLR 4, но я не могу вспомнить, чтобы я читал что-либо, что упоминает использование токена «EOF», как здесь. В окончательном справочнике по ANTLR 4 нет ни одного примера, в котором есть EOF в конце правила запуска, и он также не упоминается в разделе «Отчеты об ошибках и восстановление» :( - person nybon; 01.03.2013
comment
Я не знаю, есть ли встроенная BailErrorStrategy, которую я могу использовать, спасибо, что указали на это. Я попробую это. - person nybon; 01.03.2013
comment
@ 280Z28 Эй, я столкнулся с той же проблемой. Но моя проблема в том, что иногда мне нужно проанализировать дочернее правило (не начальное правило) с вводом только содержимого целевого дочернего правила. Парсер также отбрасывает оставшиеся токены. Как я могу это решить? Поскольку невозможно добавить EOF для всех дочерних правил. - person Stoneboy; 24.06.2014
comment
Я думаю, что у меня может быть та же проблема, что и у @Stoneboy. stackoverflow.com/questions/29834489/ - person RubberDuck; 24.04.2015
comment
@RubberDuck Мое окончательное решение состояло в том, чтобы добавить дополнительное правило EOF к каждому дочернему правилу, не чистое, но работающее, поскольку я не смог найти лучшего решения. Если вы можете найти лучшее решение, пожалуйста, поделитесь здесь в комментариях. Заранее спасибо. - person Stoneboy; 28.04.2015