Easier to read parser errors for SQL++
Change-Id: I13fd54fe2b1237b937a1706cf83fb47ce536b546
Reviewed-on: https://asterix-gerrit.ics.uci.edu/1182
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: abdullah alamoudi <bamousaa@gmail.com>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index 54f06e6..c19593b 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -3163,7 +3163,7 @@
<test-case FilePath="open-index-enforced/error-checking">
<compilation-unit name="missing-optionality">
<output-dir compare="Text">missing-optionality</output-dir>
- <expected-error>"?"</expected-error>
+ <expected-error>string) enforced</expected-error>
</compilation-unit>
</test-case>
<test-case FilePath="open-index-enforced/error-checking">
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/parser/ScopeChecker.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/parser/ScopeChecker.java
index dc0fa93..07aa473 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/parser/ScopeChecker.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/parser/ScopeChecker.java
@@ -29,6 +29,10 @@
public class ScopeChecker {
+ protected static String quot = "\"";
+
+ protected String eol = System.getProperty("line.separator", "\n");
+
protected Counter varCounter = new Counter(-1);
protected Stack<Scope> scopeStack = new Stack<Scope>();
@@ -57,7 +61,6 @@
/**
* Create a new scope, using the top scope in scopeStack as parent scope
*
- * @param scopeStack
* @return new scope
*/
public final Scope createNewScope() {
@@ -70,7 +73,6 @@
/**
* Extend the current scope
*
- * @param scopeStack
* @return
*/
public final Scope extendCurrentScope() {
@@ -172,7 +174,88 @@
return false;
}
- public static final String removeQuotesAndEscapes(String s) {
+ protected int appendExpected(StringBuilder expected, int[][] expectedTokenSequences, String[] tokenImage) {
+ int maxSize = 0;
+ for (int i = 0; i < expectedTokenSequences.length; i++) {
+ if (maxSize < expectedTokenSequences[i].length) {
+ maxSize = expectedTokenSequences[i].length;
+ }
+ for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+ append(expected, fixQuotes(tokenImage[expectedTokenSequences[i][j]]));
+ append(expected, " ");
+ }
+ if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
+ append(expected, "...");
+ }
+ append(expected, eol);
+ append(expected, " ");
+ }
+ return maxSize;
+ }
+
+ private void append(StringBuilder expected, String str) {
+ if (expected != null) {
+ expected.append(str);
+ }
+ }
+
+ protected String fixQuotes(String token) {
+ final int last = token.length() - 1;
+ if (token.charAt(0) == '"' && token.charAt(last) == '"') {
+ return "'" + token.substring(1, last) + "'";
+ } else {
+ return token;
+ }
+ }
+
+ protected static String addEscapes(String str) {
+ StringBuilder escaped = new StringBuilder();
+ for (int i = 0; i < str.length(); i++) {
+ appendChar(escaped, str.charAt(i));
+ }
+ return escaped.toString();
+ }
+
+ private static void appendChar(StringBuilder escaped, char c) {
+ char ch;
+ switch (c) {
+ case 0:
+ return;
+ case '\b':
+ escaped.append("\\b");
+ return;
+ case '\t':
+ escaped.append("\\t");
+ return;
+ case '\n':
+ escaped.append("\\n");
+ return;
+ case '\f':
+ escaped.append("\\f");
+ return;
+ case '\r':
+ escaped.append("\\r");
+ return;
+ case '\"':
+ escaped.append("\\\"");
+ return;
+ case '\'':
+ escaped.append("\\\'");
+ return;
+ case '\\':
+ escaped.append("\\\\");
+ return;
+ default:
+ if ((ch = c) < 0x20 || ch > 0x7e) {
+ String s = "0000" + Integer.toString(ch, 16);
+ escaped.append("\\u").append(s.substring(s.length() - 4, s.length()));
+ } else {
+ escaped.append(ch);
+ }
+ }
+ }
+
+ public static String removeQuotesAndEscapes(String s) {
char q = s.charAt(0); // simple or double quote
String stripped = s.substring(1, s.length() - 1);
int pos = stripped.indexOf('\\');
@@ -220,7 +303,11 @@
return res.toString();
}
- public String extractFragment(int beginLine, int beginColumn, int endLine, int endColumn) {
+ protected String getLine(int line) {
+ return inputLines[line - 1];
+ }
+
+ protected String extractFragment(int beginLine, int beginColumn, int endLine, int endColumn) {
StringBuilder extract = new StringBuilder();
if (beginLine == endLine) {
// special case that we need to handle separately
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 9cabf84..dab178b 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -151,8 +151,6 @@
import org.apache.hyracks.algebricks.core.algebra.expressions.IndexedNLJoinExpressionAnnotation;
import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
-
-
class SQLPPParser extends ScopeChecker implements IParser {
// optimizer hints
@@ -179,6 +177,9 @@
// data generator hints
private static final String DGEN_HINT = "dgen";
+ // error configuration
+ protected static final boolean REPORT_EXPECTED_TOKENS = false;
+
private static class IndexParams {
public IndexType type;
public int gramLength;
@@ -251,7 +252,7 @@
return rfdg;
}
- public SQLPPParser(String s){
+ public SQLPPParser(String s) {
this(new StringReader(s));
super.setInput(s);
}
@@ -272,9 +273,47 @@
// by the ANTLR-generated lexer or parser (e.g it does this for invalid backslash u + 4 hex digits escapes)
throw new AsterixException(new ParseException(e.getMessage()));
} catch (ParseException e) {
- throw new AsterixException(e.getMessage());
+ throw new AsterixException("Syntax error: " + getMessage(e));
}
}
+
+ protected String getMessage(ParseException pe) {
+ Token currentToken = pe.currentToken;
+ if (currentToken == null) {
+ return pe.getMessage();
+ }
+ int[][] expectedTokenSequences = pe.expectedTokenSequences;
+ String[] tokenImage = pe.tokenImage;
+ String sep = REPORT_EXPECTED_TOKENS ? eol : " ";
+ StringBuilder expected = REPORT_EXPECTED_TOKENS ? new StringBuilder() : null;
+ int maxSize = appendExpected(expected, expectedTokenSequences, tokenImage);
+ Token tok = currentToken.next;
+ int line = tok.beginLine;
+ String message = "In line " + line + " >>" + getLine(line) + "<<" + sep + "Encountered ";
+ for (int i = 0; i < maxSize; i++) {
+ if (i != 0) {
+ message += " ";
+ }
+ if (tok.kind == 0) {
+ message += fixQuotes(tokenImage[0]);
+ break;
+ }
+ message += fixQuotes(tokenImage[tok.kind]) + " ";
+ message += quot + addEscapes(tok.image) + quot;
+ tok = tok.next;
+ }
+ message += " at column " + currentToken.next.beginColumn + "." + sep;
+ if (REPORT_EXPECTED_TOKENS) {
+ if (expectedTokenSequences.length == 1) {
+ message += "Was expecting:" + sep + " ";
+ } else {
+ message += "Was expecting one of:" + sep + " ";
+ }
+ message += expected.toString();
+ }
+ return message;
+ }
+
}
PARSER_END(SQLPPParser)