diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ForExpressionNode.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ForExpressionNode.java index f25aa0ca569..1229c354457 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ForExpressionNode.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ForExpressionNode.java @@ -25,6 +25,8 @@ import org.kie.dmn.feel.lang.Type; import org.kie.dmn.feel.lang.ast.forexpressioniterators.ForIteration; import org.kie.dmn.feel.lang.types.BuiltInType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; @@ -35,6 +37,7 @@ public class ForExpressionNode extends BaseNode { + private static final Logger LOG = LoggerFactory.getLogger(ForExpressionNode.class); private List iterationContexts; private BaseNode expression; @@ -74,16 +77,11 @@ public void setExpression(BaseNode expression) { public Object evaluate(EvaluationContext ctx) { try { ctx.enterFrame(); - List results = new ArrayList(); - ctx.setValue("partial", results); - ForIteration[] ictx = initializeContexts(ctx, iterationContexts); - - while (nextIteration(ctx, ictx)) { - Object result = expression.evaluate(ctx); - results.add(result); - ctx.exitFrame(); // last i-th scope unrolled, see also ForExpressionNode.nextIteration(...) - } - return results; + List toReturn = new ArrayList<>(); + ctx.setValue("partial", toReturn); + populateToReturn(0, ctx, toReturn); + LOG.trace("returning {}", toReturn); + return toReturn; } catch (EndpointOfRangeNotValidTypeException | EndpointOfRangeOfDifferentTypeException e) { // ast error already reported return null; @@ -92,28 +90,34 @@ public Object evaluate(EvaluationContext ctx) { } } - public static boolean nextIteration(EvaluationContext ctx, ForIteration[] ictx) { - int i = ictx.length - 1; - while (i >= 0 && i < ictx.length) { - if (ictx[i].hasNextValue()) { - ctx.enterFrame(); // on first iter, open last scope frame; or new ones when prev unrolled - setValueIntoContext(ctx, ictx[i]); - i++; - } else { - if (i > 0) { - // end of iter loop for this i-th scope; i-th scope is always unrolled as part of the - // for-loop cycle, so here must unroll the _prev_ scope; - // the if-guard for this code block makes sure NOT to unroll bottom one. - ctx.exitFrame(); - } - i--; + private void populateToReturn(int k, EvaluationContext ctx, List toPopulate) { + LOG.trace("populateToReturn at index {}", k); + if (k > iterationContexts.size() - 1) { + LOG.trace("Index {} out of range, returning", k); + return; + } + IterationContextNode iterationContextNode = iterationContexts.get(k); + ForIteration forIteration = createForIteration(ctx, iterationContextNode); + while (forIteration.hasNextValue()) { + LOG.trace("{} has next value", forIteration); + ctx.enterFrame(); // open loop scope frame, for every iter ctx, except last one as guarded by if clause + // above + setValueIntoContext(ctx, forIteration.getName(), forIteration.getNextValue()); + if (k == iterationContexts.size() - 1) { + LOG.trace("i == iterationContexts.size() -1: this is the last iteration context; evaluating {}", + expression); + Object result = expression.evaluate(ctx); + LOG.trace("add {} to toReturn", result); + toPopulate.add(result); + } else if (k < iterationContexts.size() - 1) { + populateToReturn(k + 1, ctx, toPopulate); } } - return i >= 0; + ctx.exitFrame(); } - public static void setValueIntoContext(EvaluationContext ctx, ForIteration forIteration) { - ctx.setValue(forIteration.getName(), forIteration.getNextValue()); + static void setValueIntoContext(EvaluationContext ctx, String name, Object value) { + ctx.setValue(name, value); } @Override @@ -121,32 +125,19 @@ public Type getResultType() { return BuiltInType.LIST; } - private ForIteration[] initializeContexts(EvaluationContext ctx, List iterationContexts) { - ForIteration[] ictx = new ForIteration[iterationContexts.size()]; - int i = 0; - for (IterationContextNode icn : iterationContexts) { - ictx[i] = createQuantifiedExpressionIterationContext(ctx, icn); - if (i < iterationContexts.size() - 1 && ictx[i].hasNextValue()) { - ctx.enterFrame(); // open loop scope frame, for every iter ctx, except last one as guarded by if clause above - setValueIntoContext(ctx, ictx[i]); - } - i++; - } - return ictx; - } - - private ForIteration createQuantifiedExpressionIterationContext(EvaluationContext ctx, IterationContextNode icn) { - ForIteration fi; - String name = icn.evaluateName(ctx); - Object result = icn.evaluate(ctx); - Object rangeEnd = icn.evaluateRangeEnd(ctx); + private ForIteration createForIteration(EvaluationContext ctx, IterationContextNode iterationContextNode) { + LOG.trace("Creating ForIteration for {}", iterationContextNode); + ForIteration toReturn; + String name = iterationContextNode.evaluateName(ctx); + Object result = iterationContextNode.evaluate(ctx); + Object rangeEnd = iterationContextNode.evaluateRangeEnd(ctx); if (rangeEnd == null) { - Iterable values = result instanceof Iterable ? (Iterable) result : Collections.singletonList(result); - fi = new ForIteration(name, values); + Iterable values = result instanceof Iterable iterable? iterable : Collections.singletonList(result); + toReturn = new ForIteration(name, values); } else { - fi = getForIteration(ctx, name, result, rangeEnd); + toReturn = getForIteration(ctx, name, result, rangeEnd); } - return fi; + return toReturn; } @Override @@ -161,5 +152,4 @@ public ASTNode[] getChildrenNode() { public T accept(Visitor v) { return v.visit(this); } - } diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/IterationContextNode.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/IterationContextNode.java index 70cfa00c9fa..0c11f2e71a6 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/IterationContextNode.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/IterationContextNode.java @@ -20,10 +20,14 @@ import org.antlr.v4.runtime.ParserRuleContext; import org.kie.dmn.feel.lang.EvaluationContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class IterationContextNode extends BaseNode { + private static final Logger LOG = LoggerFactory.getLogger(IterationContextNode.class); + private NameDefNode name; private BaseNode expression; private BaseNode rangeEndExpr = null; @@ -69,15 +73,18 @@ public void setExpression(BaseNode expression) { } public String evaluateName(EvaluationContext ctx) { + LOG.trace("evaluateName {}", name); return this.name.evaluate(ctx); } @Override public Object evaluate(EvaluationContext ctx) { + LOG.trace("evaluate {}", expression); return expression != null ? expression.evaluate( ctx ) : null; } public Object evaluateRangeEnd(EvaluationContext ctx) { + LOG.trace("evaluateRangeEnd {}", rangeEndExpr); return rangeEndExpr != null ? rangeEndExpr.evaluate(ctx) : null; } diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/forexpressioniterators/ForIteration.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/forexpressioniterators/ForIteration.java index 43f4f576c03..bd8719f2ef4 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/forexpressioniterators/ForIteration.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/forexpressioniterators/ForIteration.java @@ -64,4 +64,12 @@ public Object getNextValue() { public String getName() { return name; } + + @Override + public String toString() { + return "ForIteration{" + + "values=" + values + + ", name='" + name + '\'' + + '}'; + } } \ No newline at end of file diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/impl/EvaluationContextImpl.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/impl/EvaluationContextImpl.java index 47759d0aefa..8e1e75fa77c 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/impl/EvaluationContextImpl.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/impl/EvaluationContextImpl.java @@ -32,9 +32,13 @@ import org.kie.dmn.feel.lang.EvaluationContext; import org.kie.dmn.feel.lang.FEELDialect; import org.kie.dmn.feel.util.NumberEvalHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class EvaluationContextImpl implements EvaluationContext { + private static final Logger LOG = LoggerFactory.getLogger(EvaluationContextImpl.class); + private final FEELEventListenersManager eventsManager; private ArrayDeque stack; private DMNRuntime dmnRuntime; @@ -102,6 +106,7 @@ public Deque getStack() { @Override public void enterFrame() { + LOG.trace("Creating new head element in stack"); push( new ExecutionFrameImpl( peek() /*, symbols, scope*/ ) ); } @@ -111,11 +116,13 @@ public void enterFrame(int size) { @Override public void exitFrame() { + LOG.trace("Removing head element from stack"); pop(); } @Override public void setValue(String name, Object value) { + LOG.trace("put {} -> {} in head stack element", name, value); peek().setValue(name, NumberEvalHelper.coerceNumber(value ) ); } diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/codegen/feel11/DirectCompilerTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/codegen/feel11/DirectCompilerTest.java index 8cb00e16551..9231a8b1d5d 100644 --- a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/codegen/feel11/DirectCompilerTest.java +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/codegen/feel11/DirectCompilerTest.java @@ -20,7 +20,9 @@ import java.math.BigDecimal; import java.time.LocalDate; +import java.util.Arrays; import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.Test; import org.kie.dmn.feel.lang.EvaluationContext; @@ -33,12 +35,13 @@ import org.kie.dmn.feel.runtime.FEELConditionsAndLoopsTest; import org.kie.dmn.feel.runtime.FEELTernaryLogicTest; import org.kie.dmn.feel.runtime.functions.CustomFEELFunction; +import org.kie.dmn.feel.util.CompilerUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.assertj.core.api.Assertions.assertThat; -import static org.kie.dmn.feel.util.CompilerUtils.parse; -import static org.kie.dmn.feel.util.CompilerUtils.parseCompileEvaluate; +import static org.kie.dmn.feel.util.CompilerUtils.parseCodegen; +import static org.kie.dmn.feel.util.CompilerUtils.parseCodegenCompileEvaluate; import static org.kie.dmn.feel.util.DynamicTypeUtils.entry; import static org.kie.dmn.feel.util.DynamicTypeUtils.mapOf; @@ -49,47 +52,47 @@ public class DirectCompilerTest { @Test void feel_number() { - assertThat(parseCompileEvaluate("10")).isEqualTo(BigDecimal.valueOf(10)); + assertThat(parseCodegenCompileEvaluate("10")).isEqualTo(BigDecimal.valueOf(10)); } @Test void feel_negative_number() { - assertThat(parseCompileEvaluate("-10")).isEqualTo(BigDecimal.valueOf(-10)); + assertThat(parseCodegenCompileEvaluate("-10")).isEqualTo(BigDecimal.valueOf(-10)); } @Test void feel_drools_2143() { // DROOLS-2143: Allow ''--1' expression as per FEEL grammar rule 26 - assertThat(parseCompileEvaluate("--10")).isEqualTo(BigDecimal.valueOf(10)); - assertThat(parseCompileEvaluate("---10")).isEqualTo(BigDecimal.valueOf(-10)); - assertThat(parseCompileEvaluate("+10")).isEqualTo(BigDecimal.valueOf(10)); + assertThat(parseCodegenCompileEvaluate("--10")).isEqualTo(BigDecimal.valueOf(10)); + assertThat(parseCodegenCompileEvaluate("---10")).isEqualTo(BigDecimal.valueOf(-10)); + assertThat(parseCodegenCompileEvaluate("+10")).isEqualTo(BigDecimal.valueOf(10)); } @Test void feel_boolean() { - assertThat(parseCompileEvaluate("false")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate("true")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate("null")).isNull(); + assertThat(parseCodegenCompileEvaluate("false")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("true")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("null")).isNull(); } @Test void feel_null() { - assertThat(parseCompileEvaluate("null")).isNull(); + assertThat(parseCodegenCompileEvaluate("null")).isNull(); } @Test void feel_string() { - assertThat(parseCompileEvaluate("\"some string\"")).isEqualTo("some string" ); + assertThat(parseCodegenCompileEvaluate("\"some string\"")).isEqualTo("some string" ); } @Test void primary_parens() { - assertThat(parseCompileEvaluate("(\"some string\")")).isEqualTo("some string" ); - assertThat(parseCompileEvaluate("(123)")).isEqualTo(BigDecimal.valueOf(123)); - assertThat(parseCompileEvaluate("(-123)")).isEqualTo(BigDecimal.valueOf(-123)); - assertThat(parseCompileEvaluate("-(123)")).isEqualTo(BigDecimal.valueOf(-123)); - assertThat(parseCompileEvaluate("(false)")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate("(true)")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("(\"some string\")")).isEqualTo("some string" ); + assertThat(parseCodegenCompileEvaluate("(123)")).isEqualTo(BigDecimal.valueOf(123)); + assertThat(parseCodegenCompileEvaluate("(-123)")).isEqualTo(BigDecimal.valueOf(-123)); + assertThat(parseCodegenCompileEvaluate("-(123)")).isEqualTo(BigDecimal.valueOf(-123)); + assertThat(parseCodegenCompileEvaluate("(false)")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("(true)")).isEqualTo(Boolean.TRUE); } /** @@ -97,29 +100,29 @@ void primary_parens() { */ @Test void ternary_logic() { - assertThat(parseCompileEvaluate( "true and true")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate( "true and false")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate( "true and null")).isNull(); - assertThat(parseCompileEvaluate( "false and true")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate( "false and false")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate( "false and null")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate( "null and true")).isNull(); - assertThat(parseCompileEvaluate( "null and false")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate( "null and null")).isNull(); - assertThat(parseCompileEvaluate( "true or true")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate( "true or false")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate( "true or null")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate( "false or true")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate( "false or false")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate( "false or null")).isNull(); - assertThat(parseCompileEvaluate( "null or true")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate( "null or false")).isNull(); - assertThat(parseCompileEvaluate( "null or null")).isNull(); + assertThat(parseCodegenCompileEvaluate("true and true")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("true and false")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("true and null")).isNull(); + assertThat(parseCodegenCompileEvaluate("false and true")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("false and false")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("false and null")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("null and true")).isNull(); + assertThat(parseCodegenCompileEvaluate("null and false")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("null and null")).isNull(); + assertThat(parseCodegenCompileEvaluate("true or true")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("true or false")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("true or null")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("false or true")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("false or false")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("false or null")).isNull(); + assertThat(parseCodegenCompileEvaluate("null or true")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("null or false")).isNull(); + assertThat(parseCodegenCompileEvaluate("null or null")).isNull(); // logical operator priority - assertThat(parseCompileEvaluate( "false and false or true")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate( "false and (false or true)")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate( "true or false and false")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate( "(true or false) and false")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("false and false or true")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("false and (false or true)")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("true or false and false")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("(true or false) and false")).isEqualTo(Boolean.FALSE); } /** @@ -127,100 +130,100 @@ void ternary_logic() { */ @Test void test_if() { - assertThat(parseCompileEvaluate( "if true then 15 else 5")).isEqualTo(BigDecimal.valueOf( 15 )); - assertThat(parseCompileEvaluate( "if false then 15 else 5")).isEqualTo(BigDecimal.valueOf( 5 )); - assertThat(parseCompileEvaluate("if null then 15 else 5")).isEqualTo(BigDecimal.valueOf(5)); - assertThat(parseCompileEvaluate("if \"hello\" then 15 else 5")).isEqualTo(BigDecimal.valueOf(5)); + assertThat(parseCodegenCompileEvaluate("if true then 15 else 5")).isEqualTo(BigDecimal.valueOf(15 )); + assertThat(parseCodegenCompileEvaluate("if false then 15 else 5")).isEqualTo(BigDecimal.valueOf(5 )); + assertThat(parseCodegenCompileEvaluate("if null then 15 else 5")).isEqualTo(BigDecimal.valueOf(5)); + assertThat(parseCodegenCompileEvaluate("if \"hello\" then 15 else 5")).isEqualTo(BigDecimal.valueOf(5)); } @Test void additive_expression() { - assertThat(parseCompileEvaluate( "1 + 2")).isEqualTo(BigDecimal.valueOf( 3 )); - assertThat(parseCompileEvaluate( "1 + null")).isNull(); - assertThat(parseCompileEvaluate( "1 - 2")).isEqualTo(BigDecimal.valueOf( -1 )); - assertThat(parseCompileEvaluate( "1 - null")).isNull(); - assertThat(parseCompileEvaluate( "\"Hello, \" + \"World\"")).isEqualTo("Hello, World"); + assertThat(parseCodegenCompileEvaluate("1 + 2")).isEqualTo(BigDecimal.valueOf(3 )); + assertThat(parseCodegenCompileEvaluate("1 + null")).isNull(); + assertThat(parseCodegenCompileEvaluate("1 - 2")).isEqualTo(BigDecimal.valueOf(-1 )); + assertThat(parseCodegenCompileEvaluate("1 - null")).isNull(); + assertThat(parseCodegenCompileEvaluate("\"Hello, \" + \"World\"")).isEqualTo("Hello, World"); } @Test void multiplicative_expression() { - assertThat(parseCompileEvaluate("3 * 5")).isEqualTo(BigDecimal.valueOf(15)); - assertThat(parseCompileEvaluate("3 * null")).isNull(); - assertThat(parseCompileEvaluate("10 / 2")).isEqualTo(BigDecimal.valueOf(5)); - assertThat(parseCompileEvaluate("10 / null")).isNull(); + assertThat(parseCodegenCompileEvaluate("3 * 5")).isEqualTo(BigDecimal.valueOf(15)); + assertThat(parseCodegenCompileEvaluate("3 * null")).isNull(); + assertThat(parseCodegenCompileEvaluate("10 / 2")).isEqualTo(BigDecimal.valueOf(5)); + assertThat(parseCodegenCompileEvaluate("10 / null")).isNull(); } @Test void exponentiation_expression() { - assertThat(parseCompileEvaluate("3 ** 3")).isEqualTo(BigDecimal.valueOf(27)); - assertThat(parseCompileEvaluate("3 ** null")).isNull(); + assertThat(parseCodegenCompileEvaluate("3 ** 3")).isEqualTo(BigDecimal.valueOf(27)); + assertThat(parseCodegenCompileEvaluate("3 ** null")).isNull(); } @Test void logical_negation_expression() { // this is all invalid syntax - assertThat(parseCompileEvaluate("not true")).isNull(); - assertThat(parseCompileEvaluate("not false")).isNull(); - assertThat(parseCompileEvaluate("not null")).isNull(); - assertThat(parseCompileEvaluate("not 3")).isNull(); + assertThat(parseCodegenCompileEvaluate("not true")).isNull(); + assertThat(parseCodegenCompileEvaluate("not false")).isNull(); + assertThat(parseCodegenCompileEvaluate("not null")).isNull(); + assertThat(parseCodegenCompileEvaluate("not 3")).isNull(); } @Test void list_expression() { - assertThat(parseCompileEvaluate("[]")).asList().isEmpty(); - assertThat(parseCompileEvaluate("[ ]")).asList().isEmpty(); - assertThat(parseCompileEvaluate("[1]")).asList().containsExactly(BigDecimal.valueOf(1)); - assertThat(parseCompileEvaluate("[1, 2,3]")).asList().containsExactly(BigDecimal.valueOf(1), BigDecimal.valueOf(2), BigDecimal.valueOf(3)); + assertThat(parseCodegenCompileEvaluate("[]")).asList().isEmpty(); + assertThat(parseCodegenCompileEvaluate("[ ]")).asList().isEmpty(); + assertThat(parseCodegenCompileEvaluate("[1]")).asList().containsExactly(BigDecimal.valueOf(1)); + assertThat(parseCodegenCompileEvaluate("[1, 2,3]")).asList().containsExactly(BigDecimal.valueOf(1), BigDecimal.valueOf(2), BigDecimal.valueOf(3)); } @Test void instance_of_expression() { - assertThat(parseCompileEvaluate("123 instance of number")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate("\"ciao\" instance of number")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate("123 instance of string")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate("\"ciao\" instance of string")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("123 instance of number")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("\"ciao\" instance of number")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("123 instance of string")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("\"ciao\" instance of string")).isEqualTo(Boolean.TRUE); } @Test void between() { - assertThat(parseCompileEvaluate("10 between 5 and 12")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate("10 between 20 and 30")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate("10 between 5 and \"foo\"")).isNull(); - assertThat(parseCompileEvaluate("\"foo\" between 5 and 12")).isNull(); - assertThat(parseCompileEvaluate("\"foo\" between \"bar\" and \"zap\"")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate("\"foo\" between null and \"zap\"")).isNull(); + assertThat(parseCodegenCompileEvaluate("10 between 5 and 12")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("10 between 20 and 30")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("10 between 5 and \"foo\"")).isNull(); + assertThat(parseCodegenCompileEvaluate("\"foo\" between 5 and 12")).isNull(); + assertThat(parseCodegenCompileEvaluate("\"foo\" between \"bar\" and \"zap\"")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("\"foo\" between null and \"zap\"")).isNull(); } @Test void filter_path() { // Filtering by index - assertThat(parseCompileEvaluate("[\"a\", \"b\", \"c\"][1]")).isEqualTo("a"); - assertThat(parseCompileEvaluate("[\"a\", \"b\", \"c\"][2]")).isEqualTo("b"); - assertThat(parseCompileEvaluate("[\"a\", \"b\", \"c\"][3]")).isEqualTo("c"); - assertThat(parseCompileEvaluate("[\"a\", \"b\", \"c\"][-1]")).isEqualTo("c"); - assertThat(parseCompileEvaluate("[\"a\", \"b\", \"c\"][-2]")).isEqualTo("b"); - assertThat(parseCompileEvaluate("[\"a\", \"b\", \"c\"][-3]")).isEqualTo("a"); - assertThat(parseCompileEvaluate("[\"a\", \"b\", \"c\"][4]")).isNull(); - assertThat(parseCompileEvaluate("[\"a\", \"b\", \"c\"][984]")).isNull(); - assertThat(parseCompileEvaluate("[\"a\", \"b\", \"c\"][-4]")).isNull(); - assertThat(parseCompileEvaluate("[\"a\", \"b\", \"c\"][-984]")).isNull(); - assertThat(parseCompileEvaluate("\"a\"[1]")).isEqualTo("a"); - assertThat(parseCompileEvaluate("\"a\"[2]")).isNull(); - assertThat(parseCompileEvaluate("\"a\"[-1]")).isEqualTo("a"); - assertThat(parseCompileEvaluate("\"a\"[-2]")).isNull(); + assertThat(parseCodegenCompileEvaluate("[\"a\", \"b\", \"c\"][1]")).isEqualTo("a"); + assertThat(parseCodegenCompileEvaluate("[\"a\", \"b\", \"c\"][2]")).isEqualTo("b"); + assertThat(parseCodegenCompileEvaluate("[\"a\", \"b\", \"c\"][3]")).isEqualTo("c"); + assertThat(parseCodegenCompileEvaluate("[\"a\", \"b\", \"c\"][-1]")).isEqualTo("c"); + assertThat(parseCodegenCompileEvaluate("[\"a\", \"b\", \"c\"][-2]")).isEqualTo("b"); + assertThat(parseCodegenCompileEvaluate("[\"a\", \"b\", \"c\"][-3]")).isEqualTo("a"); + assertThat(parseCodegenCompileEvaluate("[\"a\", \"b\", \"c\"][4]")).isNull(); + assertThat(parseCodegenCompileEvaluate("[\"a\", \"b\", \"c\"][984]")).isNull(); + assertThat(parseCodegenCompileEvaluate("[\"a\", \"b\", \"c\"][-4]")).isNull(); + assertThat(parseCodegenCompileEvaluate("[\"a\", \"b\", \"c\"][-984]")).isNull(); + assertThat(parseCodegenCompileEvaluate("\"a\"[1]")).isEqualTo("a"); + assertThat(parseCodegenCompileEvaluate("\"a\"[2]")).isNull(); + assertThat(parseCodegenCompileEvaluate("\"a\"[-1]")).isEqualTo("a"); + assertThat(parseCodegenCompileEvaluate("\"a\"[-2]")).isNull(); // Filtering by boolean expression - assertThat(parseCompileEvaluate("[1, 2, 3, 4][item = 4]")).asList().containsExactly(BigDecimal.valueOf(4)); - assertThat(parseCompileEvaluate("[1, 2, 3, 4][item > 2]")).asList().containsExactly(BigDecimal.valueOf(3), BigDecimal.valueOf(4)); - assertThat(parseCompileEvaluate("[1, 2, 3, 4][item > 5]")).asList().isEmpty(); - assertThat(parseCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ][x = 1]")).asList().containsExactly(mapOf(entry("x", new BigDecimal(1)), entry("y", new BigDecimal(2)))); - assertThat(parseCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ][x > 1]")).asList().containsExactly(mapOf(entry("x", new BigDecimal(2)), entry("y", new BigDecimal(3)))); - assertThat(parseCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ][x = 0]")).asList().isEmpty(); + assertThat(parseCodegenCompileEvaluate("[1, 2, 3, 4][item = 4]")).asList().containsExactly(BigDecimal.valueOf(4)); + assertThat(parseCodegenCompileEvaluate("[1, 2, 3, 4][item > 2]")).asList().containsExactly(BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + assertThat(parseCodegenCompileEvaluate("[1, 2, 3, 4][item > 5]")).asList().isEmpty(); + assertThat(parseCodegenCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ][x = 1]")).asList().containsExactly(mapOf(entry("x", new BigDecimal(1)), entry("y", new BigDecimal(2)))); + assertThat(parseCodegenCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ][x > 1]")).asList().containsExactly(mapOf(entry("x", new BigDecimal(2)), entry("y", new BigDecimal(3)))); + assertThat(parseCodegenCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ][x = 0]")).asList().isEmpty(); } @Test void filter_path_tricky1() { - CompiledFEELExpression nameRef = parse( "[ {x:1, y:2}, {x:2, y:3} ][x]"); + CompiledFEELExpression nameRef = CompilerUtils.parseCodegen("[ {x:1, y:2}, {x:2, y:3} ][x]"); LOG.debug("{}", nameRef); EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); @@ -233,7 +236,7 @@ void filter_path_tricky1() { @Test void filter_path_tricky2() { - CompiledFEELExpression nameRef = parse("[ {x:1, y:2}, {x:2, y:3} ][x]"); + CompiledFEELExpression nameRef = CompilerUtils.parseCodegen("[ {x:1, y:2}, {x:2, y:3} ][x]"); LOG.debug("{}", nameRef); EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); @@ -247,85 +250,103 @@ void filter_path_tricky2() { @Test void filter_path_selection() { // Selection - assertThat(parseCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ].y")).asList().containsExactly(BigDecimal.valueOf(2), BigDecimal.valueOf(3)); - assertThat(parseCompileEvaluate("[ {x:1, y:2}, {x:2} ].y")).asList().containsExactly(BigDecimal.valueOf(2), null); - assertThat(parseCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ].z")).asList().containsExactly(null, null); + assertThat(parseCodegenCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ].y")).asList().containsExactly(BigDecimal.valueOf(2), BigDecimal.valueOf(3)); + assertThat(parseCodegenCompileEvaluate("[ {x:1, y:2}, {x:2} ].y")).asList().containsExactly(BigDecimal.valueOf(2), null); + assertThat(parseCodegenCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ].z")).asList().containsExactly(null, null); } @Test void test_for() { // for - Object parseCompileEvaluate = parseCompileEvaluate("for x in [ 10, 20, 30 ], y in [ 1, 2, 3 ] return x * y"); + Object parseCompileEvaluate = parseCodegenCompileEvaluate("for x in [ 10, 20, 30 ], y in [ 1, 2, 3 ] return x * y"); assertThat(parseCompileEvaluate).asList(). containsExactly(BigDecimal.valueOf(10), BigDecimal.valueOf(20), BigDecimal.valueOf(30), BigDecimal.valueOf(20), BigDecimal.valueOf(40), BigDecimal.valueOf(60), BigDecimal.valueOf(30), BigDecimal.valueOf(60), BigDecimal.valueOf(90)); // normal: - assertThat(parseCompileEvaluate("for x in [1, 2, 3] return x+1")).asList(). + assertThat(parseCodegenCompileEvaluate("for x in [1, 2, 3] return x+1")).asList(). containsExactly(BigDecimal.valueOf(2), BigDecimal.valueOf(3), BigDecimal.valueOf(4)); // TODO in order to parse correctly the enhanced for loop it is required to configure the FEEL Profiles } + @Test + void test_nested_for() { + List firstExpected = Arrays.asList(BigDecimal.ONE, BigDecimal.valueOf(2)); + List secondExpected = Arrays.asList(BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + Object parseCompileEvaluate = parseCodegenCompileEvaluate("for x in [ [1, 2], [3, 4] ] return x"); + assertThat(parseCompileEvaluate).asList(). + containsExactly(firstExpected, secondExpected); + parseCompileEvaluate = parseCodegenCompileEvaluate("for x in [ [1,2], [3,4] ] return for y in x return y"); + assertThat(parseCompileEvaluate).asList(). + containsExactly(firstExpected, secondExpected); + parseCompileEvaluate = CompilerUtils.parseCodegenCompileEvaluate("for x in [ 1, 2, 3, 4 ], y in x return y"); + assertThat(parseCompileEvaluate).asList(). + containsExactly(BigDecimal.ONE, BigDecimal.valueOf(2), BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + parseCompileEvaluate = parseCodegenCompileEvaluate("for x in [ [1,2], [3,4] ], y in x return y"); + assertThat(parseCompileEvaluate).asList(). + containsExactly(BigDecimal.ONE, BigDecimal.valueOf(2), BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + } + @Test void quantified_expressions() { // quantified expressions - assertThat(parseCompileEvaluate("some price in [ 80, 11, 110 ] satisfies price > 100")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate("some price in [ 80, 11, 90 ] satisfies price > 100")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate("some x in [ 5, 6, 7 ], y in [ 10, 11, 6 ] satisfies x > y")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate("every price in [ 80, 11, 90 ] satisfies price > 10")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate("every price in [ 80, 11, 90 ] satisfies price > 70")).isEqualTo(Boolean.FALSE); - assertThat(parseCompileEvaluate("some x in [ 5, 6, 7 ], y in [ 10, 11, 12 ] satisfies x < y")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate("some price in [ 80, 11, 110 ] satisfies price > max(100, 50, 10)")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("some price in [ 80, 11, 110 ] satisfies price > 100")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("some price in [ 80, 11, 90 ] satisfies price > 100")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("some x in [ 5, 6, 7 ], y in [ 10, 11, 6 ] satisfies x > y")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("every price in [ 80, 11, 90 ] satisfies price > 10")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("every price in [ 80, 11, 90 ] satisfies price > 70")).isEqualTo(Boolean.FALSE); + assertThat(parseCodegenCompileEvaluate("some x in [ 5, 6, 7 ], y in [ 10, 11, 12 ] satisfies x < y")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("some price in [ 80, 11, 110 ] satisfies price > max(100, 50, 10)")).isEqualTo(Boolean.TRUE); } @Test void basic_function_invocation() { - assertThat(parseCompileEvaluate("max(1, 2, 3)")).isEqualTo(new BigDecimal(3)); + assertThat(parseCodegenCompileEvaluate("max(1, 2, 3)")).isEqualTo(new BigDecimal(3)); } @Test void basic_function_definition() { - assertThat(parseCompileEvaluate("function (a, b) a + b")).isInstanceOf(CustomFEELFunction.class); - assertThat(parseCompileEvaluate("{ s : function (a, b) a + b, x : 1, y : 2, r : s(x,y) }.r")).isEqualTo(new BigDecimal(3)); + assertThat(parseCodegenCompileEvaluate("function (a, b) a + b")).isInstanceOf(CustomFEELFunction.class); + assertThat(parseCodegenCompileEvaluate("{ s : function (a, b) a + b, x : 1, y : 2, r : s(x,y) }.r")).isEqualTo(new BigDecimal(3)); } @Test void named_function_invocation() { - assertThat(parseCompileEvaluate("substring(start position: 2, string: \"FOOBAR\")")).isEqualTo("OOBAR"); - assertThat(parseCompileEvaluate("ceiling( n : 1.5 )")).isEqualTo(new BigDecimal("2")); + assertThat(parseCodegenCompileEvaluate("substring(start position: 2, string: \"FOOBAR\")")).isEqualTo("OOBAR"); + assertThat(parseCodegenCompileEvaluate("ceiling( n : 1.5 )")).isEqualTo(new BigDecimal("2")); } @Test void misc_from_original_feelinterpreted_test_suite() { - assertThat(parseCompileEvaluate("if null then \"foo\" else \"bar\"")).isEqualTo("bar"); - assertThat(parseCompileEvaluate("{ hello world : function() \"Hello World!\", message : hello world() }.message")).isEqualTo("Hello World!"); - assertThat(parseCompileEvaluate("1 + if true then 1 else 2")).isEqualTo(new BigDecimal("2")); - assertThat(parseCompileEvaluate("\"string with \\\"quotes\\\"\"")).isEqualTo("string with \"quotes\""); - assertThat(parseCompileEvaluate("date( -0105, 8, 2 )")).isEqualTo(LocalDate.of(-105, 8, 2)); - assertThat(parseCompileEvaluate("string(null)")).isNull(); - assertThat(parseCompileEvaluate("[ null ]")).asList().containsExactly(new Object[]{null}); - assertThat(parseCompileEvaluate("[ null, null ]")).asList().containsExactly(null, null); - assertThat(parseCompileEvaluate("[ null, 47, null ]")).asList().containsExactly(null, BigDecimal.valueOf(47), null); + assertThat(parseCodegenCompileEvaluate("if null then \"foo\" else \"bar\"")).isEqualTo("bar"); + assertThat(parseCodegenCompileEvaluate("{ hello world : function() \"Hello World!\", message : hello world() }.message")).isEqualTo("Hello World!"); + assertThat(parseCodegenCompileEvaluate("1 + if true then 1 else 2")).isEqualTo(new BigDecimal("2")); + assertThat(parseCodegenCompileEvaluate("\"string with \\\"quotes\\\"\"")).isEqualTo("string with \"quotes\""); + assertThat(parseCodegenCompileEvaluate("date( -0105, 8, 2 )")).isEqualTo(LocalDate.of(-105, 8, 2)); + assertThat(parseCodegenCompileEvaluate("string(null)")).isNull(); + assertThat(parseCodegenCompileEvaluate("[ null ]")).asList().containsExactly(new Object[]{null}); + assertThat(parseCodegenCompileEvaluate("[ null, null ]")).asList().containsExactly(null, null); + assertThat(parseCodegenCompileEvaluate("[ null, 47, null ]")).asList().containsExactly(null, BigDecimal.valueOf(47), null); } @Test void benchmark_feel_expressions() { - assertThat(parseCompileEvaluate("{ full name: { first name: \"John\", last name: \"Doe\" } }.full name.last name")).isEqualTo("Doe"); - assertThat(parseCompileEvaluate("some price in [ 80, 11, 110 ] satisfies price > 100")).isEqualTo(Boolean.TRUE); - assertThat(parseCompileEvaluate("every price in [ 80, 11, 90 ] satisfies price > 10")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("{ full name: { first name: \"John\", last name: \"Doe\" } }.full name.last name")).isEqualTo("Doe"); + assertThat(parseCodegenCompileEvaluate("some price in [ 80, 11, 110 ] satisfies price > 100")).isEqualTo(Boolean.TRUE); + assertThat(parseCodegenCompileEvaluate("every price in [ 80, 11, 90 ] satisfies price > 10")).isEqualTo(Boolean.TRUE); } @Test void context_expression() { - assertThat(parseCompileEvaluate("{}")).isEqualTo(Collections.emptyMap()); - assertThat(parseCompileEvaluate("{ }")).isEqualTo(Collections.emptyMap()); - assertThat(parseCompileEvaluate("{ a : 1 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)))); - assertThat(parseCompileEvaluate("{ \"a\" : 1 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)))); - assertThat(parseCompileEvaluate("{ \" a\" : 1 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)))); // Demonstrating a bad practice. - assertThat(parseCompileEvaluate("{ a : 1, b : 2, c : 3 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)), entry("b", new BigDecimal(2)), entry("c", new BigDecimal(3)))); - assertThat(parseCompileEvaluate("{ a : 1, a name : \"John Doe\" }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)), entry("a name", "John Doe"))); + assertThat(parseCodegenCompileEvaluate("{}")).isEqualTo(Collections.emptyMap()); + assertThat(parseCodegenCompileEvaluate("{ }")).isEqualTo(Collections.emptyMap()); + assertThat(parseCodegenCompileEvaluate("{ a : 1 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)))); + assertThat(parseCodegenCompileEvaluate("{ \"a\" : 1 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)))); + assertThat(parseCodegenCompileEvaluate("{ \" a\" : 1 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)))); // Demonstrating a bad practice. + assertThat(parseCodegenCompileEvaluate("{ a : 1, b : 2, c : 3 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)), entry("b", new BigDecimal(2)), entry("c", new BigDecimal(3)))); + assertThat(parseCodegenCompileEvaluate("{ a : 1, a name : \"John Doe\" }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)), entry("a name", "John Doe"))); - assertThat(parseCompileEvaluate("{ a : 1, b : a }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)), entry("b", new BigDecimal(1)))); + assertThat(parseCodegenCompileEvaluate("{ a : 1, b : a }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)), entry("b", new BigDecimal(1)))); } /** @@ -336,7 +357,7 @@ void contextWithMultipleEntries() { String inputExpression = "{ \"a string key\" : 10," + "\n" + " a non-string key : 11," + "\n" + " a key.with + /' odd chars : 12 }"; - assertThat(parseCompileEvaluate(inputExpression)).isEqualTo(mapOf(entry("a string key", new BigDecimal(10)), entry("a non-string key", new BigDecimal(11)), entry("a key.with + /' odd chars", new BigDecimal(12)))); + assertThat(parseCodegenCompileEvaluate(inputExpression)).isEqualTo(mapOf(entry("a string key", new BigDecimal(10)), entry("a non-string key", new BigDecimal(11)), entry("a key.with + /' odd chars", new BigDecimal(12)))); } /** @@ -356,8 +377,8 @@ void nestedContexts() { + " xxx: last + name" + "\n" + " } " + "\n" + "}"; - assertThat(parseCompileEvaluate(inputExpression)).isEqualTo(mapOf(entry("a value", new BigDecimal(10)), - entry("an applicant", mapOf(entry("first name", "Edson"), + assertThat(parseCodegenCompileEvaluate(inputExpression)).isEqualTo(mapOf(entry("a value", new BigDecimal(10)), + entry("an applicant", mapOf(entry("first name", "Edson"), entry("last + name", "Tirelli"), entry("full name", "EdsonTirelli"), entry("address", mapOf(entry("street", "55 broadway st"), @@ -378,15 +399,15 @@ void nestedContexts2() { " }, \n" + " street : an applicant.home address.street name \n" + "} "; - assertThat(parseCompileEvaluate(complexContext)).isEqualTo(mapOf(entry("an applicant", mapOf(entry("home address", mapOf(entry("street name", "broadway st"), - entry("city", "New York"))))), - entry("street", "broadway st"))); + assertThat(parseCodegenCompileEvaluate(complexContext)).isEqualTo(mapOf(entry("an applicant", mapOf(entry("home address", mapOf(entry("street name", "broadway st"), + entry("city", "New York"))))), + entry("street", "broadway st"))); } @Test void nameReference() { String inputExpression = "someSimpleName"; - CompiledFEELExpression nameRef = parse( inputExpression, mapOf( entry("someSimpleName", BuiltInType.STRING) ) ); + CompiledFEELExpression nameRef = parseCodegen(inputExpression, mapOf(entry("someSimpleName", BuiltInType.STRING) ) ); LOG.debug("{}", nameRef); EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); @@ -401,7 +422,7 @@ void nameReference() { void qualifiedName() { String inputExpression = "My Person.Full Name"; Type personType = new MapBackedType("Person", mapOf( entry("Full Name", BuiltInType.STRING), entry("Age", BuiltInType.NUMBER) ) ); - CompiledFEELExpression qualRef = parse( inputExpression, mapOf( entry("My Person", personType) ) ); + CompiledFEELExpression qualRef = parseCodegen(inputExpression, mapOf(entry("My Person", personType) ) ); LOG.debug("{}", qualRef); EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); @@ -412,7 +433,7 @@ void qualifiedName() { assertThat(result).isEqualTo("John Doe" ); // check number coercion for qualified name - CompiledFEELExpression personAgeExpression = parse("My Person.Age", mapOf(entry("My Person", personType))); + CompiledFEELExpression personAgeExpression = parseCodegen("My Person.Age", mapOf(entry("My Person", personType))); LOG.debug("{}", personAgeExpression); Object resultPersonAge = personAgeExpression.apply(context); // Please notice input variable in context is a Map containing and entry value for int 47. @@ -432,7 +453,7 @@ public String getFullName() { void qualifiedName2() { String inputExpression = "My Person.Full Name"; Type personType = JavaBackedType.of(MyPerson.class); - CompiledFEELExpression qualRef = parse( inputExpression, mapOf( entry("My Person", personType) ) ); + CompiledFEELExpression qualRef = parseCodegen(inputExpression, mapOf(entry("My Person", personType) ) ); LOG.debug("{}", qualRef); EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); @@ -447,7 +468,7 @@ void qualifiedName2() { void qualifiedName3() { String inputExpression = "a date.year"; Type dateType = BuiltInType.DATE; - CompiledFEELExpression qualRef = parse(inputExpression, mapOf(entry("a date", dateType))); + CompiledFEELExpression qualRef = parseCodegen(inputExpression, mapOf(entry("a date", dateType))); LOG.debug("{}", qualRef); EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/ForExpressionNodeTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/ForExpressionNodeTest.java new file mode 100644 index 00000000000..290abe5132f --- /dev/null +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/ForExpressionNodeTest.java @@ -0,0 +1,89 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.dmn.feel.lang.ast; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.kie.dmn.feel.codegen.feel11.CodegenTestUtil; +import org.kie.dmn.feel.lang.Type; +import org.kie.dmn.feel.lang.types.BuiltInType; + +import static org.assertj.core.api.Assertions.assertThat; + +class ForExpressionNodeTest { + + @Test + void evaluateSimpleArray() { + IterationContextNode x = getIterationContextNode("x", getListNode("[ 1, 2, 3, 4 ]", Arrays.asList("1", "2", "3", "4")), "x in [ 1, 2, 3, 4 ]"); + IterationContextNode y = getIterationContextNode("y", getNameRefNode(BuiltInType.UNKNOWN, "x"), "y in x"); + ForExpressionNode forExpressionNode = new ForExpressionNode(Arrays.asList(x, y), getNameRefNode(BuiltInType.UNKNOWN, "y"), "for x in [ 1, 2, 3, 4 ], y in x return y"); + Object retrieved = forExpressionNode.evaluate(CodegenTestUtil.newEmptyEvaluationContext()); + assertThat(retrieved).isInstanceOf(List.class).asList(). + containsExactly(BigDecimal.ONE, BigDecimal.valueOf(2), BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + } + + @Test + void evaluateNestedArray() { + Map> firstIterationContext = new LinkedHashMap<>(); + firstIterationContext.put("1, 2", Arrays.asList("1", "2")); + firstIterationContext.put("3, 4", Arrays.asList("3", "4")); + IterationContextNode x = getIterationContextNode("x", getNestedListNode("[ [1, 2], [3, 4] ]", firstIterationContext), "x in [ [1, 2], [3, 4] ]"); + IterationContextNode y = getIterationContextNode("y", getNameRefNode(BuiltInType.UNKNOWN, "x"), "y in x"); + ForExpressionNode forExpressionNode = new ForExpressionNode(Arrays.asList(x, y), getNameRefNode(BuiltInType.UNKNOWN, "y"), "for x in [ [1, 2], [3, 4] ], y in x return y"); + Object retrieved = forExpressionNode.evaluate(CodegenTestUtil.newEmptyEvaluationContext()); + assertThat(retrieved).isInstanceOf(List.class).asList(). + containsExactly(BigDecimal.ONE, BigDecimal.valueOf(2), BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + + } + + private IterationContextNode getIterationContextNode(String variableName, BaseNode expression, String text) { + return new IterationContextNode(getNameDefNode(variableName), expression, null, text); + } + + private NameDefNode getNameDefNode(String text) { + return new NameDefNode(Collections.singletonList(text), null, text); + } + + private NameRefNode getNameRefNode(Type type, String text) { + return new NameRefNode(type, text); + } + + private ListNode getNestedListNode(String text, Map> values) { + List elements = values.entrySet() + .stream() + .map(entry -> getListNode(entry.getKey(), entry.getValue())) + .map(BaseNode.class::cast) + .toList(); + return new ListNode(elements, text); + } + + private ListNode getListNode(String text, List values) { + List elements = values.stream() + .map(value -> new NumberNode(new BigDecimal(value), value)) + .map(BaseNode.class::cast) + .toList(); + return new ListNode(elements, text); + } +} \ No newline at end of file diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELCompilerTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELCompilerTest.java new file mode 100644 index 00000000000..80502839d79 --- /dev/null +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELCompilerTest.java @@ -0,0 +1,484 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.dmn.feel.runtime; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.kie.dmn.feel.codegen.feel11.CodegenTestUtil; +import org.kie.dmn.feel.codegen.feel11.CompiledFEELExpression; +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.FEELProperty; +import org.kie.dmn.feel.lang.Type; +import org.kie.dmn.feel.lang.impl.JavaBackedType; +import org.kie.dmn.feel.lang.impl.MapBackedType; +import org.kie.dmn.feel.lang.types.BuiltInType; +import org.kie.dmn.feel.parser.feel11.FEELParserTest; +import org.kie.dmn.feel.runtime.functions.CustomFEELFunction; +import org.kie.dmn.feel.util.CompilerUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.kie.dmn.feel.util.CompilerUtils.parseInterpreted; +import static org.kie.dmn.feel.util.CompilerUtils.parseInterpretedCompileEvaluate; +import static org.kie.dmn.feel.util.DynamicTypeUtils.entry; +import static org.kie.dmn.feel.util.DynamicTypeUtils.mapOf; + +public class FEELCompilerTest { + + public static final Logger LOG = LoggerFactory.getLogger(FEELCompilerTest.class); + + + @Test + void feel_number() { + assertThat(parseInterpretedCompileEvaluate("10")).isEqualTo(BigDecimal.valueOf(10)); + } + + @Test + void feel_negative_number() { + assertThat(parseInterpretedCompileEvaluate("-10")).isEqualTo(BigDecimal.valueOf(-10)); + } + + @Test + void feel_drools_2143() { + // DROOLS-2143: Allow ''--1' expression as per FEEL grammar rule 26 + assertThat(parseInterpretedCompileEvaluate("--10")).isEqualTo(BigDecimal.valueOf(10)); + assertThat(parseInterpretedCompileEvaluate("---10")).isEqualTo(BigDecimal.valueOf(-10)); + assertThat(parseInterpretedCompileEvaluate("+10")).isEqualTo(BigDecimal.valueOf(10)); + } + + @Test + void feel_boolean() { + assertThat(parseInterpretedCompileEvaluate("false")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("true")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("null")).isNull(); + } + + @Test + void feel_null() { + assertThat(parseInterpretedCompileEvaluate("null")).isNull(); + } + + @Test + void feel_string() { + assertThat(parseInterpretedCompileEvaluate("\"some string\"")).isEqualTo("some string" ); + } + + @Test + void primary_parens() { + assertThat(parseInterpretedCompileEvaluate("(\"some string\")")).isEqualTo("some string" ); + assertThat(parseInterpretedCompileEvaluate("(123)")).isEqualTo(BigDecimal.valueOf(123)); + assertThat(parseInterpretedCompileEvaluate("(-123)")).isEqualTo(BigDecimal.valueOf(-123)); + assertThat(parseInterpretedCompileEvaluate("-(123)")).isEqualTo(BigDecimal.valueOf(-123)); + assertThat(parseInterpretedCompileEvaluate("(false)")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("(true)")).isEqualTo(Boolean.TRUE); + } + + /** + * See {@link FEELTernaryLogicTest} + */ + @Test + void ternary_logic() { + assertThat(parseInterpretedCompileEvaluate("true and true")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("true and false")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("true and null")).isNull(); + assertThat(parseInterpretedCompileEvaluate("false and true")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("false and false")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("false and null")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("null and true")).isNull(); + assertThat(parseInterpretedCompileEvaluate("null and false")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("null and null")).isNull(); + assertThat(parseInterpretedCompileEvaluate("true or true")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("true or false")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("true or null")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("false or true")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("false or false")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("false or null")).isNull(); + assertThat(parseInterpretedCompileEvaluate("null or true")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("null or false")).isNull(); + assertThat(parseInterpretedCompileEvaluate("null or null")).isNull(); + // logical operator priority + assertThat(parseInterpretedCompileEvaluate("false and false or true")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("false and (false or true)")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("true or false and false")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("(true or false) and false")).isEqualTo(Boolean.FALSE); + } + + /** + * Partially from {@link FEELConditionsAndLoopsTest} + */ + @Test + void test_if() { + assertThat(parseInterpretedCompileEvaluate("if true then 15 else 5")).isEqualTo(BigDecimal.valueOf(15 )); + assertThat(parseInterpretedCompileEvaluate("if false then 15 else 5")).isEqualTo(BigDecimal.valueOf(5 )); + assertThat(parseInterpretedCompileEvaluate("if null then 15 else 5")).isEqualTo(BigDecimal.valueOf(5)); + assertThat(parseInterpretedCompileEvaluate("if \"hello\" then 15 else 5")).isEqualTo(BigDecimal.valueOf(5)); + } + + @Test + void additive_expression() { + assertThat(parseInterpretedCompileEvaluate("1 + 2")).isEqualTo(BigDecimal.valueOf(3 )); + assertThat(parseInterpretedCompileEvaluate("1 + null")).isNull(); + assertThat(parseInterpretedCompileEvaluate("1 - 2")).isEqualTo(BigDecimal.valueOf(-1 )); + assertThat(parseInterpretedCompileEvaluate("1 - null")).isNull(); + assertThat(parseInterpretedCompileEvaluate("\"Hello, \" + \"World\"")).isEqualTo("Hello, World"); + } + + @Test + void multiplicative_expression() { + assertThat(parseInterpretedCompileEvaluate("3 * 5")).isEqualTo(BigDecimal.valueOf(15)); + assertThat(parseInterpretedCompileEvaluate("3 * null")).isNull(); + assertThat(parseInterpretedCompileEvaluate("10 / 2")).isEqualTo(BigDecimal.valueOf(5)); + assertThat(parseInterpretedCompileEvaluate("10 / null")).isNull(); + } + + @Test + void exponentiation_expression() { + assertThat(parseInterpretedCompileEvaluate("3 ** 3")).isEqualTo(BigDecimal.valueOf(27)); + assertThat(parseInterpretedCompileEvaluate("3 ** null")).isNull(); + } + + @Test + void logical_negation_expression() { + // this is all invalid syntax + assertThat(parseInterpretedCompileEvaluate("not true")).isNull(); + assertThat(parseInterpretedCompileEvaluate("not false")).isNull(); + assertThat(parseInterpretedCompileEvaluate("not null")).isNull(); + assertThat(parseInterpretedCompileEvaluate("not 3")).isNull(); + } + + @Test + void list_expression() { + assertThat(parseInterpretedCompileEvaluate("[]")).asList().isEmpty(); + assertThat(parseInterpretedCompileEvaluate("[ ]")).asList().isEmpty(); + assertThat(parseInterpretedCompileEvaluate("[1]")).asList().containsExactly(BigDecimal.valueOf(1)); + assertThat(parseInterpretedCompileEvaluate("[1, 2,3]")).asList().containsExactly(BigDecimal.valueOf(1), BigDecimal.valueOf(2), BigDecimal.valueOf(3)); + } + + @Test + void instance_of_expression() { + assertThat(parseInterpretedCompileEvaluate("123 instance of number")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("\"ciao\" instance of number")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("123 instance of string")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("\"ciao\" instance of string")).isEqualTo(Boolean.TRUE); + } + + @Test + void between() { + assertThat(parseInterpretedCompileEvaluate("10 between 5 and 12")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("10 between 20 and 30")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("10 between 5 and \"foo\"")).isNull(); + assertThat(parseInterpretedCompileEvaluate("\"foo\" between 5 and 12")).isNull(); + assertThat(parseInterpretedCompileEvaluate("\"foo\" between \"bar\" and \"zap\"")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("\"foo\" between null and \"zap\"")).isNull(); + } + + @Test + void filter_path() { + // Filtering by index + assertThat(parseInterpretedCompileEvaluate("[\"a\", \"b\", \"c\"][1]")).isEqualTo("a"); + assertThat(parseInterpretedCompileEvaluate("[\"a\", \"b\", \"c\"][2]")).isEqualTo("b"); + assertThat(parseInterpretedCompileEvaluate("[\"a\", \"b\", \"c\"][3]")).isEqualTo("c"); + assertThat(parseInterpretedCompileEvaluate("[\"a\", \"b\", \"c\"][-1]")).isEqualTo("c"); + assertThat(parseInterpretedCompileEvaluate("[\"a\", \"b\", \"c\"][-2]")).isEqualTo("b"); + assertThat(parseInterpretedCompileEvaluate("[\"a\", \"b\", \"c\"][-3]")).isEqualTo("a"); + assertThat(parseInterpretedCompileEvaluate("[\"a\", \"b\", \"c\"][4]")).isNull(); + assertThat(parseInterpretedCompileEvaluate("[\"a\", \"b\", \"c\"][984]")).isNull(); + assertThat(parseInterpretedCompileEvaluate("[\"a\", \"b\", \"c\"][-4]")).isNull(); + assertThat(parseInterpretedCompileEvaluate("[\"a\", \"b\", \"c\"][-984]")).isNull(); + assertThat(parseInterpretedCompileEvaluate("\"a\"[1]")).isEqualTo("a"); + assertThat(parseInterpretedCompileEvaluate("\"a\"[2]")).isNull(); + assertThat(parseInterpretedCompileEvaluate("\"a\"[-1]")).isEqualTo("a"); + assertThat(parseInterpretedCompileEvaluate("\"a\"[-2]")).isNull(); + + // Filtering by boolean expression + assertThat(parseInterpretedCompileEvaluate("[1, 2, 3, 4][item = 4]")).asList().containsExactly(BigDecimal.valueOf(4)); + assertThat(parseInterpretedCompileEvaluate("[1, 2, 3, 4][item > 2]")).asList().containsExactly(BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + assertThat(parseInterpretedCompileEvaluate("[1, 2, 3, 4][item > 5]")).asList().isEmpty(); + assertThat(parseInterpretedCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ][x = 1]")).asList().containsExactly(mapOf(entry("x", new BigDecimal(1)), entry("y", new BigDecimal(2)))); + assertThat(parseInterpretedCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ][x > 1]")).asList().containsExactly(mapOf(entry("x", new BigDecimal(2)), entry("y", new BigDecimal(3)))); + assertThat(parseInterpretedCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ][x = 0]")).asList().isEmpty(); + } + + @Test + void filter_path_tricky1() { + CompiledFEELExpression nameRef = CompilerUtils.parseInterpreted("[ {x:1, y:2}, {x:2, y:3} ][x]"); + LOG.debug("{}", nameRef); + + EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); + context.setValue("x", 2); + Object result = nameRef.apply(context); + LOG.debug("{}", result); + + assertThat(result).isEqualTo(mapOf(entry("x", new BigDecimal(2)), entry("y", new BigDecimal(3)))); + } + + @Test + void filter_path_tricky2() { + CompiledFEELExpression nameRef = CompilerUtils.parseInterpreted("[ {x:1, y:2}, {x:2, y:3} ][x]"); + LOG.debug("{}", nameRef); + + EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); + context.setValue("x", false); + Object result = nameRef.apply(context); + LOG.debug("{}", result); + + assertThat(result).asList().isEmpty(); + } + + @Test + void filter_path_selection() { + // Selection + assertThat(parseInterpretedCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ].y")).asList().containsExactly(BigDecimal.valueOf(2), BigDecimal.valueOf(3)); + assertThat(parseInterpretedCompileEvaluate("[ {x:1, y:2}, {x:2} ].y")).asList().containsExactly(BigDecimal.valueOf(2), null); + assertThat(parseInterpretedCompileEvaluate("[ {x:1, y:2}, {x:2, y:3} ].z")).asList().containsExactly(null, null); + } + + @Test + void test_for() { + // for + Object parseCompileEvaluate = parseInterpretedCompileEvaluate("for x in [ 10, 20, 30 ], y in [ 1, 2, 3 ] return x * y"); + assertThat(parseCompileEvaluate).asList(). + containsExactly(BigDecimal.valueOf(10), BigDecimal.valueOf(20), BigDecimal.valueOf(30), BigDecimal.valueOf(20), BigDecimal.valueOf(40), BigDecimal.valueOf(60), BigDecimal.valueOf(30), BigDecimal.valueOf(60), BigDecimal.valueOf(90)); + + // normal: + assertThat(parseInterpretedCompileEvaluate("for x in [1, 2, 3] return x+1")).asList(). + containsExactly(BigDecimal.valueOf(2), BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + + // TODO in order to parse correctly the enhanced for loop it is required to configure the FEEL Profiles + } + + @Test + void test_nested_for() { + List firstExpected = Arrays.asList(BigDecimal.ONE, BigDecimal.valueOf(2)); + List secondExpected = Arrays.asList(BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + Object parseCompileEvaluate = parseInterpretedCompileEvaluate("for x in [ [1, 2], [3, 4] ] return x"); + assertThat(parseCompileEvaluate).asList(). + containsExactly(firstExpected, secondExpected); + parseCompileEvaluate = parseInterpretedCompileEvaluate("for x in [ [1,2], [3,4] ] return for y in x return y"); + assertThat(parseCompileEvaluate).asList(). + containsExactly(firstExpected, secondExpected); + parseCompileEvaluate = CompilerUtils.parseInterpretedCompileEvaluate("for x in [ 1, 2, 3, 4 ], y in x return y"); + assertThat(parseCompileEvaluate).asList(). + containsExactly(BigDecimal.ONE, BigDecimal.valueOf(2), BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + parseCompileEvaluate = parseInterpretedCompileEvaluate("for x in [ [1,2], [3,4] ], y in x return y"); + assertThat(parseCompileEvaluate).asList(). + containsExactly(BigDecimal.ONE, BigDecimal.valueOf(2), BigDecimal.valueOf(3), BigDecimal.valueOf(4)); + } + + @Test + void quantified_expressions() { + // quantified expressions + assertThat(parseInterpretedCompileEvaluate("some price in [ 80, 11, 110 ] satisfies price > 100")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("some price in [ 80, 11, 90 ] satisfies price > 100")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("some x in [ 5, 6, 7 ], y in [ 10, 11, 6 ] satisfies x > y")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("every price in [ 80, 11, 90 ] satisfies price > 10")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("every price in [ 80, 11, 90 ] satisfies price > 70")).isEqualTo(Boolean.FALSE); + assertThat(parseInterpretedCompileEvaluate("some x in [ 5, 6, 7 ], y in [ 10, 11, 12 ] satisfies x < y")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("some price in [ 80, 11, 110 ] satisfies price > max(100, 50, 10)")).isEqualTo(Boolean.TRUE); + } + + @Test + void basic_function_invocation() { + assertThat(parseInterpretedCompileEvaluate("max(1, 2, 3)")).isEqualTo(new BigDecimal(3)); + } + + @Test + void basic_function_definition() { + assertThat(parseInterpretedCompileEvaluate("function (a, b) a + b")).isInstanceOf(CustomFEELFunction.class); + assertThat(parseInterpretedCompileEvaluate("{ s : function (a, b) a + b, x : 1, y : 2, r : s(x,y) }.r")).isEqualTo(new BigDecimal(3)); + } + + @Test + void named_function_invocation() { + assertThat(parseInterpretedCompileEvaluate("substring(start position: 2, string: \"FOOBAR\")")).isEqualTo("OOBAR"); + assertThat(parseInterpretedCompileEvaluate("ceiling( n : 1.5 )")).isEqualTo(new BigDecimal("2")); + } + + @Test + void misc_from_original_feelinterpreted_test_suite() { + assertThat(parseInterpretedCompileEvaluate("if null then \"foo\" else \"bar\"")).isEqualTo("bar"); + assertThat(parseInterpretedCompileEvaluate("{ hello world : function() \"Hello World!\", message : hello world() }.message")).isEqualTo("Hello World!"); + assertThat(parseInterpretedCompileEvaluate("1 + if true then 1 else 2")).isEqualTo(new BigDecimal("2")); + assertThat(parseInterpretedCompileEvaluate("\"string with \\\"quotes\\\"\"")).isEqualTo("string with \"quotes\""); + assertThat(parseInterpretedCompileEvaluate("date( -0105, 8, 2 )")).isEqualTo(LocalDate.of(-105, 8, 2)); + assertThat(parseInterpretedCompileEvaluate("string(null)")).isNull(); + assertThat(parseInterpretedCompileEvaluate("[ null ]")).asList().containsExactly(new Object[]{null}); + assertThat(parseInterpretedCompileEvaluate("[ null, null ]")).asList().containsExactly(null, null); + assertThat(parseInterpretedCompileEvaluate("[ null, 47, null ]")).asList().containsExactly(null, BigDecimal.valueOf(47), null); + } + + @Test + void benchmark_feel_expressions() { + assertThat(parseInterpretedCompileEvaluate("{ full name: { first name: \"John\", last name: \"Doe\" } }.full name.last name")).isEqualTo("Doe"); + assertThat(parseInterpretedCompileEvaluate("some price in [ 80, 11, 110 ] satisfies price > 100")).isEqualTo(Boolean.TRUE); + assertThat(parseInterpretedCompileEvaluate("every price in [ 80, 11, 90 ] satisfies price > 10")).isEqualTo(Boolean.TRUE); + } + + @Test + void context_expression() { + assertThat(parseInterpretedCompileEvaluate("{}")).isEqualTo(Collections.emptyMap()); + assertThat(parseInterpretedCompileEvaluate("{ }")).isEqualTo(Collections.emptyMap()); + assertThat(parseInterpretedCompileEvaluate("{ a : 1 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)))); + assertThat(parseInterpretedCompileEvaluate("{ \"a\" : 1 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)))); + assertThat(parseInterpretedCompileEvaluate("{ \" a\" : 1 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)))); // Demonstrating a bad practice. + assertThat(parseInterpretedCompileEvaluate("{ a : 1, b : 2, c : 3 }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)), entry("b", new BigDecimal(2)), entry("c", new BigDecimal(3)))); + assertThat(parseInterpretedCompileEvaluate("{ a : 1, a name : \"John Doe\" }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)), entry("a name", "John Doe"))); + + assertThat(parseInterpretedCompileEvaluate("{ a : 1, b : a }")).isEqualTo(mapOf(entry("a", new BigDecimal(1)), entry("b", new BigDecimal(1)))); + } + + /** + * See {@link FEELParserTest} + */ + @Test + void contextWithMultipleEntries() { + String inputExpression = "{ \"a string key\" : 10," + "\n" + + " a non-string key : 11," + "\n" + + " a key.with + /' odd chars : 12 }"; + assertThat(parseInterpretedCompileEvaluate(inputExpression)).isEqualTo(mapOf(entry("a string key", new BigDecimal(10)), entry("a non-string key", new BigDecimal(11)), entry("a key.with + /' odd chars", new BigDecimal(12)))); + } + + /** + * See {@link FEELParserTest} + */ + @Test + void nestedContexts() { + String inputExpression = "{ a value : 10," + "\n" + + " an applicant : { " + "\n" + + " first name : \"Edson\", " + "\n" + + " last + name : \"Tirelli\", " + "\n" + + " full name : first name + last + name, " + "\n" + + " address : {" + "\n" + + " street : \"55 broadway st\"," + "\n" + + " city : \"New York\" " + "\n" + + " }, " + "\n" + + " xxx: last + name" + "\n" + + " } " + "\n" + + "}"; + assertThat(parseInterpretedCompileEvaluate(inputExpression)).isEqualTo(mapOf(entry("a value", new BigDecimal(10)), + entry("an applicant", mapOf(entry("first name", "Edson"), + entry("last + name", "Tirelli"), + entry("full name", "EdsonTirelli"), + entry("address", mapOf(entry("street", "55 broadway st"), + entry("city", "New York"))), + entry("xxx", "Tirelli"))))); + } + + /** + * See {@link FEELParserTest} + */ + @Test + void nestedContexts2() { + String complexContext = "{ an applicant : { \n" + + " home address : { \n" + + " street name: \"broadway st\", \n" + + " city : \"New York\" \n" + + " } \n" + + " }, \n" + + " street : an applicant.home address.street name \n" + + "} "; + assertThat(parseInterpretedCompileEvaluate(complexContext)).isEqualTo(mapOf(entry("an applicant", mapOf(entry("home address", mapOf(entry("street name", "broadway st"), + entry("city", "New York"))))), + entry("street", "broadway st"))); + } + + @Test + void nameReference() { + String inputExpression = "someSimpleName"; + CompiledFEELExpression nameRef = parseInterpreted(inputExpression, mapOf(entry("someSimpleName", BuiltInType.STRING) ) ); + LOG.debug("{}", nameRef); + + EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); + context.setValue("someSimpleName", 123L); + Object result = nameRef.apply(context); + LOG.debug("{}", result); + + assertThat(result).isEqualTo(BigDecimal.valueOf(123)); + } + + @Test + void qualifiedName() { + String inputExpression = "My Person.Full Name"; + Type personType = new MapBackedType("Person", mapOf( entry("Full Name", BuiltInType.STRING), entry("Age", BuiltInType.NUMBER) ) ); + CompiledFEELExpression qualRef = parseInterpreted(inputExpression, mapOf(entry("My Person", personType) ) ); + LOG.debug("{}", qualRef); + + EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); + context.setValue("My Person", mapOf( entry("Full Name", "John Doe"), entry("Age", 47) )); + Object result = qualRef.apply(context); + LOG.debug("{}", result); + + assertThat(result).isEqualTo("John Doe" ); + + // check number coercion for qualified name + CompiledFEELExpression personAgeExpression = parseInterpreted("My Person.Age", mapOf(entry("My Person", personType))); + LOG.debug("{}", personAgeExpression); + + Object resultPersonAge = personAgeExpression.apply(context); // Please notice input variable in context is a Map containing and entry value for int 47. + LOG.debug("{}", resultPersonAge); + + assertThat(resultPersonAge).isEqualTo(BigDecimal.valueOf(47)); + } + + public static class MyPerson { + @FEELProperty("Full Name") + public String getFullName() { + return "John Doe"; + } + } + + @Test + void qualifiedName2() { + String inputExpression = "My Person.Full Name"; + Type personType = JavaBackedType.of(MyPerson.class); + CompiledFEELExpression qualRef = parseInterpreted(inputExpression, mapOf(entry("My Person", personType) ) ); + LOG.debug("{}", qualRef); + + EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); + context.setValue("My Person", new MyPerson()); + Object result = qualRef.apply(context); + LOG.debug("{}", result); + + assertThat(result).isEqualTo("John Doe" ); + } + + @Test + void qualifiedName3() { + String inputExpression = "a date.year"; + Type dateType = BuiltInType.DATE; + CompiledFEELExpression qualRef = parseInterpreted(inputExpression, mapOf(entry("a date", dateType))); + LOG.debug("{}", qualRef); + + EvaluationContext context = CodegenTestUtil.newEmptyEvaluationContext(); + context.setValue("a date", LocalDate.of(2016, 8, 2)); + Object result = qualRef.apply(context); + LOG.debug("{}", result); + + assertThat(result).isEqualTo(BigDecimal.valueOf(2016)); + } + + + +} diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ListReplaceFunctionTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ListReplaceFunctionTest.java index 72dfd91742c..83091ae3065 100644 --- a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ListReplaceFunctionTest.java +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ListReplaceFunctionTest.java @@ -30,7 +30,7 @@ import org.kie.dmn.feel.runtime.events.InvalidParametersEvent; import static org.assertj.core.api.Assertions.assertThat; -import static org.kie.dmn.feel.util.CompilerUtils.parseCompileEvaluate; +import static org.kie.dmn.feel.util.CompilerUtils.parseCodegenCompileEvaluate; class ListReplaceFunctionTest { @@ -101,7 +101,7 @@ void invokeMatchNull() { void invokeMatchInvalid() { List list = Arrays.asList(2, 4, 7, 8); String validMatchFunction = "function(item, newItem) item + newItem"; - Object expressionObject = parseCompileEvaluate(validMatchFunction); + Object expressionObject = parseCodegenCompileEvaluate(validMatchFunction); assertThat(expressionObject).isInstanceOf(AbstractCustomFEELFunction.class); FunctionTestUtil.assertResultError(listReplaceFunction.invoke(list, (AbstractCustomFEELFunction)expressionObject, 3), InvalidParametersEvent.class); } @@ -112,7 +112,7 @@ void invokeReplaceByMatchWithNull() { List expected = new ArrayList<>(list); expected.set(1, null); String validMatchFunction = "function(item, newItem) item = \"Element-1\""; - Object expressionObject = parseCompileEvaluate(validMatchFunction); + Object expressionObject = parseCodegenCompileEvaluate(validMatchFunction); assertThat(expressionObject).isInstanceOf(AbstractCustomFEELFunction.class); FunctionTestUtil.assertResult(listReplaceFunction.invoke(list, (AbstractCustomFEELFunction)expressionObject, null), expected); } @@ -120,7 +120,7 @@ void invokeReplaceByMatchWithNull() { @Test void invokeReplaceByMatchWithNotNull() { String validMatchFunction = "function(item, newItem) item < newItem"; - Object expressionObject = parseCompileEvaluate(validMatchFunction); + Object expressionObject = parseCodegenCompileEvaluate(validMatchFunction); assertThat(expressionObject).isInstanceOf(AbstractCustomFEELFunction.class); List list = Arrays.asList(BigDecimal.valueOf(2), BigDecimal.valueOf(4), BigDecimal.valueOf(7), BigDecimal.valueOf(8)); List expected = new ArrayList<>(list); diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CompilerUtils.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CompilerUtils.java index b56bd81798c..c201ec39ca3 100644 --- a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CompilerUtils.java +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CompilerUtils.java @@ -31,6 +31,8 @@ import org.kie.dmn.feel.lang.EvaluationContext; import org.kie.dmn.feel.lang.Type; import org.kie.dmn.feel.lang.ast.BaseNode; +import org.kie.dmn.feel.lang.impl.CompiledExpressionImpl; +import org.kie.dmn.feel.lang.impl.InterpretedExecutableExpression; 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; @@ -43,21 +45,17 @@ public class CompilerUtils { private static final String TEMPLATE_RESOURCE = "/TemplateCompiledFEELExpression.java"; private static final String TEMPLATE_CLASS = "TemplateCompiledFEELExpression"; - public static Object parseCompileEvaluate(String feelLiteralExpression) { - CompiledFEELExpression compiledExpression = parse( feelLiteralExpression ); - LOG.debug("{}", compiledExpression); - - EvaluationContext emptyContext = CodegenTestUtil.newEmptyEvaluationContext(); - Object result = compiledExpression.apply(emptyContext); - LOG.debug("{}", result); - return result; + public static Object parseCodegenCompileEvaluate(String feelLiteralExpression) { + LOG.debug("{}", feelLiteralExpression); + CompiledFEELExpression compiledExpression = parseCodegen(feelLiteralExpression ); + return evaluate(compiledExpression); } - public static CompiledFEELExpression parse(String input) { - return parse(input, Collections.emptyMap() ); + public static CompiledFEELExpression parseCodegen(String input) { + return parseCodegen(input, Collections.emptyMap() ); } - public static CompiledFEELExpression parse(String input, Map inputTypes) { + public static CompiledFEELExpression parseCodegen(String input, Map inputTypes) { FEEL_1_1Parser parser = FEELParser.parse(null, input, inputTypes, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList(), null); ParseTree tree = parser.compilation_unit(); @@ -80,4 +78,32 @@ public static CompiledFEELExpression parse(String input, Map input return compilerBytecodeLoader.compileUnit(packageName, TEMPLATE_CLASS, cu); } + public static Object parseInterpretedCompileEvaluate(String feelLiteralExpression) { + LOG.debug("{}", feelLiteralExpression); + CompiledFEELExpression compiledExpression = parseInterpreted(feelLiteralExpression ); + return evaluate(compiledExpression); + } + + public static CompiledFEELExpression parseInterpreted(String input) { + return parseInterpreted(input, Collections.emptyMap() ); + } + + public static CompiledFEELExpression parseInterpreted(String input, Map inputTypes) { + FEEL_1_1Parser parser = FEELParser.parse(null, input, inputTypes, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList(), null); + + ParseTree tree = parser.compilation_unit(); + + ASTBuilderVisitor v = new ASTBuilderVisitor(inputTypes, null); + BaseNode ast = tree.accept(v); + return new InterpretedExecutableExpression(new CompiledExpressionImpl(ast)); + } + + public static Object evaluate(CompiledFEELExpression compiledExpression) { + LOG.debug("{}", compiledExpression); + EvaluationContext emptyContext = CodegenTestUtil.newEmptyEvaluationContext(); + Object result = compiledExpression.apply(emptyContext); + LOG.debug("{}", result); + return result; + } + } \ No newline at end of file