Skip to content

Commit

Permalink
DROOLS-2823 FEEL Parser: refactor not unary negation and not func…
Browse files Browse the repository at this point in the history
…tion call; fix support to wildcard `?` (apache#2059)

* wip

* all tests pass

* wip

* fix input var type

* fields order, notExitst builtin f

* most compile-time tests pass

* fix all compiler tests

* fix all tests

* grammar fix

* cleanup

* move method back into its original position

* fix short-circuiting on null (notExists)

* test support to listeners

* fix error logging

* remove decision table in feel expresion tests -- no longer in 1.2

* replace commented out tests with error-checking tests

* move NOT to lexing section;

* fix erroneous reference to non-existing symbol "Not_Key"
  • Loading branch information
evacchi authored and mariofusco committed Sep 19, 2018
1 parent 760e4e9 commit 23b87d2
Show file tree
Hide file tree
Showing 28 changed files with 595 additions and 880 deletions.
Expand Up @@ -21,7 +21,9 @@
import org.kie.dmn.core.util.Msg; import org.kie.dmn.core.util.Msg;
import org.kie.dmn.core.util.MsgUtil; import org.kie.dmn.core.util.MsgUtil;
import org.kie.dmn.feel.FEEL; import org.kie.dmn.feel.FEEL;
import org.kie.dmn.feel.codegen.feel11.CompiledFEELSupport;
import org.kie.dmn.feel.codegen.feel11.CompilerBytecodeLoader; import org.kie.dmn.feel.codegen.feel11.CompilerBytecodeLoader;
import org.kie.dmn.feel.codegen.feel11.DirectCompilerResult;
import org.kie.dmn.feel.codegen.feel11.DirectCompilerVisitor; import org.kie.dmn.feel.codegen.feel11.DirectCompilerVisitor;
import org.kie.dmn.feel.lang.CompiledExpression; import org.kie.dmn.feel.lang.CompiledExpression;
import org.kie.dmn.feel.lang.CompilerContext; import org.kie.dmn.feel.lang.CompilerContext;
Expand All @@ -30,6 +32,7 @@
import org.kie.dmn.feel.lang.impl.EvaluationContextImpl; import org.kie.dmn.feel.lang.impl.EvaluationContextImpl;
import org.kie.dmn.feel.lang.impl.FEELEventListenersManager; import org.kie.dmn.feel.lang.impl.FEELEventListenersManager;
import org.kie.dmn.feel.lang.impl.FEELImpl; import org.kie.dmn.feel.lang.impl.FEELImpl;
import org.kie.dmn.feel.lang.types.BuiltInType;
import org.kie.dmn.feel.parser.feel11.FEELParser; import org.kie.dmn.feel.parser.feel11.FEELParser;
import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser; import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser;
import org.kie.dmn.feel.runtime.FEELFunction; import org.kie.dmn.feel.runtime.FEELFunction;
Expand Down Expand Up @@ -249,14 +252,30 @@ public Queue<FEELEvent> getFeelEvents() {
public String getSourceForUnaryTest(String packageName, String className, String input, DMNCompilerContext ctx, Type columntype) { public String getSourceForUnaryTest(String packageName, String className, String input, DMNCompilerContext ctx, Type columntype) {
Map<String, Type> variableTypes = new HashMap<>(); Map<String, Type> variableTypes = new HashMap<>();
for ( Map.Entry<String, DMNType> entry : ctx.getVariables().entrySet() ) { for ( Map.Entry<String, DMNType> entry : ctx.getVariables().entrySet() ) {
variableTypes.put( entry.getKey(), ((BaseDMNTypeImpl) entry.getValue()).getFeelType() ); variableTypes.put( entry.getKey(), dmnToFeelType((BaseDMNTypeImpl) entry.getValue()) );
} }
variableTypes.put( "?", columntype ); variableTypes.put( "?", columntype );


FEEL_1_1Parser parser = FEELParser.parse(null, input, variableTypes, Collections.emptyMap(), (( FEELImpl ) feel).getCustomFunctions(), Collections.emptyList()); FEELEventListenersManager manager = new FEELEventListenersManager();
ParseTree tree = parser.expressionList(); CompiledFEELSupport.SyntaxErrorListener errorListener = new CompiledFEELSupport.SyntaxErrorListener();
DirectCompilerVisitor v = new DirectCompilerVisitor(variableTypes, true); manager.addListener(errorListener);
return new CompilerBytecodeLoader().getSourceForUnaryTest(packageName, className, input, v.visit(tree)); FEEL_1_1Parser parser = FEELParser.parse(
manager, input, variableTypes, Collections.emptyMap(), (( FEELImpl ) feel).getCustomFunctions(), Collections.emptyList());
ParseTree tree = parser.unaryTestsRoot();
DirectCompilerResult result;
if (errorListener.isError()) {
result = CompiledFEELSupport.compiledErrorUnaryTest(errorListener.event().getMessage());
} else {
DirectCompilerVisitor v = new DirectCompilerVisitor(variableTypes, true);
result = v.visit(tree);
}
return new CompilerBytecodeLoader().getSourceForUnaryTest(packageName, className, input, result);
}


public static Type dmnToFeelType(BaseDMNTypeImpl v) {
if (v.isCollection()) return BuiltInType.LIST;
else return v.getFeelType();
} }


public EvaluationContextImpl newEvaluationContext( Collection<FEELEventListener> listeners, Map<String, Object> inputVariables) { public EvaluationContextImpl newEvaluationContext( Collection<FEELEventListener> listeners, Map<String, Object> inputVariables) {
Expand Down
Expand Up @@ -106,7 +106,10 @@ public EvaluatorResult evaluate( DMNRuntimeEventManager eventManager, DMNResult


eventResults = processEvents(events, eventManager, ( DMNResultImpl ) dmnResult, node); eventResults = processEvents(events, eventManager, ( DMNResultImpl ) dmnResult, node);


return new EvaluatorResultImpl( result, EvaluatorResult.ResultType.SUCCESS ); return new EvaluatorResultImpl(result,
eventResults.hasErrors?
EvaluatorResult.ResultType.FAILURE :
EvaluatorResult.ResultType.SUCCESS );
} catch (RuntimeException e) { } catch (RuntimeException e) {
logger.error(e.toString(), e); logger.error(e.toString(), e);
throw e; throw e;
Expand Down
Expand Up @@ -54,7 +54,7 @@ private Object[] initInputs(DMNFEELHelper feel) {


columnEvalCtxs[i] = feel.newEvaluationContext( Collections.singletonList( events::add ), evalCtx.getAllValues()); columnEvalCtxs[i] = feel.newEvaluationContext( Collections.singletonList( events::add ), evalCtx.getAllValues());
columnEvalCtxs[i].enterFrame(); columnEvalCtxs[i].enterFrame();
columnEvalCtxs[i].setValue( "?", inputs[i] ); columnEvalCtxs[i].setValue( "?", result );
} }
return inputs; return inputs;
} }
Expand Down
Expand Up @@ -56,7 +56,11 @@
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.internal.verification.VerificationModeFactory.times; import static org.mockito.internal.verification.VerificationModeFactory.times;


public class DMNDecisionTableRuntimeTest { public class DMNDecisionTableRuntimeTest extends BaseInterpretedVsCompiledTest {

public DMNDecisionTableRuntimeTest(boolean useExecModelCompiler) {
super(useExecModelCompiler);
}


@Test @Test
public void testDecisionTableWithCalculatedResult() { public void testDecisionTableWithCalculatedResult() {
Expand Down
Expand Up @@ -92,7 +92,11 @@
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.internal.verification.VerificationModeFactory.times; import static org.mockito.internal.verification.VerificationModeFactory.times;


public class DMNRuntimeTest { public class DMNRuntimeTest extends BaseInterpretedVsCompiledTest {

public DMNRuntimeTest(boolean useExecModelCompiler) {
super(useExecModelCompiler);
}


public static final Logger LOG = LoggerFactory.getLogger(DMNRuntimeTest.class); public static final Logger LOG = LoggerFactory.getLogger(DMNRuntimeTest.class);


Expand Down
Expand Up @@ -27,7 +27,7 @@
<semantic:text>-</semantic:text> <semantic:text>-</semantic:text>
</semantic:inputEntry> </semantic:inputEntry>
<semantic:outputEntry id="_aef76a51-de39-488c-bf54-fdb3e193613b"> <semantic:outputEntry id="_aef76a51-de39-488c-bf54-fdb3e193613b">
<semantic:text>-</semantic:text> <semantic:text>"DEFAULT"</semantic:text>
</semantic:outputEntry> </semantic:outputEntry>
</semantic:rule> </semantic:rule>
</semantic:decisionTable> </semantic:decisionTable>
Expand Down
Expand Up @@ -208,8 +208,7 @@ comparisonExpression
relationalExpression relationalExpression
: additiveExpression #relExpressionAdd : additiveExpression #relExpressionAdd
| val=relationalExpression between_key start=additiveExpression and_key end=additiveExpression #relExpressionBetween | val=relationalExpression between_key start=additiveExpression and_key end=additiveExpression #relExpressionBetween
| val=relationalExpression in_key '(' expressionList ')' #relExpressionValueList | val=relationalExpression in_key '(' positiveUnaryTests ')' #relExpressionTestList
| val=relationalExpression in_key '(' simpleUnaryTests ')' #relExpressionTestList
| val=relationalExpression in_key expression #relExpressionValue // includes simpleUnaryTest | val=relationalExpression in_key expression #relExpressionValue // includes simpleUnaryTest
| val=relationalExpression instance_key of_key type #relExpressionInstanceOf | val=relationalExpression instance_key of_key type #relExpressionInstanceOf
; ;
Expand Down Expand Up @@ -247,9 +246,7 @@ unaryExpression
; ;


unaryExpressionNotPlusMinus unaryExpressionNotPlusMinus
: not_key '(' simpleUnaryTests ')' #negatedUnaryTests : primary ('.' {helper.recoverScope();helper.enableDynamicResolution();} qualifiedName parameters? {helper.disableDynamicResolution();helper.dismissScope();} )? #uenpmPrimary
| not_key unaryExpression #logicalNegation
| primary ('.' {helper.recoverScope();helper.enableDynamicResolution();} qualifiedName parameters? {helper.disableDynamicResolution();helper.dismissScope();} )? #uenpmPrimary
; ;


primary primary
Expand All @@ -261,7 +258,7 @@ primary
| list #primaryList | list #primaryList
| context #primaryContext | context #primaryContext
| '(' expression ')' #primaryParens | '(' expression ')' #primaryParens
| simpleUnaryTest #primaryUnaryTest | simplePositiveUnaryTest #primaryUnaryTest
| qualifiedName parameters? #primaryName | qualifiedName parameters? #primaryName
; ;


Expand All @@ -282,22 +279,53 @@ booleanLiteral
/************************** /**************************
* OTHER CONSTRUCTS * OTHER CONSTRUCTS
**************************/ **************************/
// #13
simpleUnaryTests
: (simpleUnaryTest|primary) ( ',' (simpleUnaryTest|primary) )*
;


// #7 // #7
simpleUnaryTest simplePositiveUnaryTest
: op='<' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq : op='<' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq
| op='>' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq | op='>' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq
| op='<=' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq | op='<=' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq
| op='>=' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq | op='>=' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq
| op='=' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq | op='=' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq
| op='!=' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq | op='!=' {helper.enableDynamicResolution();} endpoint {helper.disableDynamicResolution();} #positiveUnaryTestIneq
| interval #positiveUnaryTestInterval | interval #positiveUnaryTestInterval
| null_key #positiveUnaryTestNull ;
| '-' #positiveUnaryTestDash

// #13
simplePositiveUnaryTests
: simplePositiveUnaryTest ( ',' simplePositiveUnaryTest )*
;


// #14
simpleUnaryTests
: simplePositiveUnaryTests #positiveSimplePositiveUnaryTests
| not_key '(' simplePositiveUnaryTests ')' #negatedSimplePositiveUnaryTests
| '-' #positiveUnaryTestDash
;

// #15
positiveUnaryTest
: expression
;

// #16
positiveUnaryTests
: positiveUnaryTest ( ',' positiveUnaryTest )*
;


unaryTestsRoot
: unaryTests EOF
;

// #17 (root for decision tables)
unaryTests
:
not_key '(' positiveUnaryTests ')' #unaryTests_negated
| positiveUnaryTests #unaryTests_positive
| '-' #unaryTests_empty
; ;


// #18 // #18
Expand Down Expand Up @@ -338,11 +366,13 @@ qualifiedName
; ;


nameRef nameRef
: st=Identifier { helper.startVariable( $st ); } nameRefOtherToken* : ( st=Identifier { helper.startVariable( $st ); }
| not_st=NOT { helper.startVariable( $not_st ); }
) nameRefOtherToken*
; ;


nameRefOtherToken nameRefOtherToken // added some special cases here: we should rework a bit the lexing part, though --ev
: { helper.followUp( _input.LT(1), _localctx==null ) }? ~('('|')'|'['|']'|'{'|'}'|'>'|'<'|'='|'!') : { helper.followUp( _input.LT(1), _localctx==null ) }? ~('('|')'|'['|']'|'{'|'}'|'>'|'<'|'='|'!'|'/'|'*'|'in'|',')
; ;


/******************************** /********************************
Expand Down Expand Up @@ -436,7 +466,7 @@ between_key
; ;


not_key not_key
: 'not' : NOT
; ;


null_key null_key
Expand Down Expand Up @@ -673,6 +703,10 @@ SUB : '-';
MUL : '*'; MUL : '*';
DIV : '/'; DIV : '/';
NOT
: 'not'
;
Identifier Identifier
: JavaLetter JavaLetterOrDigit* : JavaLetter JavaLetterOrDigit*
; ;
Expand Down
Expand Up @@ -173,7 +173,21 @@ private static Comparable asComparable(Object s) {
throw new IllegalArgumentException("Unable to transform s " + s + "as Comparable"); throw new IllegalArgumentException("Unable to transform s " + s + "as Comparable");
} }
} }


public static Boolean coerceToBoolean(EvaluationContext ctx, Object value) {
if (value instanceof Boolean) return (Boolean) value;

ctx.notifyEvt( () -> new ASTEventBase(
FEELEvent.Severity.ERROR,
Msg.createMessage(
Msg.X_TYPE_INCOMPATIBLE_WITH_Y_TYPE,
value == null? "null" : value.getClass(),
"Boolean"),
null));

return null;
}

public static <T> T coerceTo(Class<?> paramType, Object value) { public static <T> T coerceTo(Class<?> paramType, Object value) {
Object actual; Object actual;
if( paramType.isAssignableFrom( value.getClass() ) ) { if( paramType.isAssignableFrom( value.getClass() ) ) {
Expand Down Expand Up @@ -346,6 +360,14 @@ public static Boolean eq(Object left, Object right) {
return EvalHelper.isEqual(left, right, null); return EvalHelper.isEqual(left, right, null);
} }


public static Boolean gracefulEq(EvaluationContext ctx, Object left, Object right) {
if (left instanceof List) {
return ((List) left).contains(right);
} else {
return eq(left, right);
}
}

public static Boolean between(EvaluationContext ctx, public static Boolean between(EvaluationContext ctx,
Object value, Object start, Object end) { Object value, Object start, Object end) {
if ( value == null ) { ctx.notifyEvt(() -> new ASTEventBase(FEELEvent.Severity.ERROR, Msg.createMessage(Msg.IS_NULL, "value"), null) ); return null; } if ( value == null ) { ctx.notifyEvt(() -> new ASTEventBase(FEELEvent.Severity.ERROR, Msg.createMessage(Msg.IS_NULL, "value"), null) ); return null; }
Expand Down
Expand Up @@ -25,17 +25,47 @@
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;


import org.antlr.v4.runtime.tree.ParseTree;
import org.drools.javaparser.JavaParser;
import org.drools.javaparser.ast.Modifier;
import org.drools.javaparser.ast.NodeList;
import org.drools.javaparser.ast.body.FieldDeclaration;
import org.drools.javaparser.ast.body.Parameter;
import org.drools.javaparser.ast.body.VariableDeclarator;
import org.drools.javaparser.ast.expr.BooleanLiteralExpr;
import org.drools.javaparser.ast.expr.LambdaExpr;
import org.drools.javaparser.ast.expr.MethodCallExpr;
import org.drools.javaparser.ast.expr.NameExpr;
import org.drools.javaparser.ast.expr.NullLiteralExpr;
import org.drools.javaparser.ast.expr.StringLiteralExpr;
import org.drools.javaparser.ast.stmt.BlockStmt;
import org.drools.javaparser.ast.stmt.ExpressionStmt;
import org.drools.javaparser.ast.stmt.ReturnStmt;
import org.drools.javaparser.ast.stmt.Statement;
import org.drools.javaparser.ast.type.UnknownType;
import org.kie.dmn.api.feel.runtime.events.FEELEvent;
import org.kie.dmn.api.feel.runtime.events.FEELEvent.Severity; import org.kie.dmn.api.feel.runtime.events.FEELEvent.Severity;
import org.kie.dmn.api.feel.runtime.events.FEELEventListener;
import org.kie.dmn.feel.lang.CompiledExpression;
import org.kie.dmn.feel.lang.CompilerContext;
import org.kie.dmn.feel.lang.EvaluationContext; import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.ast.BaseNode;
import org.kie.dmn.feel.lang.ast.ForExpressionNode; import org.kie.dmn.feel.lang.ast.ForExpressionNode;
import org.kie.dmn.feel.lang.ast.ForExpressionNode.ForIteration; import org.kie.dmn.feel.lang.ast.ForExpressionNode.ForIteration;
import org.kie.dmn.feel.lang.ast.QuantifiedExpressionNode; import org.kie.dmn.feel.lang.ast.QuantifiedExpressionNode;
import org.kie.dmn.feel.lang.ast.QuantifiedExpressionNode.QEIteration; import org.kie.dmn.feel.lang.ast.QuantifiedExpressionNode.QEIteration;
import org.kie.dmn.feel.lang.ast.QuantifiedExpressionNode.Quantifier; import org.kie.dmn.feel.lang.ast.QuantifiedExpressionNode.Quantifier;
import org.kie.dmn.feel.lang.impl.CompiledExpressionImpl;
import org.kie.dmn.feel.lang.impl.SilentWrappingEvaluationContextImpl; import org.kie.dmn.feel.lang.impl.SilentWrappingEvaluationContextImpl;
import org.kie.dmn.feel.lang.types.BuiltInType;
import org.kie.dmn.feel.parser.feel11.ASTBuilderVisitor;
import org.kie.dmn.feel.parser.feel11.FEELParser;
import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser;
import org.kie.dmn.feel.runtime.FEELFunction; import org.kie.dmn.feel.runtime.FEELFunction;
import org.kie.dmn.feel.runtime.UnaryTest; import org.kie.dmn.feel.runtime.UnaryTest;
import org.kie.dmn.feel.runtime.events.ASTEventBase; import org.kie.dmn.feel.runtime.events.ASTEventBase;
import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
import org.kie.dmn.feel.runtime.events.SyntaxErrorEvent;
import org.kie.dmn.feel.util.EvalHelper; import org.kie.dmn.feel.util.EvalHelper;
import org.kie.dmn.feel.util.Msg; import org.kie.dmn.feel.util.Msg;


Expand Down Expand Up @@ -424,4 +454,73 @@ public static Object notifyCompilationError(EvaluationContext feelExprCtx, Strin
public static Object coerceNumber(Object value) { public static Object coerceNumber(Object value) {
return EvalHelper.coerceNumber(value); return EvalHelper.coerceNumber(value);
} }


/**
* Generates a compilable class that reports a (compile-time) error at runtime
*/
public static CompiledFEELExpression compiledError(String expression, String msg) {
return new CompilerBytecodeLoader()
.makeFromJPExpression(
expression,
compiledErrorExpression(msg),
Collections.emptySet());
}

public static DirectCompilerResult compiledErrorUnaryTest(String msg) {

LambdaExpr initializer = new LambdaExpr();
initializer.setEnclosingParameters(true);
initializer.addParameter(new Parameter(new UnknownType(), "feelExprCtx"));
initializer.addParameter(new Parameter(new UnknownType(), "left"));
Statement lambdaBody = new BlockStmt(new NodeList<>(
new ExpressionStmt(compiledErrorExpression(msg)),
new ReturnStmt(new BooleanLiteralExpr(false))
));
initializer.setBody(lambdaBody);
String constantName = "UT_EMPTY";
VariableDeclarator vd = new VariableDeclarator(JavaParser.parseClassOrInterfaceType(UnaryTest.class.getCanonicalName()), constantName);
vd.setInitializer(initializer);
FieldDeclaration fd = new FieldDeclaration();
fd.setModifier(Modifier.PUBLIC, true);
fd.setModifier(Modifier.STATIC, true);
fd.setModifier(Modifier.FINAL, true);
fd.addVariable(vd);

fd.setJavadocComment(" FEEL unary test: - ");

MethodCallExpr list = new MethodCallExpr(null, "list", new NodeList<>(new NameExpr(constantName)));

DirectCompilerResult directCompilerResult = DirectCompilerResult.of(list, BuiltInType.LIST);
directCompilerResult.addFieldDesclaration(fd);
return directCompilerResult;

}


public static MethodCallExpr compiledErrorExpression(String msg) {
return new MethodCallExpr(
new NameExpr("CompiledFEELSupport"),
"notifyCompilationError",
new NodeList<>(
new NameExpr("feelExprCtx"),
new StringLiteralExpr(msg)));
}

// thread-unsafe, but this is single-threaded so it's ok
public static class SyntaxErrorListener implements FEELEventListener {
private FEELEvent event = null;
@Override
public void onEvent(FEELEvent evt) {
if (evt instanceof SyntaxErrorEvent
|| evt instanceof InvalidParametersEvent) {
this.event = evt;
}
}
public boolean isError() { return event != null; }
public FEELEvent event() { return event; }
}



} }

0 comments on commit 23b87d2

Please sign in to comment.