Можно ли исключить исключение, если вход недействителен?

У меня есть простая грамматика ANLTR и сопровождающий Visitor. Все работает отлично, если вход недействителен. Если вход недействителен, ошибки проглатываются, и мой калькулятор выходит с неправильным выходом.

Я попытался внедрить прослушиватель ошибок, верхом по методу Recover lexer и... ну... еще полдюжины других вещей сегодня. Может ли кто-нибудь показать мне, как просто выбросить ошибку, а не глотать плохие "токены"? (Я использую кавычки, потому что они не являются маркерами вообще. Символы undefined в моей грамматике.)

Действительный ввод:

1 + 2 * 3 - 4

Недопустимый ввод:

1 + 2 + 3 (4)

Я хочу бросить ArgumentException, если парсер/лексер встречается с круглой скобкой (или любым другим символом undefined). В настоящее время недопустимые символы, похоже, просто исчезают в эфире, и парсер просто держится, как будто ничего не случилось.

Если я запустил его в консоли с помощью команды grun, я получаю следующий вывод, поэтому он распознает недействительные токены на некотором уровне.

строка 1: 9 ошибка распознавания маркера в: '('

строка 1:11 ошибка распознавания токена в: ')'

и это получившееся дерево синтаксического анализа.

enter image description here

BasicMath.g4

grammar BasicMath;

/*
 * Parser Rules
 */

compileUnit : expression+ EOF;

expression :
    expression MULTIPLY expression #Multiplication
    | expression DIVIDE expression #Division
    | expression ADD expression #Addition
    | expression SUBTRACT expression #Subtraction
    | NUMBER #Number
    ; 

/*
 * Lexer Rules
 */

NUMBER : INT; //Leave room to extend what kind of math we can do.

INT : ('0'..'9')+;
MULTIPLY : '*';
DIVIDE : '/';
SUBTRACT : '-';
ADD : '+';

WS : [ \t\r\n] -> channel(HIDDEN);

Калькулятор:

public static class Calculator
{
    public static int Evaluate(string expression)
    {
        var lexer = new BasicMathLexer(new AntlrInputStream(expression));
        var tokens = new CommonTokenStream(lexer);
        var parser = new BasicMathParser(tokens);

        var tree = parser.compileUnit();

        var visitor = new IntegerMathVisitor();

        return visitor.Visit(tree);
    }
}

Ответ 1

@CoronA был прав. Ошибка в лексере.. Поэтому, хотя я все еще думаю, что создание ErrorStrategy было бы лучше, это то, что на самом деле работало для меня и моя цель выбросить исключение для ввода undefined.

Сначала я создал производный класс, который наследует от BaseErrorListener и реализует IAntlrErrorListener<T>. Вторая часть была моей проблемой все время кажется. Поскольку мой посетитель унаследовал от FooBarBaseVistor<int>, мой прослушиватель ошибок также должен был иметь тип, чтобы зарегистрировать его с помощью моего lexer.

class ThrowExceptionErrorListener : BaseErrorListener, IAntlrErrorListener<int>
{
    //BaseErrorListener implementation; not called in my test, but left it just in case

    public override void SyntaxError(IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
    {
        throw new ArgumentException("Invalid Expression: {0}", msg, e);
    }

    //IAntlrErrorListener<int> implementation; this one actually gets called.

    public void SyntaxError(IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
    {
        throw new ArgumentException("Invalid Expression: {0}", msg, e);
    }
}

И изменил мой класс Calculator, чтобы привязать мой пользовательский прослушиватель ошибок к lexer. Обратите внимание, что вам не нужно удалять ConsoleListener, как я сделал, для того, чтобы ошибка была фактически выбрана. Поскольку я на самом деле не использую его, я решил, что лучше идти вперед и делать это.

public static class Calculator
{
    public static int Evaluate(string expression)
    {
        var lexer = new BasicMathLexer(new AntlrInputStream(expression));
        lexer.RemoveErrorListeners(); //removes the default console listener
        lexer.AddErrorListener(new ThrowExceptionErrorListener());

        var tokens = new CommonTokenStream(lexer);
        var parser = new BasicMathParser(tokens);

        var tree = parser.compileUnit();

        var visitor = new IntegerMathVisitor();

        return visitor.Visit(tree);
    }
}

И что это. Исключение аргумента бросается, и этот тест теперь проходит.

    [TestMethod]
    [ExpectedException(typeof(ArgumentException))]
    public void BadInput()
    {
        var expr = "1 + 5 + 2(3)";
        int value = Calculator.Evaluate(expr);
    }

Одна последняя заметка. Если вы выбросите здесь RecognitionException, он снова будет проглочен. Рекомендуется использовать ParseCancelationException, потому что он не получается из RecognitionException, но я выбираю ArgumentException, потому что я чувствовал, что это наиболее важно для кода клиента С#.

Ответ 2

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

Так как ошибка возникает в лексере (лексер просто не знает символов ( или )), обработка ошибок должна быть прикреплена к лексеру. В Java это будет выглядеть так:

    lexer.addErrorListener(new BaseErrorListener()  {
        @Override
        public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
            throw new RuntimeException(e);
        }
    });

Синтаксис С# не должен быть далек от этого. Тем не менее я рекомендую не бросать исключение. Лучше собирайте ошибки в списке и сообщайте об этом после завершения лексера и не начинайте синтаксический анализ, если список ошибок не пуст.