Reputation: 3
I'm new to ANTLR and trying to support logical expressions. My problem is that the grammar is considering even a single '&' to be a logical AND (same goes for | and OR), which should not be the case.
Here's my grammar file (TagQuery.g4):
query
: (expression)+
;
expression
: keyValue #comparisonExpression
| inExpression #singleInExpression
| hasExpression #singleHasExpression
| expressionGroup #singleExpressionGroup
| expression logicalOperator expression #logicalExpression
| expressionGroup logicalOperator expressionGroup #expressionGroupExpression
;
inExpression
: key IN literalList
;
hasExpression
: HAS key
;
expressionGroup
: NOT? L_RND_BRACKET (expression)+ R_RND_BRACKET
;
keyValue
: key comparisonOperator literal
;
key
: KEY_PREFIX (keyName) R_RND_BRACKET
;
keyName
: IDENTIFIER
;
comparisonOperator
: EQ | NEQ
;
logicalOperator
: AND | OR
;
literalList
: L_SQ_BRACKET literal (COMMA literal)* R_SQ_BRACKET
;
literal
: STRING_LITERAL
;
// keywords
AND : ' '*('&&' | 'AND' | 'and')' '*;
OR : ' '*('||' | 'OR' | 'or')' '*;
IDENTIFIER : [A-Za-z0-9_]+ ;
ESCAPED_IDENTIFIER : '"' ( '"' '"' | [^"] )* '"';
STRING_LITERAL : '\'' [A-Za-z0-9_ ]* '\'';
NOT : ' '*('!' | 'not' | 'NOT')' '*;
HAS : ' '*('HAS' | 'has')' '*;
IN : ' '*('IN' | 'in')' '*;
EQ : ' '*'='' '*;
NEQ : ' '*'!='' '*;
L_SQ_BRACKET : ' '*'['' '*;
R_SQ_BRACKET : ' '*']'' '*;
L_RND_BRACKET : ' '*'('' '*;
R_RND_BRACKET : ' '*')'' '*;
COMMA : ' '*','' '*;
KEY_PREFIX : 'tags('' '*;
//KEY_SUFFIX : ')'' '*;
SPACE : [ \t\r\n]+ -> channel(HIDDEN);
WS : [ \t\r\n]+ -> channel(HIDDEN);
//BLANK : ' '*;
A sample valid query would be as follows:
tags(environment) = 'prod' && tags(region) = 'syd'
Here's the method that evaluates the query:
public static Filter forQuery(String query) throws ParseException {
try {
LOGGER.info("Attempting to get tag filter for query {}", query);
CharStream in = CharStreams.fromStream(new ByteArrayInputStream(query.getBytes()));
TagQueryLexer lexer = new TagQueryLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
TagQueryParser parser = new TagQueryParser(tokens);
TagQueryListener listener = new TagQueryListener();
parser.addParseListener(listener);
TagQueryParseErrorListener errorListener = new TagQueryParseErrorListener();
parser.addErrorListener(errorListener);
parser.query();
if (errorListener.getException() != null) {
listener.getFilter().setParseError(errorListener.getException());
LOGGER.error("Tag filter for query {} returned an error. Please check query syntax \n {}", query, errorListener.getException());
throw errorListener.getException();
}
LOGGER.info("Tag filter for query {} returned successfully", query);
return listener.getFilter();
} catch (IOException e) {
LOGGER.error("Unexpected error encountered while getting filter for query {} \n {}", query, e);
throw new RuntimeException(e);
}
}
Here are some invalid queries:
(tags(project) = 'cloudcollectors' & tags(jira) = 'XTNXBL')
(tags(project) = 'cloudcollectors' | tags(jira) = 'XTNXBL')
(tags(project) = 'cloudcollectors' tags(jira) = 'XTNXBL')
(tags(project) = 'cloudcollectors' tags(jira) = 'XTNXBL')
(tags(project) = 'cloudcollectors'tags(jira) = 'XTNXBL')
I expect the errorListener in the method above to throw an exception as these queries are syntactically incorrect. However, that does not happen and they proceed to return a Filter object.
Can someone suggest how to get past this? Am I missing something in my grammar? Thank you!
Upvotes: 0
Views: 200
Reputation: 6785
As @kaby76 points out, you're getting a token recognition error, this comes from the Lexer. You'll also need to attach your ErrorListener
to the Lexer.
lexer.addErrorListener(errorListener);
Also, you really want your ErrorListener to collect all of the errors it encounters. Just interrogating errorListener.getException()
is (probably) just going to give you the last error it encountered.
When ANTLR encounters a syntaxError, it's just going to can the syntaxError(...)
method on all attached listeners and continue processing. It's up to you to decide how to react to a syntax error reported to your listener.
You can choose how to handle your exceptions, but a "normal" process would be to build an ArrayList
of all the errors you encounter and make it accessible in your TagQueryListener
class. Then you can add methods to check for errors, check max severity, check error count, etc. (You could also just chose to throw an Exception to abort processing at the first error your encounter, but that's not going to be a great user experience.)
(I agree with his advise re: whitespace handling as well)
Upvotes: 1