Skip to content

Commit

Permalink
Add error listener to DrlExprParser (apache#5778)
Browse files Browse the repository at this point in the history
  • Loading branch information
yurloc authored and rgdoliveira committed Oct 24, 2024
1 parent f36a0c9 commit f388e03
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1806,15 +1806,15 @@ protected ConstraintConnectiveDescr parseExpression(final RuleBuildContext conte
final String expression) {
DrlExprParser parser = DrlExprParserFactory.getDrlExprParser(context.getConfiguration().getOption(LanguageLevelOption.KEY));
ConstraintConnectiveDescr result = parser.parse(normalizeEval(expression));
result.setResource(patternDescr.getResource());
result.copyLocation(original);
if (parser.hasErrors()) {
for (DroolsParserException error : parser.getErrors()) {
registerDescrBuildError(context, patternDescr,
"Unable to parse pattern expression:\n" + error.getMessage());
}
return null;
}
result.setResource(patternDescr.getResource());
result.copyLocation(original);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public String getOperator() {
}

public void setOperator( String operator ) {
this.operator = operator.trim();
this.operator = operator != null ? operator.trim() : null;
}

public boolean isNegated() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.drools.drl.ast.descr.ConstraintConnectiveDescr;
import org.drools.drl.ast.descr.RelationalExprDescr;
import org.drools.drl.parser.DrlExprParser;
import org.drools.drl.parser.DroolsParserException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -325,4 +326,50 @@ public void testKeyword_instanceof() {
assertThat(left.getExpression()).isEqualTo("a");
assertThat(right.getExpression()).isEqualTo("A");
}

@Test
public void testMismatchedInput() {
String source = "+";
parser.parse(source);
assertThat(parser.hasErrors()).isTrue();
assertThat(parser.getErrors()).hasSize(1);
DroolsParserException exception = parser.getErrors().get(0);
assertThat(exception.getErrorCode()).isEqualTo("ERR 102");
assertThat(exception.getLineNumber()).isEqualTo(1);
assertThat(exception.getColumn()).isEqualTo(1);
assertThat(exception.getOffset()).isEqualTo(1);
assertThat(exception.getMessage())
.startsWithIgnoringCase("[ERR 102] Line 1:1 mismatched input '<EOF>' expecting ")
.contains("TIME_INTERVAL", "DRL_STRING_LITERAL", "?/", "boolean", "byte", "char", "double", "float", "int", "long", "new", "short", "super", "DECIMAL_LITERAL", "HEX_LITERAL", "FLOAT_LITERAL", "BOOL_LITERAL", "STRING_LITERAL", "null", "(", "[", ".", "<", "!", "~", "++", "--", "+", "-", "*", "/", "IDENTIFIER");
}

@Test
public void testExtraneousInput() {
String source = "a +; b";
parser.parse(source);
assertThat(parser.hasErrors()).isTrue();
assertThat(parser.getErrors()).hasSize(1);
DroolsParserException exception = parser.getErrors().get(0);
assertThat(exception.getErrorCode()).isEqualTo("ERR 109");
assertThat(exception.getLineNumber()).isEqualTo(1);
assertThat(exception.getColumn()).isEqualTo(3);
assertThat(exception.getOffset()).isEqualTo(3);
assertThat(exception.getMessage())
.startsWithIgnoringCase("[ERR 109] Line 1:3 extraneous input ';' expecting ")
.contains("TIME_INTERVAL", "DRL_STRING_LITERAL", "?/", "boolean", "byte", "char", "double", "float", "int", "long", "new", "short", "super", "DECIMAL_LITERAL", "HEX_LITERAL", "FLOAT_LITERAL", "BOOL_LITERAL", "STRING_LITERAL", "null", "(", "[", ".", "<", "!", "~", "++", "--", "+", "-", "*", "/", "IDENTIFIER");
}
@Test
public void testNoViableAlt() {
String source = "a~a";
parser.parse(source);
assertThat(parser.hasErrors()).isTrue();
assertThat(parser.getErrors()).hasSize(1);
DroolsParserException exception = parser.getErrors().get(0);
assertThat(exception.getErrorCode()).isEqualTo("ERR 101");
assertThat(exception.getLineNumber()).isEqualTo(1);
assertThat(exception.getColumn()).isEqualTo(2);
assertThat(exception.getOffset()).isEqualTo(2);
assertThat(exception.getMessage())
.isEqualToIgnoringCase("[ERR 101] Line 1:2 no viable alternative at input 'a'");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
import java.util.Collections;
import java.util.List;

import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.drools.drl.ast.descr.BaseDescr;
import org.drools.drl.ast.descr.ConstraintConnectiveDescr;
import org.drools.drl.parser.DrlExprParser;
Expand All @@ -47,21 +49,23 @@ public Drl6ExprParserAntlr4(LanguageLevelOption languageLevel) {
/** Parse an expression from text */
public ConstraintConnectiveDescr parse(final String text) {
ConstraintConnectiveDescr constraint = null;
try {
DRLLexer lexer = new DRLLexer(CharStreams.fromString(text));
CommonTokenStream input = new CommonTokenStream(lexer);
helper = new ParserHelper(input, null, languageLevel);
helper = new ParserHelper(input, languageLevel);
DRLExpressions parser = new DRL6Expressions(input, helper);
parser.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
helper.reportError(offendingSymbol, line, charPositionInLine, msg, e);
}
});
parser.setBuildDescr(true);
parser.setLeftMostExpr(null); // setting initial value just in case
BaseDescr expr = parser.conditionalOrExpressionDescr();
if (expr != null && !parser.hasErrors()) {
constraint = ConstraintConnectiveDescr.newAnd();
constraint.addOrMerge(expr);
}
} catch (RecognitionException e) {
helper.reportError(e);
}
return constraint;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.antlr.v4.runtime.FailedPredicateException;
import org.antlr.v4.runtime.InputMismatchException;
import org.antlr.v4.runtime.NoViableAltException;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Token;
Expand All @@ -42,8 +41,6 @@
* @see DroolsParserException
*/
public class DroolsParserExceptionFactory {
public final static String NO_VIABLE_ALT_MESSAGE = "Line %1$d:%2$d no viable alternative at input '%3$s'%4$s";
public final static String FAILED_PREDICATE_MESSAGE = "Line %1$d:%2$d rule '%3$s' failed predicate: {%4$s}?%5$s";
public final static String TRAILING_SEMI_COLON_NOT_ALLOWED_MESSAGE = "Line %1$d:%2$d trailing semi-colon not allowed%3$s";
public final static String PARSER_LOCATION_MESSAGE_COMPLETE = " in %1$s %2$s";
public final static String PARSER_LOCATION_MESSAGE_PART = " in %1$s";
Expand Down Expand Up @@ -103,58 +100,45 @@ public DroolsParserException createTrailingSemicolonException( int line,
/**
* This method creates a DroolsParserException full of information.
*
* @param e
* original exception
* @param offendingSymbol
* @param line
* @param charPositionInLine
* @param e original exception
* @return DroolsParserException filled.
*/
public DroolsParserException createDroolsException( RecognitionException e ) {
List<String> codeAndMessage = createErrorMessage( e );
return new DroolsParserException( codeAndMessage.get( 1 ),
codeAndMessage
.get( 0 ),
// TODO verify this is correct
e.getOffendingToken().getLine(),
e.getOffendingToken().getCharPositionInLine(),
e.getOffendingToken().getStartIndex(),
public DroolsParserException createDroolsException(Object offendingSymbol, int line, int charPositionInLine, String message, RecognitionException e ) {
return new DroolsParserException( determineErrorCode( e ),
String.format("Line %d:%d %s", line, charPositionInLine, message),
line,
charPositionInLine,
determineStartingIndex(offendingSymbol),
e );
}

private int determineStartingIndex(Object offendingSymbol) {
if (offendingSymbol instanceof Token) {
return ((Token) offendingSymbol).getStartIndex();
}
return -1;
}

/**
* This will take a RecognitionException, and create a sensible error
* message out of it
* Determines the error code based on a specific subtype of RecognitionException.
*/
private List<String> createErrorMessage( RecognitionException e ) {
List<String> codeAndMessage = new ArrayList<>( 2 );
String message;
private String determineErrorCode( RecognitionException e ) {
if (e == null) {
return "ERR 109";
}
if ( e instanceof InputMismatchException) {
return "ERR 102";
}
if ( e instanceof NoViableAltException ) {
// NoViableAltException nvae = (NoViableAltException) e;
message = String.format(
NO_VIABLE_ALT_MESSAGE,
// TODO verify this is correct
e.getOffendingToken().getLine(),
e.getOffendingToken().getCharPositionInLine(),
getBetterToken( e.getOffendingToken() ),
formatParserLocation() );
codeAndMessage.add( message );
codeAndMessage.add( "ERR 101" );
} else if ( e instanceof FailedPredicateException ) {
FailedPredicateException fpe = (FailedPredicateException) e;
String ruleName = fpe.getRecognizer().getRuleNames()[fpe.getRuleIndex()];
message = String.format(
FAILED_PREDICATE_MESSAGE,
// TODO verify this is correct
e.getOffendingToken().getLine(),
e.getOffendingToken().getCharPositionInLine(),
ruleName,
fpe.getPredicate(),
formatParserLocation() );
codeAndMessage.add( message );
codeAndMessage.add( "ERR 103" );
return "ERR 101";
}
if ( codeAndMessage.get( 0 ).length() == 0 ) {
codeAndMessage.add( "?????" );
if ( e instanceof FailedPredicateException ) {
return "ERR 103";
}
return codeAndMessage;
throw new IllegalArgumentException("Unexpected exception type: " + e);
}

public DroolsParserException createDroolsException( Exception e,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,9 @@ public class ParserHelper {
private final LanguageLevelOption languageLevel;

public ParserHelper(TokenStream input,
RecognizerSharedState state,
LanguageLevelOption languageLevel) {
this.errorMessageFactory = new DroolsParserExceptionFactory( paraphrases, languageLevel );
this.input = input;
this.state = state;
this.languageLevel = languageLevel;
}

Expand Down Expand Up @@ -360,15 +358,8 @@ public boolean validateAttribute( int index ) {
DroolsSoftKeywords.DIRECT );
}

public void reportError( RecognitionException ex ) {
// if we've already reported an error and have not matched a token
// yet successfully, don't report any errors.
if ( state.errorRecovery ) {
return;
}
state.errorRecovery = true;

errors.add( errorMessageFactory.createDroolsException( ex ) );
public void reportError(Object offendingSymbol, int line, int charPositionInLine, String message, RecognitionException ex ) {
errors.add( errorMessageFactory.createDroolsException( offendingSymbol, line, charPositionInLine, message, ex ) );
}

public void reportError( Exception e ) {
Expand Down

0 comments on commit f388e03

Please sign in to comment.