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

commit 46273739be4fc04456c0655475e0944e50a385b1
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/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     | 27 +++++++---
 .../org/apache/commons/jexl3/Issues300Test.java    | 12 +++++
 6 files changed, 178 insertions(+), 19 deletions(-)

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 106a060f..6617c3db 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 19358524..8c3e7351 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -983,7 +983,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 709695f2..990c4c57 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;
@@ -381,6 +381,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>
@@ -600,6 +621,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 05eca5ad..b25f529d 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -136,7 +136,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(); }
@@ -453,6 +454,13 @@ void DeclareVar(boolean lexical, boolean constant) #Var :
 {
     t=<IDENTIFIER> { declareVariable(jjtThis, t, lexical, constant); }
 }
+void DeclareFunction(Scope scope) #Var :
+{
+    Token t;
+}
+{
+    t=<IDENTIFIER> { declareFunction(jjtThis, t, scope); }
+}
 
 void Pragma() #void :
 {
@@ -878,23 +886,26 @@ void ParametersLookahead() #void : {}
 
 void LambdaLookahead() #void : {}
 {
-  <FUNCTION> ParametersLookahead()
+  <FUNCTION> (<IDENTIFIER>)? ParametersLookahead()
   |
-  ParametersLookahead() <LAMBDA>
+  ParametersLookahead() (<LAMBDA> | <FATARROW>)
   |
-  <IDENTIFIER> <LAMBDA>
+  <IDENTIFIER> (<LAMBDA> | <FATARROW>)
 }
 
 void Lambda() #JexlLambda :
 {
-   pushScope();
+   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);
+    }
 }

Reply via email to