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]

Reply via email to