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

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


The following commit(s) were added to refs/heads/JEXL-367 by this push:
     new b7a89c98 JEXL-367: adding fat-arrow vs thin-arrow lambda syntax; add 
named function syntax;
b7a89c98 is described below

commit b7a89c985324f94376da51278c93cd347fe11ac8
Author: henrib <hen...@apache.org>
AuthorDate: Thu May 5 00:06:59 2022 +0200

    JEXL-367: adding fat-arrow vs thin-arrow lambda syntax; add named function 
syntax;
---
 .../org/apache/commons/jexl3/JexlArithmetic.java   |  6 +-
 .../org/apache/commons/jexl3/JexlFeatures.java     | 50 ++++++++++++++++-
 .../apache/commons/jexl3/internal/Debugger.java    | 58 +++++++++++++++++---
 .../apache/commons/jexl3/internal/Interpreter.java | 10 +++-
 .../apache/commons/jexl3/parser/JexlParser.java    | 40 +++++++++++++-
 .../org/apache/commons/jexl3/parser/Parser.jjt     | 25 ++++++---
 .../org/apache/commons/jexl3/Issues300Test.java    | 12 ++++
 .../apache/commons/jexl3/jexl342/OptionalTest.java | 64 ++++++++++++++++++++++
 8 files changed, 244 insertions(+), 21 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java 
b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index b34d3678..9b0929f9 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -497,7 +497,7 @@ public class JexlArithmetic {
     }
 
     /**
-     * Given a Number, return back the value using the smallest type the result
+     * Given a Number, return the value using the smallest type the result
      * will fit into.
      * <p>This works hand in hand with parameter 'widening' in java
      * method calls, e.g. a call to substring(int,int) with an int and a long
@@ -1390,8 +1390,8 @@ public class JexlArithmetic {
                 return 0;
             }
             if (isNumberable(left) || isNumberable(right)) {
-                final long lhs = toLong(left);
-                final long rhs = toLong(right);
+                final long lhs = toLong(left instanceof String? 
Double.parseDouble((String) left) : left);
+                final long rhs = toLong(right instanceof String? 
Double.parseDouble((String) right) : right);
                 if (lhs < rhs) {
                     return -1;
                 }
diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java 
b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index 15d07e72..06ed52a5 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -59,7 +59,8 @@ public final class JexlFeatures {
     private static final String[] F_NAMES = {
         "register", "reserved variable", "local variable", "assign/modify",
         "global assign/modify", "array reference", "create instance", "loop", 
"function",
-        "method call", "set/map/array literal", "pragma", "annotation", 
"script", "lexical", "lexicalShade"
+        "method call", "set/map/array literal", "pragma", "annotation", 
"script", "lexical", "lexicalShade",
+        "thin-arrow", "fat-arrow"
     };
     /** Registers feature ordinal. */
     private static final int REGISTER = 0;
