This is an automated email from the ASF dual-hosted git repository.
yamer 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 6e88d9ed63 [kie-issues#2016] DMN 1.6: New Descendant expression (#6388)
6e88d9ed63 is described below
commit 6e88d9ed63ee01ba3a549d204f9fcc60f520a4a6
Author: Yeser Amer <[email protected]>
AuthorDate: Thu Jul 10 09:24:45 2025 +0200
[kie-issues#2016] DMN 1.6: New Descendant expression (#6388)
* First draft
* minor
* Change Request
* Change Request
---
.../org/kie/dmn/feel/parser/feel11/FEEL_1_1.g4 | 16 ++-
.../dmn/feel/codegen/feel11/ASTCompilerHelper.java | 12 +-
.../feel/codegen/feel11/ASTCompilerVisitor.java | 7 ++
.../feel/codegen/feel11/DMNCodegenConstants.java | 2 +
.../kie/dmn/feel/lang/ast/ASTBuilderFactory.java | 4 +
.../feel/lang/ast/DescendantExpressionNode.java | 122 +++++++++++++++++++++
.../java/org/kie/dmn/feel/lang/ast/Visitor.java | 1 +
.../feel/lang/ast/visitor/DefaultedVisitor.java | 6 +
.../dmn/feel/parser/feel11/ASTBuilderVisitor.java | 17 ++-
.../kie/dmn/feel/parser/feel11/ParserHelper.java | 6 +-
.../src/main/java/org/kie/dmn/feel/util/Msg.java | 2 +
.../kie/dmn/feel/parser/feel11/FEELParserTest.java | 16 +++
.../org/kie/dmn/feel/runtime/BaseFEELTest.java | 6 +
.../kie/dmn/feel/runtime/FEELExpressionsTest.java | 16 +++
14 files changed, 218 insertions(+), 15 deletions(-)
diff --git
a/kie-dmn/kie-dmn-feel/src/main/antlr4/org/kie/dmn/feel/parser/feel11/FEEL_1_1.g4
b/kie-dmn/kie-dmn-feel/src/main/antlr4/org/kie/dmn/feel/parser/feel11/FEEL_1_1.g4
index 09eb8cb081..a267fa42e0 100644
---
a/kie-dmn/kie-dmn-feel/src/main/antlr4/org/kie/dmn/feel/parser/feel11/FEEL_1_1.g4
+++
b/kie-dmn/kie-dmn-feel/src/main/antlr4/org/kie/dmn/feel/parser/feel11/FEEL_1_1.g4
@@ -280,17 +280,22 @@ multiplicativeExpression
;
powerExpression
- : filterPathExpression #powExpressionUnary
- | powerExpression op=POW filterPathExpression #powExpression
+ : pathDescendantFilterExpression
#powExpressionUnary
+ | powerExpression op=POW pathDescendantFilterExpression #powExpression
;
-filterPathExpression
+// FEEL Grammar (2.g) (Path, Descendant, and Filter Expressions)
+pathDescendantFilterExpression
@init {
int count = 0;
}
: unaryExpression
- | n0=filterPathExpression LBRACK {helper.enableDynamicResolution();}
filter=expression {helper.disableDynamicResolution();} RBRACK
- | n1=filterPathExpression DOT {count = helper.fphStart($n1.ctx, this);
helper.enableDynamicResolution();} qualifiedName
{helper.disableDynamicResolution(); helper.fphEnd(count);}
+ // #50 Filter Expression
+ | n0=pathDescendantFilterExpression LBRACK
{helper.enableDynamicResolution();} filter=expression
{helper.disableDynamicResolution();} RBRACK
+ // #43 Path Expression
+ | n1=pathDescendantFilterExpression DOT {count =
helper.fphStart($n1.ctx, this); helper.enableDynamicResolution();}
qualifiedName {helper.disableDynamicResolution(); helper.fphEnd(count);}
+ // #68 Descendant Expression
+ | n2=pathDescendantFilterExpression SPREAD
{helper.enableDynamicResolution();} qualifiedName
{helper.disableDynamicResolution();}
;
unaryExpression
@@ -761,6 +766,7 @@ RBRACK : ']';
COMMA : ',';
ELIPSIS : '..';
DOT : '.';
+SPREAD : '...';
// Operators
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/ASTCompilerHelper.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/ASTCompilerHelper.java
index 4f0952a29d..06a8ab7945 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/ASTCompilerHelper.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/ASTCompilerHelper.java
@@ -50,6 +50,7 @@ import org.kie.dmn.feel.lang.ast.ContextEntryNode;
import org.kie.dmn.feel.lang.ast.ContextNode;
import org.kie.dmn.feel.lang.ast.ContextTypeNode;
import org.kie.dmn.feel.lang.ast.DashNode;
+import org.kie.dmn.feel.lang.ast.DescendantExpressionNode;
import org.kie.dmn.feel.lang.ast.FilterExpressionNode;
import org.kie.dmn.feel.lang.ast.ForExpressionNode;
import org.kie.dmn.feel.lang.ast.FormalParameterNode;
@@ -110,6 +111,7 @@ import static
org.kie.dmn.feel.codegen.feel11.DMNCodegenConstants.CONTEXTNODE_CT
import static
org.kie.dmn.feel.codegen.feel11.DMNCodegenConstants.CONTEXTTYPENODE_CT;
import static org.kie.dmn.feel.codegen.feel11.DMNCodegenConstants.CTYPENODE_CT;
import static org.kie.dmn.feel.codegen.feel11.DMNCodegenConstants.DASHNODE_CT;
+import static
org.kie.dmn.feel.codegen.feel11.DMNCodegenConstants.DESCENDANTEXPRESSIONNODE_CT;
import static
org.kie.dmn.feel.codegen.feel11.DMNCodegenConstants.DETERMINEOPERATOR_S;
import static
org.kie.dmn.feel.codegen.feel11.DMNCodegenConstants.FILTEREXPRESSIONNODE_CT;
import static
org.kie.dmn.feel.codegen.feel11.DMNCodegenConstants.FOREXPRESSIONNODE_CT;
@@ -154,7 +156,7 @@ import static
org.kie.dmn.feel.util.CodegenUtils.getVariableDeclaratorWithObject
public class ASTCompilerHelper {
- private static final Logger LOGGER =
LoggerFactory.getLogger(ASTCompilerVisitor.class);
+ private static final Logger LOGGER =
LoggerFactory.getLogger(ASTCompilerHelper.class);
private static final String EXTENDED_FUNCTION_PACKAGE =
"org.kie.dmn.feel.runtime.functions.extended";
private final ASTCompilerVisitor astCompilerVisitor;
private final BlockStmt toPopulate;
@@ -248,6 +250,14 @@ public class ASTCompilerHelper {
return addVariableDeclaratorWithObjectCreation(DASHNODE_CT,
NodeList.nodeList(), n.getText());
}
+ public BlockStmt add(DescendantExpressionNode n) {
+ Expression expressionExpression = getNodeExpression(n.getExpression());
+ Expression nameExpression = getNodeExpression(n.getName());
+ return
addVariableDeclaratorWithObjectCreation(DESCENDANTEXPRESSIONNODE_CT,
+ NodeList.nodeList(expressionExpression, nameExpression),
+ n.getText());
+ }
+
public BlockStmt add(ForExpressionNode n) {
List iterationContextsExpressions =
n.getIterationContexts().stream().map(elementNode ->
getNodeExpression(elementNode))
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/ASTCompilerVisitor.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/ASTCompilerVisitor.java
index 67dc620bf4..0b2c3f6cc5 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/ASTCompilerVisitor.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/ASTCompilerVisitor.java
@@ -28,6 +28,7 @@ import org.kie.dmn.feel.lang.ast.ContextEntryNode;
import org.kie.dmn.feel.lang.ast.ContextNode;
import org.kie.dmn.feel.lang.ast.ContextTypeNode;
import org.kie.dmn.feel.lang.ast.DashNode;
+import org.kie.dmn.feel.lang.ast.DescendantExpressionNode;
import org.kie.dmn.feel.lang.ast.FilterExpressionNode;
import org.kie.dmn.feel.lang.ast.ForExpressionNode;
import org.kie.dmn.feel.lang.ast.FormalParameterNode;
@@ -123,6 +124,12 @@ public class ASTCompilerVisitor implements
Visitor<BlockStmt> {
return compilerHelper.add(n);
}
+ @Override
+ public BlockStmt visit(DescendantExpressionNode n) {
+ LOGGER.trace("visit {}", n);
+ return compilerHelper.add(n);
+ }
+
@Override
public BlockStmt visit(ForExpressionNode n) {
LOGGER.trace("visit {}", n);
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/DMNCodegenConstants.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/DMNCodegenConstants.java
index dd5b169d48..37b269e80f 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/DMNCodegenConstants.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/codegen/feel11/DMNCodegenConstants.java
@@ -28,6 +28,7 @@ import org.kie.dmn.feel.lang.ast.ContextEntryNode;
import org.kie.dmn.feel.lang.ast.ContextNode;
import org.kie.dmn.feel.lang.ast.ContextTypeNode;
import org.kie.dmn.feel.lang.ast.DashNode;
+import org.kie.dmn.feel.lang.ast.DescendantExpressionNode;
import org.kie.dmn.feel.lang.ast.FilterExpressionNode;
import org.kie.dmn.feel.lang.ast.ForExpressionNode;
import org.kie.dmn.feel.lang.ast.FormalParameterNode;
@@ -120,6 +121,7 @@ public class DMNCodegenConstants {
public static final ClassOrInterfaceType CTYPENODE_CT =
parseClassOrInterfaceType(CTypeNode.class.getCanonicalName());
public static final ClassOrInterfaceType DASHNODE_CT =
parseClassOrInterfaceType(DashNode.class.getCanonicalName());
+ public static final ClassOrInterfaceType DESCENDANTEXPRESSIONNODE_CT =
parseClassOrInterfaceType(DescendantExpressionNode.class.getCanonicalName());
public static final ClassOrInterfaceType FILTEREXPRESSIONNODE_CT =
parseClassOrInterfaceType(FilterExpressionNode.class.getCanonicalName());
public static final ClassOrInterfaceType FOREXPRESSIONNODE_CT =
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ASTBuilderFactory.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ASTBuilderFactory.java
index 0674e0c3af..c102b2edc2 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ASTBuilderFactory.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ASTBuilderFactory.java
@@ -159,6 +159,10 @@ public class ASTBuilderFactory {
return new DashNode( ctx );
}
+ public static DescendantExpressionNode
newDescendantExpressionNode(ParserRuleContext ctx, BaseNode expr, BaseNode
name) {
+ return new DescendantExpressionNode( ctx, expr, name );
+ }
+
public static CTypeNode newCTypeNode(ParserRuleContext ctx, Type type) {
return new CTypeNode(ctx, type);
}
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/DescendantExpressionNode.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/DescendantExpressionNode.java
new file mode 100644
index 0000000000..025dee437b
--- /dev/null
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/DescendantExpressionNode.java
@@ -0,0 +1,122 @@
+/*
+ * 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 org.antlr.v4.runtime.ParserRuleContext;
+import org.kie.dmn.api.feel.runtime.events.FEELEvent;
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.util.Msg;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+
+public class DescendantExpressionNode extends BaseNode {
+
+ private BaseNode expression;
+ private BaseNode name;
+
+ public DescendantExpressionNode(ParserRuleContext ctx, BaseNode
expression, BaseNode name) {
+ super( ctx );
+ this.expression = expression;
+ this.name = name;
+ }
+
+ public DescendantExpressionNode(BaseNode expression, BaseNode name, String
text) {
+ this.expression = expression;
+ this.name = name;
+ this.setText(text);
+ }
+
+ public BaseNode getExpression() {
+ return expression;
+ }
+
+ public void setExpression(BaseNode expression) {
+ this.expression = expression;
+ }
+
+ public BaseNode getName() {
+ return name;
+ }
+
+ public void setName(BaseNode name) {
+ this.name = name;
+ }
+
+ @Override
+ public Object evaluate(EvaluationContext ctx) {
+ if (expression == null || expression.getText() == null ||
expression.getText().isEmpty()) {
+ ctx.notifyEvt(astEvent(FEELEvent.Severity.ERROR,
Msg.createMessage(Msg.IS_NULL, "expression")));
+ return null;
+ }
+ if (!(expression instanceof ContextNode)) {
+ ctx.notifyEvt(astEvent(FEELEvent.Severity.ERROR,
Msg.createMessage(Msg.ERROR_EVALUATING_DESCENDANT_EXPRESSION_NOT_CONTEXT,
expression.getText())));
+ return null;
+ }
+ if (name == null || name.getText() == null ||
name.getText().isEmpty()) {
+ ctx.notifyEvt(astEvent(FEELEvent.Severity.ERROR,
Msg.createMessage(Msg.IS_NULL, "name")));
+ return null;
+ }
+
+ try {
+ Object evaluatedExpression = this.expression.evaluate(ctx);
+ List<Object> results = new ArrayList<>();
+ Deque<Object> currentContextNestingLevel = new ArrayDeque<>();
+ currentContextNestingLevel.push(evaluatedExpression);
+
+ while (!currentContextNestingLevel.isEmpty()) {
+ Object current = currentContextNestingLevel.pop();
+
+ if (current instanceof Map<?, ?> map) {
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
+ Object key = entry.getKey();
+ Object value = entry.getValue();
+
+ if (name.getText().equals(key)) {
+ results.add(value);
+ }
+
+ if (value instanceof Map<?, ?>) {
+ currentContextNestingLevel.push(value);
+ }
+ }
+ }
+ }
+
+ return results;
+ } catch ( Exception e ) {
+ ctx.notifyEvt( astEvent(FEELEvent.Severity.ERROR,
Msg.createMessage(Msg.ERROR_EVALUATING_DESCENDANT_EXPRESSION,
expression.getText(), name.getText()), e) );
+ return null;
+ }
+ }
+
+ @Override
+ public ASTNode[] getChildrenNode() {
+ return new ASTNode[] { expression, name };
+ }
+
+ @Override
+ 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/Visitor.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/Visitor.java
index 1aaf70d9dc..dd02b84021 100644
--- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/Visitor.java
+++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/Visitor.java
@@ -28,6 +28,7 @@ public interface Visitor<T> {
T visit(ContextTypeNode n);
T visit(CTypeNode n);
T visit(DashNode n);
+ T visit(DescendantExpressionNode n);
T visit(FilterExpressionNode n);
T visit(ForExpressionNode n);
T visit(FormalParameterNode n);
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/visitor/DefaultedVisitor.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/visitor/DefaultedVisitor.java
index dc7172c710..0d79eaa875 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/visitor/DefaultedVisitor.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/visitor/DefaultedVisitor.java
@@ -27,6 +27,7 @@ import org.kie.dmn.feel.lang.ast.ContextEntryNode;
import org.kie.dmn.feel.lang.ast.ContextNode;
import org.kie.dmn.feel.lang.ast.ContextTypeNode;
import org.kie.dmn.feel.lang.ast.DashNode;
+import org.kie.dmn.feel.lang.ast.DescendantExpressionNode;
import org.kie.dmn.feel.lang.ast.FilterExpressionNode;
import org.kie.dmn.feel.lang.ast.ForExpressionNode;
import org.kie.dmn.feel.lang.ast.FormalParameterNode;
@@ -71,6 +72,11 @@ public abstract class DefaultedVisitor<T> implements
Visitor<T> {
return defaultVisit(n);
}
+ @Override
+ public T visit(DescendantExpressionNode n) {
+ return defaultVisit(n);
+ }
+
@Override
public T visit(BooleanNode n) {
return defaultVisit(n);
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/ASTBuilderVisitor.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/ASTBuilderVisitor.java
index 4222ad732b..10a22bc714 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/ASTBuilderVisitor.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/ASTBuilderVisitor.java
@@ -131,7 +131,7 @@ public class ASTBuilderVisitor
@Override
public BaseNode visitPowExpression(FEEL_1_1Parser.PowExpressionContext
ctx) {
BaseNode left = visit( ctx.powerExpression() );
- BaseNode right = visit( ctx.filterPathExpression() );
+ BaseNode right = visit( ctx.pathDescendantFilterExpression() );
String op = ctx.op.getText();
return ASTBuilderFactory.newInfixOpNode( ctx, left, op, right );
}
@@ -640,17 +640,22 @@ public class ASTBuilderVisitor
}
@Override
- public BaseNode
visitFilterPathExpression(FEEL_1_1Parser.FilterPathExpressionContext ctx) {
+ public BaseNode
visitPathDescendantFilterExpression(FEEL_1_1Parser.PathDescendantFilterExpressionContext
ctx) {
if( ctx.filter != null ) {
- BaseNode expr = visit( ctx.filterPathExpression() );
+ BaseNode expr = visit( ctx.pathDescendantFilterExpression() );
BaseNode filter = visit( ctx.filter );
expr = ASTBuilderFactory.newFilterExpressionNode( ctx, expr,
filter );
return expr;
- } else if( ctx.qualifiedName() != null ) {
- BaseNode expr = visit( ctx.filterPathExpression() );
+ } else if (ctx.n1 != null) {
+ BaseNode expr = visit( ctx.pathDescendantFilterExpression());
BaseNode path = visit( ctx.qualifiedName() );
return ASTBuilderFactory.newPathExpressionNode( ctx, expr, path );
- } else {
+ } else if (ctx.n2 != null) {
+ BaseNode expr = visit( ctx.pathDescendantFilterExpression() );
+ BaseNode path = visit( ctx.qualifiedName() );
+ return ASTBuilderFactory.newDescendantExpressionNode( ctx, expr,
path );
+ }
+ else {
return visit( ctx.unaryExpression() );
}
}
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/ParserHelper.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/ParserHelper.java
index d9d5ced34e..53062a1513 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/ParserHelper.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/ParserHelper.java
@@ -46,7 +46,7 @@ import org.kie.dmn.feel.lang.types.GenRangeType;
import org.kie.dmn.feel.lang.types.ScopeImpl;
import org.kie.dmn.feel.lang.types.SymbolTable;
import org.kie.dmn.feel.lang.types.VariableSymbol;
-import
org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser.FilterPathExpressionContext;
+import
org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser.PathDescendantFilterExpressionContext;
import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser.QualifiedNameContext;
import org.kie.dmn.feel.runtime.events.UnknownVariableErrorEvent;
import org.kie.dmn.feel.util.StringEvalHelper;
@@ -307,10 +307,10 @@ public class ParserHelper {
* a specific heuristic for scope retrieval for filterPathExpression
*/
public int fphStart(ParserRuleContext ctx, Parser parser) {
- if (!(ctx instanceof FEEL_1_1Parser.FilterPathExpressionContext)) { //
I expect in `var[1].name` for this param ctx=`var[1]` to be a
filterPathExpression
+ if (!(ctx instanceof
FEEL_1_1Parser.PathDescendantFilterExpressionContext)) { // I expect in
`var[1].name` for this param ctx=`var[1]` to be a filterPathExpression
return 0;
}
- FilterPathExpressionContext ctx0 =
(FEEL_1_1Parser.FilterPathExpressionContext) ctx;
+ PathDescendantFilterExpressionContext ctx0 =
(FEEL_1_1Parser.PathDescendantFilterExpressionContext) ctx;
boolean ctxSquared = ctx0.filter != null && ctx0.n0 != null;
if (!ctxSquared) { // I expect `var[1]` to be in the squared form
`...[...]`
return 0;
diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/Msg.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/Msg.java
index 885168af56..1011cdfc5a 100644
--- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/Msg.java
+++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/Msg.java
@@ -30,6 +30,8 @@ public final class Msg {
public static final Message0 CANNOT_BE_SIGNED = new Message0("Cannot sign
a value which is not a number");
public static final Message1 ERROR_ACCESSING_QUALIFIED_NAME = new
Message1("Error accessing qualified name: %s");
public static final Message2 ERROR_EVALUATING_PATH_EXPRESSION = new
Message2("Error evaluating path expression: %s. %s");
+ public static final Message2 ERROR_EVALUATING_DESCENDANT_EXPRESSION = new
Message2("Error evaluating descendant expression: %s. %s");
+ public static final Message1
ERROR_EVALUATING_DESCENDANT_EXPRESSION_NOT_CONTEXT = new Message1("Error
evaluating descendant expression, the given expression is not a context: %s.");
public static final Message0
VALUE_NULL_EXPR_NOT_NULL_AND_NOT_UNARY_TEST_EVALUATING_THIS_NODE_AS_FALSE = new
Message0("value == null, expr != null and not Unary test, Evaluating this node
as FALSE.");
public static final Message2
EXPRESSION_IS_RANGE_BUT_VALUE_IS_NOT_COMPARABLE = new Message2("Value '%s' is
not comparable with range '%s'");
public static final Message0 CONDITION_WAS_NOT_A_BOOLEAN = new
Message0("Condition was not a Boolean");
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/parser/feel11/FEELParserTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/parser/feel11/FEELParserTest.java
index 1ff2be7a6e..42c0e03455 100644
---
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/parser/feel11/FEELParserTest.java
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/parser/feel11/FEELParserTest.java
@@ -33,6 +33,7 @@ import org.kie.dmn.feel.lang.ast.BetweenNode;
import org.kie.dmn.feel.lang.ast.BooleanNode;
import org.kie.dmn.feel.lang.ast.ContextEntryNode;
import org.kie.dmn.feel.lang.ast.ContextNode;
+import org.kie.dmn.feel.lang.ast.DescendantExpressionNode;
import org.kie.dmn.feel.lang.ast.FilterExpressionNode;
import org.kie.dmn.feel.lang.ast.ForExpressionNode;
import org.kie.dmn.feel.lang.ast.FunctionDefNode;
@@ -1120,6 +1121,21 @@ public class FEELParserTest {
assertThat( filter.getFilter().getText()).isEqualTo( "x=1");
}
+ @Test
+ void descendantExpression() {
+ String inputExpression = "{a: { b: { b: 1 } } }...c";
+ BaseNode descendantBaseNode = parse( inputExpression );
+
+
assertThat(descendantBaseNode).isInstanceOf(DescendantExpressionNode.class);
+ assertThat(descendantBaseNode.getText()).isEqualTo(inputExpression);
+
+ DescendantExpressionNode descendantExpressionNode =
(DescendantExpressionNode) descendantBaseNode;
+
assertThat(descendantExpressionNode.getExpression()).isInstanceOf(ContextNode.class);
+
assertThat(descendantExpressionNode.getExpression().getText()).isEqualTo( "{a:
{ b: { b: 1 } } }");
+
assertThat(descendantExpressionNode.getName()).isInstanceOf(NameRefNode.class);
+ assertThat( descendantExpressionNode.getName().getText()).isEqualTo(
"c");
+ }
+
@Test
void functionInvocationNamedParams() {
String inputExpression = "my.test.Function( named parameter 1 : x+10,
named parameter 2 : \"foo\" )";
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/BaseFEELTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/BaseFEELTest.java
index 99796c1527..f9e2b4a0a9 100644
---
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/BaseFEELTest.java
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/BaseFEELTest.java
@@ -19,8 +19,10 @@
package org.kie.dmn.feel.runtime;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import org.assertj.core.api.InstanceOfAssertFactories;
import org.assertj.core.api.ObjectAssert;
import org.kie.dmn.api.feel.runtime.events.FEELEvent;
import org.kie.dmn.api.feel.runtime.events.FEELEventListener;
@@ -93,6 +95,10 @@ public abstract class BaseFEELTest {
assertion.isNull();
} else if( expected instanceof Class<?> ) {
assertion.isInstanceOf((Class<?>) expected);
+ } else if (expected instanceof Collection<?> collection) {
+
assertion.asInstanceOf(InstanceOfAssertFactories.collection(Object.class))
+ .hasSameSizeAs(collection)
+ .containsExactlyInAnyOrderElementsOf(collection);
} else {
assertion.isEqualTo(expected);
}
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELExpressionsTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELExpressionsTest.java
index 5ee2077f74..0c3a693340 100644
---
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELExpressionsTest.java
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELExpressionsTest.java
@@ -21,6 +21,7 @@ package org.kie.dmn.feel.runtime;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -46,8 +47,23 @@ public class FEELExpressionsTest extends BaseFEELTest {
{ "some x in [ 5, 6, 7 ], y in [ 10, 11, 12 ] satisfies x <
y", Boolean.TRUE , null},
{ "some price in [ 80, 11, 110 ] satisfies price > max(100,
50, 10)", Boolean.TRUE , null},
+ // descendant expressions
+ {"{a: { b: { b: 1 } } }...a", List.of(Map.of("b", Map.of("b",
BigDecimal.valueOf(1)))), null},
+ {"{a: { b: { b: 1 } } }...b", List.of(Map.of("b",
BigDecimal.valueOf(1)), BigDecimal.valueOf(1)), null},
+ {"{a: { b: { a: 1 } } }...a", List.of(Map.of("b", Map.of("a",
BigDecimal.valueOf(1))), BigDecimal.valueOf(1)), null},
+ {"{a: { b: { c: 1 } } }...c", List.of(BigDecimal.valueOf(1)) ,
null},
+ {"{a: { b: { b: 1 } } }...d", List.of() , null},
+ {"{a: { b: { b: 1 } } }...", null , FEELEvent.Severity.ERROR},
+ {"...a", null , FEELEvent.Severity.ERROR},
+ {"[1, 2, 3]...d", null , FEELEvent.Severity.ERROR},
+ {"1...d", null , FEELEvent.Severity.ERROR},
+
// path expressions
{"{ full name: { first name: \"John\", last name: \"Doe\" }
}.full name.last name", "Doe" , null},
+ {"{ full name: { first name: \"John\", last name: \"Doe\" }
}.full name.first name", "John" , null},
+ {"{ full name: { first name: \"John\", last name: \"Doe\" }
}.full name.middle name", null , null},
+ {"{ full name: { first name: \"John\", last name: \"Doe\" }
}.full name", Map.of("first name", "John", "last name", "Doe") , null},
+ {"{ full name: { first name: \"John\", last name: \"Doe\" }
}.full name.first", null , null},
// filter expressions with proper precedence
{"{ EmployeeTable : [ \n"+
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]