/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * *************************************************************************************** */ package com.espertech.esper.epl.parse; import com.espertech.esper.client.EPStatementSyntaxException; import com.espertech.esper.client.PropertyAccessException; import com.espertech.esper.collection.UniformPair; import com.espertech.esper.epl.generated.EsperEPL2GrammarLexer; import com.espertech.esper.epl.generated.EsperEPL2GrammarParser; import org.antlr.v4.runtime.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.StringWriter; import java.util.Locale; import java.util.Set; import java.util.Stack; /** * Converts recognition exceptions. */ public class ExceptionConvertor { private static final Logger log = LoggerFactory.getLogger(ExceptionConvertor.class); protected final static String END_OF_INPUT_TEXT = "end-of-input"; /** * Converts from a syntax error to a nice statement exception. * * @param e is the syntax error * @param expression is the expression text * @param parser the parser that parsed the expression * @param addPleaseCheck indicates to add "please check" paraphrases * @return syntax exception */ public static EPStatementSyntaxException convertStatement(RecognitionException e, String expression, boolean addPleaseCheck, EsperEPL2GrammarParser parser) { UniformPair<String> pair = convert(e, expression, addPleaseCheck, parser); return new EPStatementSyntaxException(pair.getFirst(), pair.getSecond()); } /** * Converts from a syntax error to a nice property exception. * * @param e is the syntax error * @param expression is the expression text * @param parser the parser that parsed the expression * @param addPleaseCheck indicates to add "please check" paraphrases * @return syntax exception */ public static PropertyAccessException convertProperty(RecognitionException e, String expression, boolean addPleaseCheck, EsperEPL2GrammarParser parser) { UniformPair<String> pair = convert(e, expression, addPleaseCheck, parser); return new PropertyAccessException(pair.getFirst(), pair.getSecond()); } /** * Converts from a syntax error to a nice exception. * * @param e is the syntax error * @param expression is the expression text * @param parser the parser that parsed the expression * @param addPleaseCheck indicates to add "please check" paraphrases * @return syntax exception */ public static UniformPair<String> convert(RecognitionException e, String expression, boolean addPleaseCheck, EsperEPL2GrammarParser parser) { if (expression.trim().length() == 0) { String message = "Unexpected " + END_OF_INPUT_TEXT; return new UniformPair<String>(message, expression); } Token t; Token tBeforeBefore = null; Token tBefore = null; Token tAfter = null; int tIndex = e.getOffendingToken() != null ? e.getOffendingToken().getTokenIndex() : Integer.MAX_VALUE; if (tIndex < parser.getTokenStream().size()) { t = parser.getTokenStream().get(tIndex); if ((tIndex + 1) < parser.getTokenStream().size()) { tAfter = parser.getTokenStream().get(tIndex + 1); } if (tIndex - 1 >= 0) { tBefore = parser.getTokenStream().get(tIndex - 1); } if (tIndex - 2 >= 0) { tBeforeBefore = parser.getTokenStream().get(tIndex - 2); } } else { if (parser.getTokenStream().size() >= 1) { tBeforeBefore = parser.getTokenStream().get(parser.getTokenStream().size() - 1); } if (parser.getTokenStream().size() >= 2) { tBefore = parser.getTokenStream().get(parser.getTokenStream().size() - 2); } t = parser.getTokenStream().get(parser.getTokenStream().size() - 1); } Token tEnd = null; if (parser.getTokenStream().size() > 0) { tEnd = parser.getTokenStream().get(parser.getTokenStream().size() - 1); } String positionInfo = getPositionInfo(t); String token = t.getType() == EsperEPL2GrammarParser.EOF ? "end-of-input" : "'" + t.getText() + "'"; Stack stack = parser.getParaphrases(); String check = ""; boolean isSelect = stack.size() == 1 && stack.get(0).equals("select clause"); if ((stack.size() > 0) && addPleaseCheck) { String delimiter = ""; StringBuilder checkList = new StringBuilder(); checkList.append(", please check the "); while (stack.size() != 0) { checkList.append(delimiter); checkList.append(stack.pop()); delimiter = " within the "; } check = checkList.toString(); } // check if token is a reserved keyword Set<String> keywords = parser.getKeywords(); boolean reservedKeyword = false; if (keywords.contains(token.toLowerCase(Locale.ENGLISH))) { token += " (a reserved keyword)"; reservedKeyword = true; } else if (tAfter != null && keywords.contains("'" + tAfter.getText().toLowerCase(Locale.ENGLISH) + "'")) { token += " ('" + tAfter.getText() + "' is a reserved keyword)"; reservedKeyword = true; } else { if ((tBefore != null) && (tAfter != null) && (keywords.contains("'" + tBefore.getText().toLowerCase(Locale.ENGLISH) + "'")) && (keywords.contains("'" + tAfter.getText().toLowerCase(Locale.ENGLISH) + "'"))) { token += " ('" + tBefore.getText() + "' and '" + tAfter.getText() + "' are a reserved keyword)"; reservedKeyword = true; } else if ((tBefore != null) && (keywords.contains("'" + tBefore.getText().toLowerCase(Locale.ENGLISH) + "'"))) { token += " ('" + tBefore.getText() + "' is a reserved keyword)"; reservedKeyword = true; } else if (tEnd != null && keywords.contains("'" + tEnd.getText().toLowerCase(Locale.ENGLISH) + "'")) { token += " ('" + tEnd.getText() + "' is a reserved keyword)"; reservedKeyword = true; } } // special handling for the select-clause "as" keyword, which is required if (isSelect && !reservedKeyword) { check += getSelectClauseAsText(tBeforeBefore, t); } String message = "Incorrect syntax near " + token + positionInfo + check; if (e instanceof NoViableAltException || e instanceof LexerNoViableAltException || checkForInputMismatchWithNoExpected(e)) { Token nvaeToken = e.getOffendingToken(); int nvaeTokenType = nvaeToken != null ? nvaeToken.getType() : EsperEPL2GrammarLexer.EOF; if (nvaeTokenType == EsperEPL2GrammarLexer.EOF) { if (token.equals(END_OF_INPUT_TEXT)) { message = "Unexpected " + END_OF_INPUT_TEXT + positionInfo + check; } else { if (ParseHelper.hasControlCharacters(expression)) { message = "Unrecognized control characters found in text" + positionInfo; } else { message = "Unexpected " + END_OF_INPUT_TEXT + " near " + token + positionInfo + check; } } } else { if (parser.getParserTokenParaphrases().get(nvaeTokenType) != null) { message = "Incorrect syntax near " + token + positionInfo + check; } else { // find next keyword in the next 3 tokens int currentIndex = tIndex + 1; while ((currentIndex > 0) && (currentIndex < parser.getTokenStream().size() - 1) && (currentIndex < tIndex + 3)) { Token next = parser.getTokenStream().get(currentIndex); currentIndex++; String quotedToken = "'" + next.getText() + "'"; if (parser.getKeywords().contains(quotedToken)) { check += " near reserved keyword '" + next.getText() + "'"; break; } } message = "Incorrect syntax near " + token + positionInfo + check; } } } else if (e instanceof InputMismatchException) { InputMismatchException mismatched = (InputMismatchException) e; String expected; if (mismatched.getExpectedTokens().size() > 1) { StringWriter writer = new StringWriter(); writer.append("any of the following tokens {"); String delimiter = ""; for (int i = 0; i < mismatched.getExpectedTokens().size(); i++) { writer.append(delimiter); if (i > 5) { writer.append("..."); writer.append(Integer.toString(mismatched.getExpectedTokens().size() - 5)); writer.append(" more"); break; } delimiter = ", "; writer.append(getTokenText(parser, mismatched.getExpectedTokens().get(i))); } writer.append("}"); expected = writer.toString(); } else { expected = getTokenText(parser, mismatched.getExpectedTokens().get(0)); } int offendingTokenType = mismatched.getOffendingToken().getType(); String unexpected = getTokenText(parser, offendingTokenType); String expecting = " expecting " + expected.trim() + " but found " + unexpected.trim(); message = "Incorrect syntax near " + token + expecting + positionInfo + check; } return new UniformPair<String>(message, expression); } private static boolean checkForInputMismatchWithNoExpected(RecognitionException e) { if (!(e instanceof InputMismatchException)) { return false; } if (e.getExpectedTokens().size() > 1) { return false; } return e.getExpectedTokens().size() == 1 && e.getExpectedTokens().get(0) == -1; } private static String getTokenText(EsperEPL2GrammarParser parser, int tokenIndex) { String expected = END_OF_INPUT_TEXT; if ((tokenIndex >= 0) && (tokenIndex < parser.getTokenNames().length)) { expected = parser.getTokenNames()[tokenIndex]; } if (parser.getLexerTokenParaphrases().get(tokenIndex) != null) { expected = parser.getLexerTokenParaphrases().get(tokenIndex); } if (parser.getParserTokenParaphrases().get(tokenIndex) != null) { expected = parser.getParserTokenParaphrases().get(tokenIndex); } return expected; } /** * Returns the position information string for a parser exception. * * @param t the token to return the information for * @return is a string with line and column information */ private static String getPositionInfo(Token t) { return t.getLine() > 0 && t.getCharPositionInLine() > 0 ? " at line " + t.getLine() + " column " + t.getCharPositionInLine() : ""; } private static String getSelectClauseAsText(Token tBeforeBefore, Token t) { if (tBeforeBefore != null && tBeforeBefore.getType() == EsperEPL2GrammarParser.IDENT && t != null && t.getType() == EsperEPL2GrammarParser.IDENT) { return " (did you forget 'as'?)"; } return ""; } }