This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new 4692b3c6 JEXL-412: improved ambiguity resolution for namespace funcall;
4692b3c6 is described below

commit 4692b3c606baca56a2d2f0a4cb34faa85f920eee
Author: Henri Biestro <hbies...@cloudera.com>
AuthorDate: Fri Nov 3 13:17:43 2023 +0100

    JEXL-412: improved ambiguity resolution for namespace funcall;
---
 RELEASE-NOTES.txt                                  |  1 +
 src/changes/changes.xml                            |  3 ++
 .../apache/commons/jexl3/parser/JexlParser.java    | 60 +++++++++++++++-------
 .../org/apache/commons/jexl3/parser/Parser.jjt     |  4 +-
 .../org/apache/commons/jexl3/Issues400Test.java    | 43 ++++++++++++++++
 5 files changed, 91 insertions(+), 20 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 0334ee79..1dfb7d87 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -37,6 +37,7 @@ New Features in 3.3.1:
 
 Bugs Fixed in 3.3.1:
 ===================
+* JEXL-412:     Ambiguous syntax between namespace function call and map 
object definition.
 * JEXL-410:     JexlFeatures: ctor does not enable all features
 * JEXL-409:     Disable LEXICAL should disable LEXICAL_SHADE
 * JEXL-405:     Recursive functions corrupt evaluation frame if reassigned
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 97e5fa12..9d3c4c38 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -42,6 +42,9 @@
                 Allow 'trailing commas' or ellipsis while defining array, map 
and set literals
             </action>
             <!-- FIX -->
+            <action dev="henrib" type="fix" issue="JEXL-412" due-to="Xu 
Pengcheng" >
+                Ambiguous syntax between namespace function call and map 
object definition.
+            </action>action>
             <action dev="henrib" type="fix" issue="JEXL-410" due-to="sebb">
                 JexlFeatures: ctor does not enable all features
             </action>
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java 
b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
index a353c2fc..fa5f127f 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -575,27 +575,51 @@ public abstract class JexlParser extends StringParser {
     }
 
     /**
-     * Checks whether a name identifies a declared namespace.
-     * @param token the namespace token
-     * @return true if the name qualifies a namespace
+     * Semantic check identifying whether a list of 4 tokens forms a namespace 
function call.
+     * <p>This is needed to disambiguate ternary operator, map entries and 
actual calls.</p>
+     * <p>Note that this check is performed before syntactic check so the 
expected parameters need to be
+     * verified.</p>
+     * @param ns the namespace token
+     * @param colon expected to be &quot;:&quot;
+     * @param fun the function name
+     * @param paren expected to be &quot;(&quot;
+     * @return true if the name qualifies a namespace function call
+     */
+    protected boolean isNamespaceFuncall(final Token ns, final Token colon, 
final Token fun, final Token paren) {
+        // let's make sure this is a namespace function call
+        if (!":".equals(colon.image)) {
+            return false;
+        }
+        if (!"(".equals(paren.image)) {
+            return false;
+        }
+        // if namespace name is shared with a variable name, use syntactic hint
+        final String name = ns.image;
+        if (isVariable(name)) {
+            // the namespace sticks to the colon as in 'ns:fun()' (vs 'ns : 
fun()')
+            return colon.beginColumn - 1 == ns.endColumn && isNamespace(name);
+        }
+        return true;
+    }
+
+    /**
+     * Checks whether a name is a declared namespace.
+     * @param name the namespace name
+     * @return true if declared, false otherwise
      */