@@ -93,6 +94,10 @@ public final class JexlFeatures {
     public static final int LEXICAL = 14;
     /** Lexical shade feature ordinal. */
     public static final int LEXICAL_SHADE = 15;
+    /** Fat-arrow lambda syntax. */
+    public static final int THIN_ARROW = 16;
+    /** Fat-arrow lambda syntax. */
+    public static final int FAT_ARROW = 17;
 
     /**
      * Creates an all-features-enabled instance.
@@ -109,7 +114,8 @@ public final class JexlFeatures {
                 | (1L << STRUCTURED_LITERAL)
                 | (1L << PRAGMA)
                 | (1L << ANNOTATION)
-                | (1L << SCRIPT);
+                | (1L << SCRIPT)
+                | (1L << THIN_ARROW);
         reservedNames = Collections.emptySet();
         nameSpaces = TEST_STR_FALSE;
     }
@@ -433,6 +439,46 @@ public final class JexlFeatures {
         return getFeature(LAMBDA);
     }
 
+    /**
+     * Sets whether thin-arrow lambda syntax is enabled.
+     * <p>
+     * When disabled, parsing a script/expression using syntactic thin-arrow 
(->)
+     * will throw a parsing exception.
+     * @param flag true to enable, false to disable
+     * @return this features instance
+     */
+    public JexlFeatures thinArrow(final boolean flag) {
+        setFeature(THIN_ARROW, flag);
+        return this;
+    }
+
+    /**
+     * @return true if thin-arrow lambda syntax is enabled, false otherwise
+     */
+    public boolean supportsThinArrow() {
+        return getFeature(THIN_ARROW);
+    }
+
+    /**
+     * Sets whether fat-arrow lambda syntax is enabled.
+     * <p>
+     * When disabled, parsing a script/expression using syntactic fat-arrow 
(=>)
+     * will throw a parsing exception.
+     * @param flag true to enable, false to disable
+     * @return this features instance
+     */
+    public JexlFeatures fatArrow(final boolean flag) {
+        setFeature(FAT_ARROW, flag);
+        return this;
+    }
+
+    /**
+     * @return true if fat-arrow lambda syntax is enabled, false otherwise
+     */
+    public boolean supportsFatArrow() {
+        return getFeature(FAT_ARROW);
+    }
+
     /**
      * Sets whether pragma constructs are enabled.
      * <p>
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java 
b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
index dcd6ea4b..4b089b4d 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
@@ -18,6 +18,7 @@ package org.apache.commons.jexl3.internal;
 
 
 import org.apache.commons.jexl3.JexlExpression;
+import org.apache.commons.jexl3.JexlFeatures;
 import org.apache.commons.jexl3.JexlInfo;
 import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.parser.*;
@@ -47,6 +48,8 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
     protected int indent = 2;
     /** accept() relative depth. */
     protected int depth = Integer.MAX_VALUE;
+    /** Arrow symbol. */
+    protected String arrow = "->";
 
     /**
      * Creates a Debugger.
@@ -68,6 +71,36 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         depth = Integer.MAX_VALUE;
     }
 
+    /**
+     * Tries (hard) to find the features used to parse a node.
+     * @param node the node
+     * @return the features or null
+     */
+    protected JexlFeatures getFeatures(JexlNode node) {
+        JexlNode walk = node;
+        while(walk != null) {
+            if (walk instanceof ASTJexlScript) {
+                ASTJexlScript script = (ASTJexlScript) walk;
+                return script.getFeatures();
+            }
+            walk = walk.jjtGetParent();
+        }
+        return null;
+    }
+
+    /**
+     * Sets the arrow style (fat or thin) depending on features.
+     * @param node the node to start seeking features from.
+     */
+    protected void setArrowSymbol(JexlNode node) {
+        JexlFeatures features = getFeatures(node);
+        if (features != null && features.supportsFatArrow() && 
!features.supportsThinArrow()) {
+            arrow = "=>";
+        } else {
+            arrow = "->";
+        }
+    }
+
     /**
      * Position the debugger on the root of an expression.
      * @param jscript the expression
@@ -75,7 +108,8 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
      */
     public boolean debug(final JexlExpression jscript) {
         if (jscript instanceof Script) {
-            return debug(((Script) jscript).script);
+            Script script = (Script) jscript;
+            return debug(script.script);
         }
         return false;
     }
@@ -87,7 +121,8 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
      */
     public boolean debug(final JexlScript jscript) {
         if (jscript instanceof Script) {
-            return debug(((Script) jscript).script);
+            Script script = (Script) jscript;
+            return debug(script.script);
         }
         return false;
     }
@@ -111,6 +146,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         start = 0;
         end = 0;
         indentLevel = 0;
+        setArrowSymbol(node);
         if (node != null) {
             builder.setLength(0);
             cause = node;
@@ -144,6 +180,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         start = 0;
         end = 0;
         indentLevel = 0;
+        setArrowSymbol(node);
         if (node != null) {
             builder.setLength(0);
             cause = node;
@@ -675,15 +712,22 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
     @Override
     protected Object visit(final ASTJexlScript node, Object arg) {
         Object data = arg;
+        boolean named = false;
         // if lambda, produce parameters
         if (node instanceof ASTJexlLambda) {
             final ASTJexlLambda lambda = (ASTJexlLambda) node;
             final JexlNode parent = node.jjtGetParent();
             // use lambda syntax if not assigned
             boolean expr = isLambdaExpr(lambda);
-            final boolean named = parent instanceof ASTAssignment;
-            if (named && !expr) {
+            named = node.jjtGetChild(0) instanceof ASTVar;
+            final boolean assigned = parent instanceof ASTAssignment || named;
+            if (assigned && !expr) {
                 builder.append("function");
+                if (named) {
+                    ASTVar avar = (ASTVar) node.jjtGetChild(0);
+                    builder.append(' ');
+                    builder.append(avar.getName());
+                }
             }
             builder.append('(');
             final String[] params = lambda.getParameters();
@@ -695,11 +739,11 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
                 }
             }
             builder.append(')');
-            if (named && !expr) {
+            if (assigned && !expr) {
                 // block follows
                 builder.append(' ');
             } else {
-                builder.append("->");
+                builder.append(arrow);
                 // add a space if lambda expr otherwise block follows
                 if (expr) {
                     builder.append(' ');
@@ -711,7 +755,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         if (num == 1 && !(node instanceof ASTJexlLambda)) {
             data = accept(node.jjtGetChild(0), data);
         } else {
-            for (int i = 0; i < num; ++i) {
+            for (int i = named? 1 : 0; i < num; ++i) {
                 final JexlNode child = node.jjtGetChild(i);
                 acceptStatement(child, data);
             }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java 
b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index 469e184d..acfd2c50 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -980,7 +980,15 @@ public class Interpreter extends InterpreterBase {
     @Override
     protected Object visit(final ASTJexlScript script, final Object data) {
         if (script instanceof ASTJexlLambda && !((ASTJexlLambda) 
script).isTopLevel()) {
-            return new Closure(this, (ASTJexlLambda) script);
+            Closure closure = new Closure(this, (ASTJexlLambda) script);
+            // if the function is named, assign in the local frame
+            JexlNode child0 = script.jjtGetChild(0);
+            if (child0 instanceof ASTVar) {
+                ASTVar var = (ASTVar) child0;
+                this.visit(var, data);
+                frame.set(var.getSymbol(), closure);
+            }
+            return closure;
         }
         block = new LexicalFrame(frame, block).defineArgs();
         try {
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 d893a0fc..aea15f5a 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -20,8 +20,8 @@ import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
 import org.apache.commons.jexl3.JexlFeatures;
 import org.apache.commons.jexl3.JexlInfo;
-import org.apache.commons.jexl3.internal.Scope;
 import org.apache.commons.jexl3.internal.LexicalScope;
+import org.apache.commons.jexl3.internal.Scope;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -376,6 +376,27 @@ public abstract class JexlParser extends StringParser {
         return block == null || block.declareSymbol(symbol);
     }
 
+    /**
+     * Declares a local function.
+     * @param variable the identifier used to declare
+     * @param token      the variable name toekn
+     */
+    protected void declareFunction(final ASTVar variable, final Token token, 
Scope scope) {
+        final String name = token.image;
+        final int symbol = scope.declareVariable(name);
+        variable.setSymbol(symbol, name);
+        if (scope.isCapturedSymbol(symbol)) {
+            variable.setCaptured(true);
+        }
+        // lexical feature error
+        if (!declareSymbol(symbol)) {
+            if (getFeatures().isLexical()) {
+                throw new JexlException(variable, name + ": variable is 
already declared");
+            }
+            variable.setRedefined(true);
+        }
+    }
+
     /**
      * Declares a local variable.
      * <p> This method creates an new entry in the symbol map. </p>
@@ -561,6 +582,23 @@ public abstract class JexlParser extends StringParser {
         featureController.controlNode(node);
     }
 
+    /**
+     * Check fat vs thin arrow syntax feature.
+     * @param token the arrow token
+     */
+    protected void checkLambda(Token token) {
+        final String arrow = token.image;
+        if ("->".equals(arrow)) {
+            if (!getFeatures().supportsThinArrow()) {
+                throwFeatureException(JexlFeatures.THIN_ARROW, token);
+            }
+            return;
+        }
+        if ("=>".equals(arrow) && !getFeatures().supportsFatArrow()) {
+            throwFeatureException(JexlFeatures.FAT_ARROW, token);
+        }
+    }
+
     /**
      * Throws Ambiguous exception.
      * <p>Seeks the end of the ambiguous statement to recover.
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 94efc9d1..090ae4cd 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -135,7 +135,8 @@ TOKEN_MGR_DECLS : {
     | < FALSE : "false" >  { popDot(); }
     | < RETURN : "return" > { popDot(); }
     | < FUNCTION : "function" >  { popDot(); }
-    | < LAMBDA : "->" | "=>" > { popDot(); }
+    | < LAMBDA : "->" > { popDot(); }
+    | < FATARROW : "=>" > { popDot(); }
     | < BREAK : "break" > { popDot(); }
     | < CONTINUE : "continue" > { popDot(); }
     | < PRAGMA : "#pragma" > { popDot(); }
@@ -443,6 +444,13 @@ void DeclareVar(boolean lexical) #Var :
 {
     t=<IDENTIFIER> { declareVariable(jjtThis, t, lexical); }
 }
+void DeclareFunction(Scope scope) #Var :
+{
+    Token t;
+}
+{
+    t=<IDENTIFIER> { declareFunction(jjtThis, t, scope); }
+}
 
 void Pragma() #void :
 {
@@ -860,23 +868,26 @@ void Parameters() #void : {}
 
 void LambdaLookahead() #void : {}
 {
-  <FUNCTION> Parameters()
+  <FUNCTION> (<IDENTIFIER>)? Parameters()
   |
-  Parameters() <LAMBDA>
+  Parameters() (<LAMBDA> | <FATARROW>)
   |
-  Parameter() <LAMBDA>
+  Parameter() (<LAMBDA> | <FATARROW>)
 }
 
 void Lambda() #JexlLambda :
 {
+   Token arrow;
+   Token name;
+   Scope scope = getFrame();
    pushFrame();
 }
 {
-  { pushUnit(jjtThis); } <FUNCTION> Parameters() ( LOOKAHEAD(3) Block() | 
Expression()) { popUnit(jjtThis); }
+  { pushUnit(jjtThis); } <FUNCTION> (LOOKAHEAD(<IDENTIFIER>) 
DeclareFunction(scope))? Parameters() ( LOOKAHEAD(3) Block() | Expression()) { 
popUnit(jjtThis); }
   |
-  { pushUnit(jjtThis); } Parameters() <LAMBDA>  ( LOOKAHEAD(3) Block() | 
Expression()) { popUnit(jjtThis); }
+  { pushUnit(jjtThis); } Parameters() (arrow=<LAMBDA> | arrow=<FATARROW>) ( 
LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); }
   |
-  { pushUnit(jjtThis); } Parameter() <LAMBDA> ( LOOKAHEAD(3) Block() | 
Expression()) { popUnit(jjtThis); }
+  { pushUnit(jjtThis); } Parameter() (arrow=<LAMBDA> | arrow=<FATARROW>)( 
LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); }
 }
 
 
diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
index 7cdf7b15..c5de60bd 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
@@ -768,4 +768,16 @@ public class Issues300Test {
         Object result = script.execute(null);
         return result;
     }
+
+
+    @Test public void test367() {
+        String text = "var toto; function foo(x) { x }; var tata = 3; foo(3)";
+        JexlEngine jexl = new JexlBuilder().safe(true).create();
+        JexlScript script = jexl.createScript(text);
+        Object result = script.execute(null);
+        Assert.assertEquals(3, result);
+        String s0 = script.getParsedText();
+        String s1 = script.getSourceText();
+        Assert.assertNotEquals(s0, s1);
+    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/jexl342/OptionalTest.java 
b/src/test/java/org/apache/commons/jexl3/jexl342/OptionalTest.java
index ea28e115..13d18a5f 100644
--- a/src/test/java/org/apache/commons/jexl3/jexl342/OptionalTest.java
+++ b/src/test/java/org/apache/commons/jexl3/jexl342/OptionalTest.java
@@ -16,18 +16,24 @@
  */
 package org.apache.commons.jexl3.jexl342;
 
+import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlBuilder;
+import org.apache.commons.jexl3.JexlContext;
 import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
 import org.apache.commons.jexl3.JexlInfo;
 import org.apache.commons.jexl3.JexlScript;
+import org.apache.commons.jexl3.MapContext;
 import org.apache.commons.jexl3.introspection.JexlUberspect;
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 public class OptionalTest {
 
@@ -45,6 +51,64 @@ public class OptionalTest {
         }
     }
 
+    public static class StreamContext extends MapContext {
+        public Stream map(Collection<Object> c, JexlScript s) {
+            JexlContext context = JexlEngine.getThreadContext();
+            return c.stream().map(a->s.execute(context, a));
+        }
+        public Object reduce(Stream<Object> stream, JexlScript script) {
+            return stream.reduce((identity, element)->{
+                JexlContext context = JexlEngine.getThreadContext();
+                return script.execute(context, identity, element);
+            });
+        }
+    }
+
+    @Test
+    public void testStream() {
+        String src = "[1, 2, 3, ...].map(x -> x * x).reduce((acc, x)->acc + 
x).intValue()";
+        JexlBuilder builder = new JexlBuilder();
+        JexlUberspect uber = builder.create().getUberspect();
+        JexlEngine jexl = builder.uberspect(new 
ReferenceUberspect(uber)).safe(false).create();
+        JexlInfo info = new JexlInfo("testStream", 1, 1);
+        MapContext context = new StreamContext();
+        JexlScript script = jexl.createScript(src, "list");
+        Object result = script.execute(context, Arrays.asList(1, 2, 3));
+        Assert.assertEquals(14, result);
+        //Optional<?> result = (Optional<?>) script.execute(context, 
Arrays.asList(1, 2, 3));
+        //Assert.assertEquals(14, result.get());
+    }
+
+    public static class OptionalArithmetic extends JexlArithmetic {
+        public OptionalArithmetic(boolean astrict) {
+            super(astrict);
+        }
+        public Object add(Optional<?> lhs, Optional<?> rhs) {
+            return add(lhs.get(), rhs.get());
+        }
+        public Object add(Object lhs, Optional<?> rhs) {
+            return add(lhs, rhs.get());
+        }
+        public Object add(Optional<?> lhs, Object rhs) {
+            return add(lhs, rhs);
+        }
+    }
+
+    @Test
+    public void testOptionalArgs() {
+        JexlBuilder builder = new JexlBuilder();
+        JexlArithmetic jexla = new OptionalArithmetic(true);
+        JexlUberspect uber = builder.create().getUberspect();
+        JexlEngine jexl = builder.uberspect(new 
ReferenceUberspect(uber)).arithmetic(jexla).safe(false).create();
+        JexlInfo info = new JexlInfo("testStream", 1, 1);
+        MapContext context = new StreamContext();
+        String src = "x + x";
+        JexlScript script = jexl.createScript(src, "x");
+        Optional<Integer> x = Optional.of(21);
+        Object result = script.execute(context, x);
+        Assert.assertEquals(42, result);
+    }
+
     @Test
     public void test342() {
         JexlBuilder builder = new JexlBuilder();

Reply via email to