This is an automated email from the ASF dual-hosted git repository.
gitgabrio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git
The following commit(s) were added to refs/heads/main by this push:
new 2b991ff859 [incubator-kie-issues#1350] Refactor ForExpressionNode to
allow dynamically-generated iteration contexts (#6041)
2b991ff859 is described below
commit 2b991ff859190ed8dec5edfdb284b7dcdebc5f3c
Author: Gabriele Cardosi <[email protected]>
AuthorDate: Mon Aug 12 14:45:01 2024 +0200
[incubator-kie-issues#1350] Refactor ForExpressionNode to allow
dynamically-generated iteration contexts (#6041)
* [incubator-kie-issues#1350] Refactor ForExpressionNode to allow
dynamically-generated iteration contexts
* [incubator-kie-issues#1350] Fix headers
* [incubator-kie-issues#1350] Minor fix
---------
Co-authored-by: Gabriele-Cardosi <[email protected]>
---
.../kie/dmn/feel/lang/ast/ForExpressionNode.java | 94 ++--
.../dmn/feel/lang/ast/IterationContextNode.java | 7 +
.../ast/forexpressioniterators/ForIteration.java | 8 +
.../dmn/feel/lang/impl/EvaluationContextImpl.java | 7 +
.../feel/codegen/feel11/DirectCompilerTest.java | 307 +++++++------
.../dmn/feel/lang/ast/ForExpressionNodeTest.java | 89 ++++
.../org/kie/dmn/feel/runtime/FEELCompilerTest.java | 484 +++++++++++++++++++++
.../runtime/functions/ListReplaceFunctionTest.java | 8 +-
.../java/org/kie/dmn/feel/util/CompilerUtils.java | 48 +-
9 files changed, 842 insertions(+), 210 deletions(-)
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 f25aa0ca56..1229c35445 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.EvaluationContext;
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 @@ import static
org.kie.dmn.feel.lang.ast.forexpressioniterators.ForIterationUtils
public class ForExpressionNode
extends BaseNode {
+ private static final Logger LOG =
LoggerFactory.getLogger(ForExpressionNode.class);
private List<IterationContextNode> iterationContexts;
private BaseNode expression;
@@ -74,16 +77,11 @@ public class ForExpressionNode
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<Object> 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 class ForExpressionNode
}
}
- 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<Object>
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 class ForExpressionNode
return BuiltInType.LIST;
}
- private ForIteration[] initializeContexts(EvaluationContext ctx,
List<IterationContextNode> 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 class ForExpressionNode
public <T> T accept(Visitor<T> 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 70cfa00c9f..0c11f2e71a 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 @@ package org.kie.dmn.feel.lang.ast;
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 class IterationContextNode
}
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 43f4f576c0..bd8719f2ef 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 class ForIteration {
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 47759d0aef..8e1e75fa77 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.api.feel.runtime.events.FEELEventListener;
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<ExecutionFrame> stack;
private DMNRuntime dmnRuntime;
@@ -102,6 +106,7 @@ public class EvaluationContextImpl implements
EvaluationContext {
@Override
public void enterFrame() {
+ LOG.trace("Creating new head element in stack");
push( new ExecutionFrameImpl( peek() /*, symbols, scope*/ ) );
}
@@ -111,11 +116,13 @@ public class EvaluationContextImpl implements
EvaluationContext {
@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 8cb00e1655..9231a8b1d5 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 @@ package org.kie.dmn.feel.codegen.feel11;
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.parser.feel11.FEELParserTest;
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 @@ public class DirectCompilerTest {
*/
@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 @@ public class DirectCompilerTest {
*/
@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 @@ public class DirectCompilerTest {
@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 @@ public class DirectCompilerTest {
@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<BigDecimal> firstExpected = Arrays.asList(BigDecimal.ONE,
BigDecimal.valueOf(2));
+ List<BigDecimal> 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 @@ public class DirectCompilerTest {
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 @@ public class DirectCompilerTest {
+ " 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 @@ public class DirectCompilerTest {
" },
\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 @@ public class DirectCompilerTest {
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 @@ public class DirectCompilerTest {
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 class DirectCompilerTest {
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 @@ public class DirectCompilerTest {
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 0000000000..290abe5132
--- /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<String, List<String>> 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<String, List<String>>
values) {
+ List<BaseNode> 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<String> values) {
+ List<BaseNode> 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 0000000000..80502839d7
--- /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<BigDecimal> firstExpected = Arrays.asList(BigDecimal.ONE,
BigDecimal.valueOf(2));
+ List<BigDecimal> 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 72dfd91742..83091ae306 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.junit.jupiter.api.Test;
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 @@ class ListReplaceFunctionTest {
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 @@ class ListReplaceFunctionTest {
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 @@ class ListReplaceFunctionTest {
@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 b56bd81798..c201ec39ca 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.codegen.feel11.CompilerBytecodeLoader;
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<String, Type>
inputTypes) {
+ public static CompiledFEELExpression parseCodegen(String input,
Map<String, Type> 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 class CompilerUtils {
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<String, Type> 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
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]