C# ANTLR4 DefaultErrorStrategy или пользовательский прослушиватель ошибок не перехватывает нераспознанные символы - PullRequest
1 голос
/ 29 марта 2020

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

Моя грамматика

grammar Polynomial;

parse           : canonical EOF
                ;

canonical       : polynomial+                                     #canonicalPolynom
                | polynomial+ EQUAL polynomial+                   #equality
                ;

polynomial      : SIGN? '(' (polynomial)* ')'                     #parens
                | monomial                                        #monom
                ;

monomial        : SIGN? coefficient? VAR ('^' INT)?               #addend
                | SIGN? coefficient                               #number
                ;

coefficient             : INT | DEC;

INT                     : ('0'..'9')+;
DEC                     : INT '.' INT;
VAR                     : [a-z]+;
SIGN                    : '+' | '-';
EQUAL                   : '=';
WHITESPACE              : (' '|'\t')+ -> skip;

, и я даю ввод 23*44=12 или @1234

Я ожидаю, что мой синтаксический анализатор выдает несовпадающий токен или любое другое исключение для символа * или @, который не определен в моей грамматике.

Вместо этого мой анализатор просто пропускает * или @ и пересекает дерево, как будто его не существует.

Моя функция-обработчик, в которой я вызываю лексер, анализатор и все такое.

private static (IParseTree tree, string parseErrorMessage) TryParseExpression(string expression)
        {
            ICharStream stream = CharStreams.fromstring(expression);
            ITokenSource lexer = new PolynomialLexer(stream);

            ITokenStream tokens = new CommonTokenStream(lexer);
            PolynomialParser parser = new PolynomialParser(tokens);

            //parser.ErrorHandler = new PolynomialErrorStrategy(); -> I tried custom error strategy
            //parser.RemoveErrorListeners();
            //parser.AddErrorListener(new PolynomialErrorListener()); -> I tried custom error listener
            parser.BuildParseTree = true;

            try
            {
                var tree = parser.canonical();
                return (tree, string.Empty);
            }
            catch (RecognitionException re)
            {
                return (null, re.Message);
            }
            catch (ParseCanceledException pce)
            {
                return (null, pce.Message);
            }
        }            

Я попытался добавить пользовательский обработчик ошибок.

public class PolynomialErrorListener : BaseErrorListener
    {
        private const string Eof = "EOF";

        public override void SyntaxError(TextWriter output, IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg,
            RecognitionException e)
        {
            if (msg.Contains(Eof))
            {
                throw new ParseCanceledException($"{GetSyntaxErrorHeader(charPositionInLine)}. Missing an expression after '=' sign");
            }

            if (e is NoViableAltException || e is InputMismatchException)
            {
                throw new ParseCanceledException($"{GetSyntaxErrorHeader(charPositionInLine)}. Probably, not closed operator");
            }

            throw new ParseCanceledException($"{GetSyntaxErrorHeader(charPositionInLine)}. {msg}");
        }

        private static string GetSyntaxErrorHeader(int errorPosition)
        {
            return $"Expression is invalid. Input is not valid at {--errorPosition} position";
        }
    }

После этого я попытался реализовать собственную стратегию ошибок.

public class PolynomialErrorStrategy : DefaultErrorStrategy
    {
        public override void ReportError(Parser recognizer, RecognitionException e)
        {
            throw e;
        }

        public override void Recover(Parser recognizer, RecognitionException e)
        {
            for (ParserRuleContext context = recognizer.Context; context != null; context = (ParserRuleContext) context.Parent) {
                context.exception = e;
            }

            throw new ParseCanceledException(e);
        }

        public override IToken RecoverInline(Parser recognizer)
        {
            InputMismatchException e = new InputMismatchException(recognizer);
            for (ParserRuleContext context = recognizer.Context; context != null; context = (ParserRuleContext) context.Parent) {
                context.exception = e;
            }

            throw new ParseCanceledException(e);
        }

        protected override void ReportInputMismatch(Parser recognizer, InputMismatchException e)
        {
            string msg = "mismatched input " + GetTokenErrorDisplay(e.OffendingToken);
            // msg += " expecting one of " + e.GetExpectedTokens().ToString(recognizer.());
            RecognitionException ex = new RecognitionException(msg, recognizer, recognizer.InputStream, recognizer.Context);
            throw ex;
        }

        protected override void ReportMissingToken(Parser recognizer)
        {
            BeginErrorCondition(recognizer);
            IToken token = recognizer.CurrentToken;
            IntervalSet expecting = GetExpectedTokens(recognizer);
            string msg = "missing " + expecting.ToString() + " at " + GetTokenErrorDisplay(token);
            throw new RecognitionException(msg, recognizer, recognizer.InputStream, recognizer.Context);
        }
    }

Есть ли флаг, который я забыл указать в синтаксическом анализаторе или у меня неправильная грамматика?

Забавно, что я использую плагин ANTLR в своей IDE, и когда я тестирую свою грамматику здесь, этот плагин правильно отвечает с line 1:2 token recognition error at: '*'

Полный исходный код: https://github.com/EvgeniyZ/PolynomialCanonicForm

Я использую ANTLR 4.8-complete.jar

Редактировать

Я пытался добавить к правилу грамматики

parse           : canonical EOF
                ;

Все еще не повезло здесь

1 Ответ

1 голос
/ 30 марта 2020

Что произойдет, если вы сделаете это:

parse
 : canonical EOF
 ;

, а также вызовете это правило:

var tree = parser.parse();

Добавив токен EOF (конец ввода), вы заставляете синтаксический анализатор использует все токены, что должно приводить к ошибке, когда синтаксический анализатор не может обработать их должным образом.

Забавно, что я использую плагин ANTLR в своей IDE и когда я тестирую свою грамматику здесь этот плагин правильно отвечает line 1:2 token recognition error at: '*'

Это то, что лексер испускает в потоке std.err. Лексер просто сообщает об этом предупреждении и идет своим путем. Таким образом, лексер просто игнорирует эти символы и поэтому никогда не попадает в парсер. Если вы добавите следующую строку в конце вашего лексера:

// Fallback rule: matches any single character if not matched by another lexer rule
UNKNOWN : . ;

, тогда символы * и @ будут отправлены анализатору как токены UNKNOWN и должны вызвать ошибки распознавания.

...