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 e0e3e690 JEXL-413: added option and tests;
e0e3e690 is described below

commit e0e3e690fa86312896ca313f61a10321fc24f899
Author: Henri Biestro <hbies...@cloudera.com>
AuthorDate: Sun Nov 12 12:33:41 2023 -0800

    JEXL-413: added option and tests;
---
 .../org/apache/commons/jexl3/JexlException.java    |  5 +-
 .../java/org/apache/commons/jexl3/JexlOptions.java | 23 +++++++-
 .../org/apache/commons/jexl3/internal/Engine.java  |  3 ++
 .../apache/commons/jexl3/internal/Interpreter.java | 17 +++---
 .../commons/jexl3/internal/InterpreterBase.java    | 10 ++++
 .../org/apache/commons/jexl3/Issues400Test.java    | 61 +++++++++++++++++++---
 6 files changed, 104 insertions(+), 15 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlException.java 
b/src/main/java/org/apache/commons/jexl3/JexlException.java
index a6f10ec6..c60b7296 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlException.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlException.java
@@ -504,7 +504,9 @@ public class JexlException extends RuntimeException {
         /** The variable is already declared. */
         REDEFINED,
         /** The variable has a null value. */
-        NULLVALUE;
+        NULLVALUE,
+        /** THe variable is const and an attempt is made to assign it*/
+        CONST;
 
         /**
          * Stringifies the variable issue.
@@ -515,6 +517,7 @@ public class JexlException extends RuntimeException {
             switch(this) {
                 case NULLVALUE : return VARQUOTE + var + "' is null";
                 case REDEFINED : return VARQUOTE + var + "' is already 
defined";
+                case CONST : return VARQUOTE + var + "' is const";
                 case UNDEFINED :
                 default: return VARQUOTE + var + "' is undefined";
             }
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java 
b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
index 66fcaa90..79f51d1c 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -42,6 +42,8 @@ import org.apache.commons.jexl3.internal.Engine;
  * @since 3.2
  */
 public final class JexlOptions {
+    /** The const capture bit. */
+    private static final int CONST_CAPTURE = 8;
     /** The shared instance bit. */
     private static final int SHARED = 7;
     /** The local shade bit. */
@@ -60,7 +62,7 @@ public final class JexlOptions {
     private static final int CANCELLABLE = 0;
     /** The flag names ordered. */
     private static final String[] NAMES = {
-        "cancellable", "strict", "silent", "safe", "lexical", "antish", 
"lexicalShade", "sharedInstance"
+        "cancellable", "strict", "silent", "safe", "lexical", "antish", 
"lexicalShade", "sharedInstance", "constCapture"
     };
     /** Default mask .*/
     private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << 
ANTISH | 1 << SAFE;
@@ -299,6 +301,25 @@ public final class JexlOptions {
         }
     }
 
+    /**
+     * Sets whether lambda captured-variables are const or not.
+     * <p>
+     * When disabled, lambda-captured variables are implicitly converted to 
read-write local variable (let),
+     * when enabled, those are implicitly converted to read-only local 
variables (const).
+     * </p>
+     * @param flag true to enable, false to disable
+     */
+    public void setConstCapture(final boolean flag) {
+        flags = set(CONST_CAPTURE, flags, true);
+    }
+
+    /**
+     * @return true if lambda captured-variables are const, false otherwise
+     */
+    public boolean isConstCapture() {
+        return isSet(CONST_CAPTURE, flags);
+    }
+
     /**
      * Sets the arithmetic math context.
      * @param mcontext the context
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java 
b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index f381fff2..fa304700 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -411,6 +411,9 @@ public class Engine extends JexlEngine {
             if (scriptFeatures.isLexicalShade()) {
                 opts.setLexicalShade(true);
             }
+            if (scriptFeatures.supportsConstCapture()) {
+                opts.setConstCapture(true);
+            }
         }
         if (script != null) {
            // process script pragmas if any
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 2f99a7de..61b5bed8 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -1486,13 +1486,18 @@ public class Interpreter extends InterpreterBase {
         if (left instanceof ASTIdentifier) {
             variable = (ASTIdentifier) left;
             symbol = variable.getSymbol();
-            if (symbol >= 0 && (variable.isLexical() || options.isLexical())) {
-                if (variable instanceof ASTVar) {
-                    if (!defineVariable((ASTVar) variable, block)) {
-                        return redefinedVariable(variable, variable.getName());
+            if (symbol >= 0) {
+                if  (variable.isLexical() || options.isLexical()) {
+                    if (variable instanceof ASTVar) {
+                        if (!defineVariable((ASTVar) variable, block)) {
+                            return redefinedVariable(variable, 
variable.getName());
+                        }
+                    } else if (variable.isShaded() && (variable.isLexical() || 
options.isLexicalShade())) {
+                        return undefinedVariable(variable, variable.getName());
                     }
-                } else if (variable.isShaded() && (variable.isLexical() || 
options.isLexicalShade())) {
-                    return undefinedVariable(variable, variable.getName());
+                }
+                if (variable.isCaptured() && options.isConstCapture()) {
+                    return constVariable(variable, variable.getName());
                 }
             }
         } else {
diff --git 
a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java 
b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
index 55461117..967b393c 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -476,6 +476,16 @@ public abstract class InterpreterBase extends 
ParserVisitor {
         return variableError(node, var, VariableIssue.REDEFINED);
     }
 
+    /**
+     * Triggered when a captured variable is const and assignment is attempted.
+     * @param node  the node where the error originated from
+     * @param var   the variable name
+     * @return throws JexlException if strict and not silent, null otherwise
+     */
+    protected Object constVariable(final JexlNode node, final String var) {
+        return variableError(node, var, VariableIssue.CONST);
+    }
+
     /**
      * Check if a null evaluated expression is protected by a ternary 
expression.
      * <p>
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index b01492d8..af1d565b 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -235,25 +235,72 @@ public class Issues400Test {
 
   @Test
   public void test412() {
-    Map<Object,Object> ctl = new HashMap<>();
+    final Map<Object, Object> ctl = new HashMap<>();
     ctl.put("one", 1);
     ctl.put("two", 2);
-    String fnsrc = "function f(x) { x }\n" +
+    final 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;
+      Map<?, ?> map = (Map<?, ?>) o;
       Assert.assertEquals(map, ctl);
-    } catch(JexlException.Parsing xparse) {
+    } catch (JexlException.Parsing xparse) {
       Assert.fail(fnsrc + " : " + xparse.getMessage());
     }
   }
+
+  @Test
+  public void test413a() {
+    final JexlBuilder builder = new JexlBuilder();
+    final JexlEngine jexl = builder.create();
+    final JexlScript script = jexl.createScript("var c = 42; var f = y -> c += 
y; f(z)", "z");
+    final Number result = (Number) script.execute(null, 12);
+    Assert.assertEquals(54, result);
+  }
+
+  @Test
+  public void test413b() {
+    final JexlBuilder builder = new JexlBuilder();
+    final JexlOptions options = builder.options();
+    options.setConstCapture(true);
+    options.setLexical(true);
+    final JexlEngine jexl = builder.create();
+    final JexlScript script = jexl.createScript("var c = 42; var f = y -> c += 
y; f(z)", "z");
+    try {
+      final Number result = (Number) script.execute(null, 12);
+      Assert.fail("c should be const");
+    } catch(JexlException.Variable xvar) {
+      Assert.assertEquals("c", xvar.getVariable());
+    }
+  }
+
+  @Test
+  public void test413c() {
+    final JexlBuilder builder = new JexlBuilder();
+    final JexlEngine jexl = builder.create();
+    final JexlScript script = jexl.createScript("#pragma jexl.options 
'+constCapture'\nvar c = 42; var f = y -> c += y; f(z)", "z");
+    try {
+      Number result = (Number) script.execute(null, 12);
+      Assert.fail("c should be const");
+    } catch(JexlException.Variable xvar) {
+      Assert.assertEquals("c", xvar.getVariable());
+    }
+  }
+
+  @Test
+  public void test413d() {
+    final JexlBuilder builder = new JexlBuilder().features(new 
JexlFeatures().constCapture(true));
+    final JexlEngine jexl = builder.create();
+    try {
+      final JexlScript script = jexl.createScript("var c = 42; var f = y -> c 
+= y; f(z)", "z");
+      Assert.fail("c should be const");
+    } catch(JexlException.Parsing xvar) {
+      Assert.assertTrue(xvar.getMessage().contains("const"));
+    }
+  }
 }

Reply via email to