Skip to content
This repository was archived by the owner on Jun 26, 2022. It is now read-only.

Commit fe685a7

Browse files
committed
Fixed Parser logic to capture all error when it fails to parse, by constructing new Lexer and TokenStream each time a parse rule is executed
1 parent 982f2f6 commit fe685a7

File tree

4 files changed

+103
-154
lines changed

4 files changed

+103
-154
lines changed

parser/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ java_library(
4242
"@vaticle_typedb_common//:common",
4343

4444
# External dependencies
45+
"@maven//:com_google_code_findbugs_jsr305",
4546
"@maven//:org_antlr_antlr4_runtime", # sync version with @antlr4_runtime//jar
4647
],
4748
tags = ["maven_coordinates=com.vaticle.typeql:typeql-parser:{pom_version}"],

parser/ErrorListener.java

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,67 +21,105 @@
2121

2222
package com.vaticle.typeql.lang.parser;
2323

24-
import org.antlr.v4.runtime.BaseErrorListener;
25-
import org.antlr.v4.runtime.RecognitionException;
26-
import org.antlr.v4.runtime.Recognizer;
27-
2824
import java.util.ArrayList;
2925
import java.util.List;
26+
import java.util.Objects;
3027
import java.util.stream.Collectors;
31-
28+
import org.antlr.v4.runtime.BaseErrorListener;
29+
import org.antlr.v4.runtime.RecognitionException;
30+
import org.antlr.v4.runtime.Recognizer;
3231
import static com.vaticle.typedb.common.collection.Collections.list;
32+
import static com.vaticle.typeql.lang.common.exception.ErrorMessage.SYNTAX_ERROR_DETAILED;
33+
import static com.vaticle.typeql.lang.common.exception.ErrorMessage.SYNTAX_ERROR_NO_DETAILS;
3334