-    protected boolean isDeclaredNamespace(final Token token, final Token 
colon) {
-        // syntactic hint, the namespace sticks to the colon
-        if (colon != null && ":".equals(colon.image) && colon.beginColumn - 1 
== token.endColumn) {
+    private boolean isNamespace(String name) {
+        // templates
+        if ("jexl".equals(name) || "$jexl".equals(name)) {
             return true;
         }
-        // if name is shared with a variable name, use syntactic hint
-        final String name = token.image;
-        if (!isVariable(name)) {
-            final Set<String> ns = namespaces;
-            // declared through local pragma ?
-            if (ns != null && ns.contains(name)) {
-                return true;
-            }
-            // declared through engine features ?
-            if (getFeatures().namespaceTest().test(name)) {
-                return true;
-            }
+        final Set<String> ns = namespaces;
+        // declared through local pragma ?
+        if (ns != null && ns.contains(name)) {
+            return true;
+        }
+        // declared through engine features ?
+        if (getFeatures().namespaceTest().test(name)) {
+            return true;
         }
         return false;
     }
diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt 
b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
index 6e49bf37..725c5582 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -914,7 +914,7 @@ void Arguments() #Arguments : {}
 
 void FunctionCallLookahead() #void : {}
 {
-    LOOKAHEAD(<IDENTIFIER> <COLON> <IDENTIFIER> <LPAREN>,  { 
isDeclaredNamespace(getToken(1), getToken(2)) }) <IDENTIFIER> <COLON> 
<IDENTIFIER> <LPAREN>
+    LOOKAHEAD(4, <IDENTIFIER> <COLON> <IDENTIFIER> <LPAREN>,  { 
isNamespaceFuncall(getToken(1), getToken(2), getToken(3), getToken(4)) }) 
<IDENTIFIER> <COLON> <IDENTIFIER> <LPAREN>
     |
     LOOKAHEAD(2) <IDENTIFIER> <LPAREN>
     |
@@ -923,7 +923,7 @@ void FunctionCallLookahead() #void : {}
 
 void FunctionCall() #void : {}
 {
-    LOOKAHEAD(<IDENTIFIER> <COLON> <IDENTIFIER> <LPAREN>,  { 
isDeclaredNamespace(getToken(1), getToken(2)) })  NamespaceIdentifier() 
Arguments() #FunctionNode(2)
+    LOOKAHEAD(4, <IDENTIFIER> <COLON> <IDENTIFIER> <LPAREN>,  { 
isNamespaceFuncall(getToken(1), getToken(2), getToken(3), getToken(4)) })  
NamespaceIdentifier() Arguments() #FunctionNode(2)
     |
     LOOKAHEAD(<IDENTIFIER> <LPAREN>) Identifier(true) Arguments() 
#FunctionNode(2)
 }
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index cb4a2a31..9a43e8b5 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -16,8 +16,10 @@
  */
 package org.apache.commons.jexl3;
 
+import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
@@ -213,4 +215,45 @@ public class Issues400Test {
       Assert.assertEquals(src1, "1*2*3*4", result);
     }
   }
+
+  @Test
+  public void test407() {
+    // Java version
+    double r = 99.0d + 7.82d -99.0d -7.82d;
+    Assert.assertEquals(0d, r, 8.e-15); // Not zero, IEEE 754
+    // jexl
+    final JexlEngine jexl = new JexlBuilder().create();
+    JexlScript script = jexl.createScript("a + b - a - b", "a", "b");
+    // using doubles, same as Java
+    Number result = (Number) script.execute(null, 99.0d, 7.82d);
+    Assert.assertEquals(0d, result.doubleValue(), 8.e-15);
+    // using BigdDecimal, more precise, still not zero
+    result = (Number) script.execute(null, new BigDecimal(99.0d), new 
BigDecimal(7.82d));
+    Assert.assertEquals(0d, result.doubleValue(), 3.e-32);
+  }
+
+
+  @Test
+  public void test412() {
+    Map<Object,Object> ctl = new HashMap<>();
+    ctl.put("one", 1);
+    ctl.put("two", 2);
+    String fnsrc = "function f(x) { x }\n" +
+        "let one = 'one', two = 'two';\n" +
+        "{ one : f(1), two:f(2) }";
+    final JexlContext jc = new MapContext();
+    final String[] sources = {
+       fnsrc
+    };
+    final JexlEngine jexl = new JexlBuilder().create();
+    try {
+      final JexlScript e = jexl.createScript(fnsrc);
+      final Object o = e.execute(jc);
+      Assert.assertTrue(o instanceof Map);
+      Map<?,?> map = (Map<?, ?>) o;
+      Assert.assertEquals(map, ctl);
+    } catch(JexlException.Parsing xparse) {
+      Assert.fail(fnsrc + " : " + xparse.getMessage());
+    }
+  }
 }

Reply via email to