Skip to content

Commit

Permalink
DROOLS-1701 Basic support for FEEL for expression
Browse files Browse the repository at this point in the history
  • Loading branch information
tarilabs authored and mariofusco committed Jul 5, 2018
1 parent cbaa6b2 commit 6c47004
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 26 deletions.
Expand Up @@ -16,15 +16,22 @@

package org.kie.dmn.feel.codegen.feel11;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import org.kie.dmn.api.feel.runtime.events.FEELEvent.Severity;
import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.ast.ForExpressionNode;
import org.kie.dmn.feel.lang.ast.ForExpressionNode.ForIteration;
import org.kie.dmn.feel.runtime.events.ASTEventBase;
import org.kie.dmn.feel.util.EvalHelper;
import org.kie.dmn.feel.util.Msg;

public class CompiledFEELSupport {

Expand Down Expand Up @@ -172,4 +179,127 @@ private Object fetchValue(final Object o, final String... names) {
return result;
}
}

public static ForBuilder ffor(EvaluationContext ctx) {
return new ForBuilder(ctx);
}

public static class ForBuilder {

private EvaluationContext ctx;
private List<IterationContextCompiled> iterationContexts = new ArrayList<>();

public ForBuilder(EvaluationContext evaluationContext) {
this.ctx = evaluationContext;
}

public ForBuilder with(Function<EvaluationContext, Object> nameExpression, Function<EvaluationContext, Object> iterationExpression) {
iterationContexts.add(new IterationContextCompiled(nameExpression, iterationExpression));
return this;
}

public ForBuilder with(Function<EvaluationContext, Object> nameExpression,
Function<EvaluationContext, Object> iterationExpression,
Function<EvaluationContext, Object> rangeEndExpression) {
iterationContexts.add(new IterationContextCompiled(nameExpression, iterationExpression, rangeEndExpression));
return this;
}

public Object rreturn(Function<EvaluationContext, Object> expression) {
try {
ctx.enterFrame();
List results = new ArrayList();
ctx.setValue("partial", results);
ForIteration[] ictx = initializeContexts(ctx, iterationContexts);

while (ForExpressionNode.nextIteration(ctx, ictx)) {
Object result = expression.apply(ctx);
results.add(result);
}
return results;
} catch (EndpointOfRangeNotOfNumberException e) {
// ast error already reported
return null;
} finally {
ctx.exitFrame();
}
}

private ForIteration[] initializeContexts(EvaluationContext ctx, List<IterationContextCompiled> iterationContexts) {
ForIteration[] ictx = new ForIteration[iterationContexts.size()];
int i = 0;
for (IterationContextCompiled icn : iterationContexts) {
ictx[i] = createQuantifiedExpressionIterationContext(ctx, icn);
if (i < iterationContexts.size() - 1 && ictx[i].hasNextValue()) {
ForExpressionNode.setValueIntoContext(ctx, ictx[i]);
}
i++;
}
return ictx;
}

private ForIteration createQuantifiedExpressionIterationContext(EvaluationContext ctx, IterationContextCompiled icn) {
ForIteration fi = null;
String name = (String) icn.getName().apply(ctx); // TODO revise
Object result = icn.getExpression().apply(ctx);
Object rangeEnd = icn.getRangeEndExpr().apply(ctx);
if (rangeEnd == null) {
Iterable values = result instanceof Iterable ? (Iterable) result : Collections.singletonList(result);
fi = new ForIteration(name, values);
} else {
valueMustBeANumber(ctx, result);
BigDecimal start = (BigDecimal) result;
valueMustBeANumber(ctx, rangeEnd);
BigDecimal end = (BigDecimal) rangeEnd;
fi = new ForIteration(name, start, end);
}
return fi;
}

private void valueMustBeANumber(EvaluationContext ctx, Object value) {
if (!(value instanceof BigDecimal)) {
ctx.notifyEvt(() -> new ASTEventBase(Severity.ERROR, Msg.createMessage(Msg.VALUE_X_NOT_A_VALID_ENDPOINT_FOR_RANGE_BECAUSE_NOT_A_NUMBER, value), null));
throw new EndpointOfRangeNotOfNumberException();
}
}

private static class EndpointOfRangeNotOfNumberException extends RuntimeException {

private static final long serialVersionUID = 1L;
}

public static class IterationContextCompiled {

private final Function<EvaluationContext, Object> name;
private final Function<EvaluationContext, Object> expression;
private final Function<EvaluationContext, Object> rangeEndExpr;

public IterationContextCompiled(Function<EvaluationContext, Object> name, Function<EvaluationContext, Object> expression) {
this.name = name;
this.expression = expression;
this.rangeEndExpr = (ctx) -> null;
}

public IterationContextCompiled(Function<EvaluationContext, Object> name, Function<EvaluationContext, Object> expression, Function<EvaluationContext, Object> rangeEndExpr) {
this.name = name;
this.expression = expression;
this.rangeEndExpr = rangeEndExpr;
}

public Function<EvaluationContext, Object> getName() {
return name;
}

public Function<EvaluationContext, Object> getExpression() {
return expression;
}

public Function<EvaluationContext, Object> getRangeEndExpr() {
return rangeEndExpr;
}

}
}


}
Expand Up @@ -23,9 +23,11 @@
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -65,6 +67,7 @@
import org.kie.dmn.feel.parser.feel11.FEEL_1_1BaseVisitor;
import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser;
import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser.ContextEntryContext;
import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser.IterationContextsContext;
import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser.KeyNameContext;
import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser.NameRefContext;
import org.kie.dmn.feel.parser.feel11.ParserHelper;
Expand Down Expand Up @@ -690,10 +693,12 @@ public DirectCompilerResult visitList(FEEL_1_1Parser.ListContext ctx) {
}
}