3435
/**
35-
* ANTLR error listener that listens for syntax errors.
36-
* When a syntax error occurs, it is recorded. Call {@link ErrorListener#hasErrors()} to see if there were errors.
36+
* ANTLR error listener that listens for syntax errors, and record them.
3737
* View the errors with {@link ErrorListener#toString()}.
3838
*/
3939
public class ErrorListener extends BaseErrorListener {
4040

41-
private final List<String> query;
41+
private final List<String> queryLines;
4242
private final List<SyntaxError> errors = new ArrayList<>();
4343

44-
private ErrorListener(List<String> query) {
45-
this.query = query;
46-
}
47-
48-
/**
49-
* Create a {@link ErrorListener} without a reference to a query string.
50-
* This will have limited error-reporting abilities, but is necessary when dealing with very large queries
51-
* that should not be held in memory all at once.
52-
*/
53-
public static ErrorListener withoutQueryString() {
54-
return new ErrorListener(null);
44+
private ErrorListener(List<String> queryLines) {
45+
this.queryLines = queryLines;
5546
}
5647

5748
public static ErrorListener of(String query) {
58-
List<String> queryList = list(query.split("\n"));
59-
return new ErrorListener(queryList);
49+
List<String> queryLines = list(query.split("\n"));
50+
return new ErrorListener(queryLines);
6051
}
6152

6253
@Override
6354
public void syntaxError(
64-
Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg,
65-
RecognitionException e) {
66-
67-
if (query == null) {
68-
errors.add(new SyntaxError(null, line, 0, msg));
69-
} else {
70-
errors.add(new SyntaxError(query.get(line - 1), line, charPositionInLine, msg));
71-
}
55+
Recognizer<?, ?> recognizer, Object offendingSymbol,
56+
int line, int charPositionInLine, String msg, RecognitionException e
57+
) {
58+
errors.add(new SyntaxError(queryLines.get(line - 1), line, charPositionInLine, msg));
7259
}
7360

74-
public boolean hasErrors() {
75-
return !errors.isEmpty();
61+
@Override
62+
public String toString() {
63+
return errors.stream().map(SyntaxError::toString).collect(Collectors.joining("\n\n"));
7664
}
7765

78-
public void clearErrors() {
79-
errors.clear();
80-
}
66+
private static class SyntaxError {
8167

82-
@Override
83-
public String toString() {
84-
return errors.stream().map(SyntaxError::toString).collect(Collectors.joining("\n"));
68+
private final String queryLine;
69+
private final int line;
70+
private final int charPositionInLine;
71+
private final String msg;
72+
private final int hash;
73+
74+
public SyntaxError(String queryLine, int line, int charPositionInLine, String msg) {
75+
if (msg == null) throw new NullPointerException("Null msg");
76+
this.queryLine = queryLine;
77+
this.line = line;
78+
this.charPositionInLine = charPositionInLine;
79+
this.msg = msg;
80+
this.hash = Objects.hash(this.queryLine, this.line, this.charPositionInLine, this.msg);
81+
}
82+
83+
private String spaces(int len) {
84+
final char ch = ' ';
85+
char[] output = new char[len];
86+
for (int i = len - 1; i >= 0; i--) {
87+
output[i] = ch;
88+
}
89+
return new String(output);
90+
}
91+
92+
@Override
93+
public String toString() {
94+
if (queryLine == null) {
95+
return SYNTAX_ERROR_NO_DETAILS.message(line, msg);
96+
} else {
97+
// Error message appearance:
98+
//
99+
// syntax error at line 1:
100+
// match $
101+
// ^
102+
// blah blah antlr blah
103+
String pointer = spaces(charPositionInLine) + "^";
104+
return SYNTAX_ERROR_DETAILED.message(line, queryLine, pointer, msg);
105+
}
106+
}
107+
108+
@Override
109+
public boolean equals(Object o) {
110+
if (o == this) return true;
111+
if (!(o instanceof SyntaxError)) return false;
112+
SyntaxError that = (SyntaxError) o;
113+
return (Objects.equals(this.queryLine, that.queryLine) &&
114+
this.line == that.line &&
115+
this.charPositionInLine == that.charPositionInLine &&
116+
this.msg.equals(that.msg));
117+
}
118+
119+
@Override
120+
public int hashCode() {
121+
return hash;
122+
}
85123
}
86124
}
87125

parser/Parser.java

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161
import java.util.Set;
6262
import java.util.function.Function;
6363
import java.util.stream.Stream;
64+
import javax.annotation.Nullable;
65+
import org.antlr.v4.runtime.ANTLRErrorStrategy;
6466
import org.antlr.v4.runtime.BailErrorStrategy;
6567
import org.antlr.v4.runtime.CharStreams;
6668
import org.antlr.v4.runtime.CommonTokenStream;
@@ -76,6 +78,8 @@
7678
import static com.vaticle.typeql.lang.common.util.Strings.unescapeRegex;
7779
import static com.vaticle.typeql.lang.pattern.variable.UnboundVariable.hidden;
7880
import static java.util.stream.Collectors.toList;
81+
import static org.antlr.v4.runtime.atn.PredictionMode.LL_EXACT_AMBIG_DETECTION;
82+
import static org.antlr.v4.runtime.atn.PredictionMode.SLL;
7983

8084
/**
8185
* TypeQL query string parser to produce TypeQL Java objects
@@ -102,47 +106,43 @@ public TypeQLLexer lexer(String string) {
102106
}
103107

104108
private <CONTEXT extends ParserRuleContext, RETURN> RETURN parse(
105-
String rawTypeQLString, Function<TypeQLParser, CONTEXT> parserMethod, Function<CONTEXT, RETURN> visitor
109+
String rawTypeQLString, Function<TypeQLParser, CONTEXT> rule, Function<CONTEXT, RETURN> visitor
106110
) {
107111
if (rawTypeQLString == null) throw TypeQLException.of("Query String is NULL");
108112
String typeQLString = rawTypeQLString.stripTrailing();
109113
if (typeQLString.isEmpty()) throw TypeQLException.of("Query String is empty or blank");
110114

111-
ErrorListener errorListener = ErrorListener.of(typeQLString);
112-
TypeQLLexer lexer = lexer(typeQLString);
113-
114-
lexer.removeErrorListeners();
115-
lexer.addErrorListener(errorListener);
116-
117-
CommonTokenStream tokens = new CommonTokenStream(lexer);
118-
TypeQLParser parser = new TypeQLParser(tokens);
119-
120-
parser.removeErrorListeners();
121-
parser.addErrorListener(errorListener);
122-
123-
// BailErrorStrategy + SLL is a very fast parsing strategy for queries
124-
// that are expected to be correct. However, it may not be able to
125-
// provide detailed/useful error message, if at all.
126-
parser.setErrorHandler(new BailErrorStrategy());
127-
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
128-
129-
CONTEXT queryContext;
130115
try {
131-
queryContext = parserMethod.apply(parser);
116+
// BailErrorStrategy + SLL is a very fast parsing strategy for queries
117+
// that are expected to be correct. However, it may not be able to
118+
// provide detailed/useful error message, if at all.
119+
return visitor.apply(parseContext(rule, typeQLString, new BailErrorStrategy(), SLL, null));
132120
} catch (ParseCancellationException e) {
133121
// We parse the query one more time, with "strict strategy" :
134122
// DefaultErrorStrategy + LL_EXACT_AMBIG_DETECTION
135123
// This was not set to default parsing strategy, but it is useful
136124
// to produce detailed/useful error message
137-
errorListener.clearErrors();
138-
parser.setErrorHandler(new DefaultErrorStrategy());
139-
parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION);
140-
queryContext = parserMethod.apply(parser);
141-
125+
ErrorListener errorListener = ErrorListener.of(typeQLString);
126+
parseContext(rule, typeQLString, new DefaultErrorStrategy(), LL_EXACT_AMBIG_DETECTION, errorListener);
142127
throw TypeQLException.of(errorListener.toString());
143128
}
129+
}
144130

145-
return visitor.apply(queryContext);
131+
private <CONTEXT extends ParserRuleContext> CONTEXT parseContext(
132+
Function<TypeQLParser, CONTEXT> rule,
133+
String typeQLString, ANTLRErrorStrategy errorHandlingStrategy, PredictionMode prediction,
134+
@Nullable ErrorListener errorListener
135+
) {
136+
TypeQLLexer lexer = lexer(typeQLString);
137+
lexer.removeErrorListeners();
138+
if (errorListener != null) lexer.addErrorListener(errorListener);
139+
CommonTokenStream tokens = new CommonTokenStream(lexer);
140+
TypeQLParser parser = new TypeQLParser(tokens);
141+
parser.removeErrorListeners();
142+
if (errorListener != null) parser.addErrorListener(errorListener);
143+
parser.setErrorHandler(errorHandlingStrategy);
144+
parser.getInterpreter().setPredictionMode(prediction);
145+
return rule.apply(parser);
146146
}
147147

148148
@SuppressWarnings("unchecked")

parser/SyntaxError.java

Lines changed: 0 additions & 90 deletions
This file was deleted.

0 commit comments

Comments
 (0)