// @Override
// public DirectCompilerResult visitNameDefinition(FEEL_1_1Parser.NameDefinitionContext ctx) {
// throw new UnsupportedOperationException("TODO"); // TODO
// }
@Override
public DirectCompilerResult visitNameDefinition(FEEL_1_1Parser.NameDefinitionContext ctx) {
// this is used by the For loop for the variable name of the iteration contexts.
StringLiteralExpr expr = new StringLiteralExpr(EvalHelper.normalizeVariableName(ParserHelper.getOriginalText(ctx)));
return DirectCompilerResult.of(expr, BuiltInType.STRING);
}
//
// @Override
// public DirectCompilerResult visitContextEntry(FEEL_1_1Parser.ContextEntryContext ctx) {
Expand Down Expand Up @@ -766,19 +771,54 @@ public DirectCompilerResult visitContext(FEEL_1_1Parser.ContextContext ctx) {
//
// @Override
// public DirectCompilerResult visitIterationContext(FEEL_1_1Parser.IterationContextContext ctx) {
// throw new UnsupportedOperationException("TODO"); // TODO
// throw new UnsupportedOperationException("TODO"); // TODO won't be needed
// }
//
// @Override
// public DirectCompilerResult visitIterationContexts(FEEL_1_1Parser.IterationContextsContext ctx) {
// throw new UnsupportedOperationException("TODO"); // TODO
// }
//
// @Override
// public DirectCompilerResult visitForExpression(FEEL_1_1Parser.ForExpressionContext ctx) {
// throw new UnsupportedOperationException("TODO"); // TODO
// throw new UnsupportedOperationException("TODO"); // TODO won't be needed
// }

@Override
public DirectCompilerResult visitForExpression(FEEL_1_1Parser.ForExpressionContext ctx) {
Set<FieldDeclaration> fds = new HashSet<>();
MethodCallExpr forCall = new MethodCallExpr(new NameExpr(CompiledFEELSupport.class.getSimpleName()), "ffor");
forCall.addArgument(new NameExpr("feelExprCtx"));
Expression curForCallTail = forCall;
IterationContextsContext iCtxs = ctx.iterationContexts();
for (FEEL_1_1Parser.IterationContextContext ic : iCtxs.iterationContext()) {
DirectCompilerResult name = visit(ic.nameDefinition());
DirectCompilerResult expr = visit(ic.expression().get(0));
fds.addAll(name.getFieldDeclarations());
fds.addAll(expr.getFieldDeclarations());
if (ic.expression().size() == 1) {
MethodCallExpr filterWithCall = new MethodCallExpr(curForCallTail, "with");
Expression nameParam = anonFunctionEvaluationContext2Object(name.getExpression());
Expression exprParam = anonFunctionEvaluationContext2Object(expr.getExpression());
filterWithCall.addArgument(nameParam);
filterWithCall.addArgument(exprParam);
curForCallTail = filterWithCall;
} else {
DirectCompilerResult rangeEndExpr = visit(ic.expression().get(1));
fds.addAll(rangeEndExpr.getFieldDeclarations());
MethodCallExpr filterWithCall = new MethodCallExpr(curForCallTail, "with");
Expression nameParam = anonFunctionEvaluationContext2Object(name.getExpression());
Expression exprParam = anonFunctionEvaluationContext2Object(expr.getExpression());
Expression rangeEndExprParam = anonFunctionEvaluationContext2Object(rangeEndExpr.getExpression());
filterWithCall.addArgument(nameParam);
filterWithCall.addArgument(exprParam);
filterWithCall.addArgument(rangeEndExprParam);
curForCallTail = filterWithCall;
}
}
DirectCompilerResult expr = visit(ctx.expression());
fds.addAll(expr.getFieldDeclarations());
MethodCallExpr returnCall = new MethodCallExpr(curForCallTail, "rreturn");
Expression returnParam = anonFunctionEvaluationContext2Object(expr.getExpression());
returnCall.addArgument(returnParam);
return DirectCompilerResult.of(returnCall, expr.resultType, fds);
}

@Override
public DirectCompilerResult visitQualifiedName(FEEL_1_1Parser.QualifiedNameContext ctx) {
List<NameRefContext> parts = ctx.nameRef();
Expand Down Expand Up @@ -953,18 +993,7 @@ public DirectCompilerResult visitFilterPathExpression(FEEL_1_1Parser.FilterPathE
filterWithCall.addArgument(filter.getExpression());
} else {
// Then is the case Table 54: Semantics of lists, ROW: e1 is a list and type(FEEL(e2 , s')) is boolean
Expression anonFunctionClass = JavaParser.parseExpression("new java.util.function.Function<EvaluationContext, Object>() {\n" +
" @Override\n" +
" public Object apply(EvaluationContext feelExprCtx) {\n" +
" return null;\n" +
" }\n" +
"}");
List<ReturnStmt> lookupReturnList = anonFunctionClass.getChildNodesByType(ReturnStmt.class);
if (lookupReturnList.size() != 1) {
throw new RuntimeException("Something unexpected changed in the template.");
}
ReturnStmt returnStmt = lookupReturnList.get(0);
returnStmt.setExpression(filter.getExpression());
Expression anonFunctionClass = anonFunctionEvaluationContext2Object(filter.getExpression());
filterWithCall.addArgument(anonFunctionClass);
}
return DirectCompilerResult.of(filterWithCall, BuiltInType.UNKNOWN).withFD(expr).withFD(filter);
Expand All @@ -977,6 +1006,22 @@ public DirectCompilerResult visitFilterPathExpression(FEEL_1_1Parser.FilterPathE
}
}

private Expression anonFunctionEvaluationContext2Object(Expression expression) {
Expression anonFunctionClass = JavaParser.parseExpression("new java.util.function.Function<EvaluationContext, Object>() {\n" +
" @Override\n" +
" public Object apply(EvaluationContext feelExprCtx) {\n" +
" return null;\n" +
" }\n" +
"}");
List<ReturnStmt> lookupReturnList = anonFunctionClass.getChildNodesByType(ReturnStmt.class);
if (lookupReturnList.size() != 1) {
throw new RuntimeException("Something unexpected changed in the template.");
}
ReturnStmt returnStmt = lookupReturnList.get(0);
returnStmt.setExpression(expression);
return anonFunctionClass;
}

@Override
public DirectCompilerResult visitExpressionTextual(FEEL_1_1Parser.ExpressionTextualContext ctx) {
DirectCompilerResult expr = visit( ctx.expr );
Expand Down
Expand Up @@ -83,7 +83,7 @@ public Object evaluate(EvaluationContext ctx) {
}
}

private boolean nextIteration( EvaluationContext ctx, ForIteration[] ictx ) {
public static boolean nextIteration(EvaluationContext ctx, ForIteration[] ictx) {
int i = ictx.length-1;
while ( i >= 0 && i < ictx.length ) {
if ( ictx[i].hasNextValue() ) {
Expand All @@ -96,7 +96,7 @@ private boolean nextIteration( EvaluationContext ctx, ForIteration[] ictx ) {
return i >= 0;
}

private void setValueIntoContext(EvaluationContext ctx, ForIteration forIteration) {
public static void setValueIntoContext(EvaluationContext ctx, ForIteration forIteration) {
ctx.setValue( forIteration.getName(), forIteration.getNextValue() );
}

Expand Down Expand Up @@ -147,7 +147,7 @@ private void valueMustBeANumber(EvaluationContext ctx, Object value) {
}
}

private static class ForIteration {
public static class ForIteration {
private String name;
private Iterable values;

Expand Down
Expand Up @@ -21,6 +21,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

import com.github.javaparser.ast.expr.Expression;
import org.antlr.v4.runtime.tree.ParseTree;
Expand Down Expand Up @@ -259,6 +260,19 @@ public void test_filterPathSelection() {
assertThat(parseCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ].z"), is(Collections.emptyList()));
}

@Test
public void test_for() {
// for
assertThat(parseCompileEvaluate("for x in [ 10, 20, 30 ], y in [ 1, 2, 3 ] return x * y"),
is(Arrays.asList(10, 20, 30, 20, 40, 60, 30, 60, 90).stream().map(x -> BigDecimal.valueOf(x)).collect(Collectors.toList())));

// normal:
assertThat(parseCompileEvaluate("for x in [1, 2, 3] return x+1"),
is(Arrays.asList(1, 2, 3).stream().map(x -> BigDecimal.valueOf(x + 1)).collect(Collectors.toList())));

// TODO in order to parse correctly the enhanced for loop it is required to configure the FEEL Profiles
}

@Test
public void test_contextExpression() {
assertThat(parseCompileEvaluate("{}"), is(Collections.emptyMap()));
Expand Down

0 comments on commit 6c47004

Please sign in to comment.