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 ba11d0e0 JEXL-425, JEXL-426, JEXL-427 : - prototype code respectively 
through option flag (strictInterpolation) and feature flag (referenceCapture) ; 
- made short-circuit operators behavior closer to EcmaScript;
ba11d0e0 is described below

commit ba11d0e078913dd37501bfda0b76df2a32077241
Author: Henrib <hbies...@gmail.com>
AuthorDate: Tue Aug 27 19:51:25 2024 +0200

    JEXL-425, JEXL-426, JEXL-427 : - prototype code respectively through option 
flag (strictInterpolation) and feature flag (referenceCapture) ;
    - made short-circuit operators behavior closer to EcmaScript;
---
 .../org/apache/commons/jexl3/JexlArithmetic.java   |   8 ++
 .../java/org/apache/commons/jexl3/JexlBuilder.java |  27 +++--
 .../org/apache/commons/jexl3/JexlFeatures.java     |  29 ++++-
 .../java/org/apache/commons/jexl3/JexlOptions.java |  24 +++-
 .../org/apache/commons/jexl3/internal/Closure.java |   2 +-
 .../org/apache/commons/jexl3/internal/Frame.java   |  95 +++++++++++++--
 .../apache/commons/jexl3/internal/Interpreter.java | 135 ++++++++++-----------
 .../org/apache/commons/jexl3/internal/Scope.java   |  12 +-
 .../commons/jexl3/parser/ASTIdentifierAccess.java  |   2 +-
 .../jexl3/parser/ASTIdentifierAccessJxlt.java      |   9 +-
 .../apache/commons/jexl3/parser/ASTJexlScript.java |   3 +-
 .../commons/jexl3/parser/ASTJxltLiteral.java       |   9 +-
 .../org/apache/commons/jexl3/parser/JexlNode.java  |  10 ++
 .../org/apache/commons/jexl3/parser/Parser.jjt     |  27 +++--
 .../org/apache/commons/jexl3/ArithmeticTest.java   |   9 +-
 .../org/apache/commons/jexl3/FeaturesTest.java     |   5 +-
 .../org/apache/commons/jexl3/Issues400Test.java    |  58 ++++++++-
 17 files changed, 343 insertions(+), 121 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java 
b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index 76d1a7d7..a04b0e25 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -2070,6 +2070,14 @@ public class JexlArithmetic {
                 + val.getClass().getName() + ":(" + val + ")");
     }
 
+    public Object falsy(Object arg) {
+        return false;
+    }
+
+    public Object truthy(Object arg) {
+        return true;
+    }
+
     /**
      * Coerce to a primitive boolean.
      * <p>Double.NaN, null, "false" and empty string coerce to false.</p>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java 
b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index f415cdde..03146109 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -367,15 +367,15 @@ public class JexlBuilder {
     }
 
    /**
- * Sets whether the engine will report debugging information when error occurs.
- *
- * @param flag true implies debug is on, false implies debug is off.
- * @return this builder
- */
-public JexlBuilder debug(final boolean flag) {
-    this.debug = flag;
-    return this;
-}
+     * Sets whether the engine will report debugging information when error 
occurs.
+     *
+     * @param flag true implies debug is on, false implies debug is off.
+     * @return this builder
+     */
+    public JexlBuilder debug(final boolean flag) {
+        this.debug = flag;
+        return this;
+    }
 
     /** @return the features */
     public JexlFeatures features() {
@@ -675,6 +675,15 @@ public JexlBuilder debug(final boolean flag) {
         return this;
     }
 
+    public JexlBuilder strictInterpolation(final boolean flag) {
+        options.setStrictInterpolation(flag);
+        return this;
+    }
+
+    public boolean strictInterpolation() {
+        return options.isStrictInterpolation();
+    }
+
     /** @return the uberspect */
     public JexlUberspect uberspect() {
         return this.uberspect;
diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java 
b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index a7f278e0..73f65068 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -71,7 +71,7 @@ public final class JexlFeatures {
         "global assign/modify", "array reference", "create instance", "loop", 
"function",
         "method call", "set/map/array literal", "pragma", "annotation", 
"script", "lexical", "lexicalShade",
         "thin-arrow", "fat-arrow", "namespace pragma", "import pragma", 
"comparator names", "pragma anywhere",
-        "const capture"
+        "const capture", "ref capture"
     };
     /** Registers feature ordinal. */
     private static final int REGISTER = 0;
@@ -119,11 +119,13 @@ public final class JexlFeatures {
     public static final int PRAGMA_ANYWHERE = 21;
     /** Captured variables are const. */
     public static final int CONST_CAPTURE = 22;
+    /** Captured variables are reference. */
+    public static final int REF_CAPTURE = 23;
     /**
      * All features.
      * N.B. ensure this is updated if additional features are added.
      */
-    private static final long ALL_FEATURES = (1L << CONST_CAPTURE + 1) - 1L; 
// MUST REMAIN PRIVATE
+    private static final long ALL_FEATURES = (1L << REF_CAPTURE + 1) - 1L; // 
MUST REMAIN PRIVATE
     /**
      * The default features flag mask.
      * <p>Meant for compatibility with scripts written before 3.3.1</p>
@@ -352,6 +354,22 @@ public final class JexlFeatures {
         return this;
     }
 
+    /**
+     * Sets whether lambda captured-variables are references or not.
+     * <p>When variables are pass-by-reference, side-effects are visible from 
inner lexical scopes
+     * to outer-scope.</p>
+     * <p>
+     * When disabled, lambda-captured variables use pass-by-value semantic,
+     * when enabled, those use pass-by-reference semantic.
+     * </p>
+     * @param flag true to enable, false to disable
+     * @return this features instance
+     */
+    public JexlFeatures referenceCapture(final boolean flag) {
+        setFeature(REF_CAPTURE, flag);
+        return this;
+    }
+
     @Override
     public boolean equals(final Object obj) {
         if (this == obj) {
@@ -744,6 +762,13 @@ public final class JexlFeatures {
         return getFeature(CONST_CAPTURE);
     }
 
+    /**
+     * @return true if lambda captured-variables are references, false 
otherwise
+     */
+    public boolean supportsReferenceCapture() {
+        return getFeature(REF_CAPTURE);
+    }
+
     /**
      *
      * @return true if expressions (aka not scripts) are enabled, false 
otherwise
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java 
b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
index 5b109caa..3ca5c7ef 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -36,12 +36,16 @@ import org.apache.commons.jexl3.internal.Engine;
  * <li>strict: whether unknown or unsolvable identifiers are errors</li>
  * <li>strictArithmetic: whether null as operand is an error</li>
  * <li>sharedInstance: whether these options can be modified at runtime during 
execution (expert)</li>
+ * <li>constCapture: whether captured variables will throw an error if an 
attempt is made to change their value</li>
+ * <li>strictInterpolation: whether interpolation strings always return a 
string or attempt to parse and return integer</li>
  * </ul>
  * The sensible default is cancellable, strict and strictArithmetic.
  * <p>This interface replaces the now deprecated JexlEngine.Options.
  * @since 3.2
  */
 public final class JexlOptions {
+    /** The interpolation string bit. */
+    private static final int STRICT_INTERPOLATION= 9;
     /** The const capture bit. */
     private static final int CONST_CAPTURE = 8;
     /** The shared instance bit. */
@@ -62,7 +66,8 @@ 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", "constCapture"
+        "cancellable", "strict", "silent", "safe", "lexical", "antish",
+        "lexicalShade", "sharedInstance", "constCapture", "strictInterpolation"
     };
     /** Default mask .*/
     private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << 
ANTISH | 1 << SAFE;
@@ -291,6 +296,13 @@ public final class JexlOptions {
         return strictArithmetic;
     }
 
+    /**
+     * @return true if interpolation strings always return string, false 
otherwise
+     */
+    public boolean isStrictInterpolation() {
+        return isSet(STRICT_INTERPOLATION, flags);
+    }
+
     /**
      * Sets options from engine.
      * @param jexl the engine
@@ -345,7 +357,7 @@ public final class JexlOptions {
      * @param flag true to enable, false to disable
      */
     public void setConstCapture(final boolean flag) {
-        flags = set(CONST_CAPTURE, flags, true);
+        flags = set(CONST_CAPTURE, flags, flag);
     }
 
     /**
@@ -459,6 +471,14 @@ public final class JexlOptions {
         this.strictArithmetic = stricta;
     }
 
+    /**
+     * Sets the strict interpolation flag.
+     * @param flag true or false
+     */
+    public void setStrictInterpolation(final boolean flag) {
+        flags = set(STRICT_INTERPOLATION, flags, flag);
+    }
+
     @Override public String toString() {
         final StringBuilder strb = new StringBuilder();
         for(int i = 0; i < NAMES.length; ++i) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Closure.java 
b/src/main/java/org/apache/commons/jexl3/internal/Closure.java
index 28ae0315..9748ca20 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Closure.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Closure.java
@@ -87,7 +87,7 @@ public class Closure extends Script {
         if (script instanceof ASTJexlLambda) {
             final Scope parentScope = parentFrame != null ? 
parentFrame.getScope() : null;
             final Scope localScope = frame != null ? frame.getScope() : null;
-            if (parentScope != null  && localScope != null && parentScope == 
localScope.getParent()) {
+            if (parentScope != null && localScope != null && parentScope == 
localScope.getParent()) {
                 final Integer reg = localScope.getCaptured(symbol);
                 if (reg != null) {
                     frame.set(reg, this);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Frame.java 
b/src/main/java/org/apache/commons/jexl3/internal/Frame.java
index 550e6c36..e8387694 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Frame.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Frame.java
@@ -17,16 +17,17 @@
 package org.apache.commons.jexl3.internal;
 
 import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * A call frame, created from a scope, stores the arguments and local 
variables in a "stack frame" (sic).
  * @since 3.0
  */
-public final class Frame {
+public class Frame {
     /** The scope. */
     private final Scope scope;
     /** The actual stack frame. */
-    private final Object[] stack;
+    protected final Object[] stack;
     /** Number of curried parameters. */
     private final int curried;
 
@@ -36,7 +37,7 @@ public final class Frame {
      * @param r the stack frame
      * @param c the number of curried parameters
      */
-    Frame(final Scope s, final Object[] r, final int c) {
+    protected Frame(final Scope s, final Object[] r, final int c) {
         scope = s;
         stack = r;
         curried = c;
@@ -58,11 +59,31 @@ public final class Frame {
             }
             // unbound parameters are defined as null
             Arrays.fill(copy, curried + ncopy, nparm, null);
-            return new Frame(scope, copy, curried + ncopy);
+            return newFrame(scope, copy, curried + ncopy);
         }
         return this;
     }
 
+    /**
+     * Creates a new from of this frame&quote;s class.
+     * @param s the scope
+     * @param r the arguments
+     * @param c the number of curried parameters
+     * @return a new instance of frame
+     */
+    Frame newFrame(final Scope s, final Object[] r, final int c) {
+        return new Frame(s, r, c);
+    }
+
+    /**
+     * Captures a value.
+     * @param s the offset in this frame
+     * @return the stacked value
+     */
+    Object capture(int s) {
+        return stack[s];
+    }
+
     /**
      * Gets a value.
      * @param s the offset in this frame
@@ -72,6 +93,15 @@ public final class Frame {
         return stack[s];
     }
 
+    /**
+     * Sets a value.
+     * @param r the offset in this frame
+     * @param value the value to set in this frame
+     */
+    void set(final int r, final Object value) {
+        stack[r] = value;
+    }
+
     /**
      * Gets the scope.
      * @return this frame scope
@@ -117,14 +147,57 @@ public final class Frame {
         }
         return ns;
     }
+}
 
-    /**
-     * Sets a value.
-     * @param r the offset in this frame
-     * @param value the value to set in this frame
-     */
-    void set(final int r, final Object value) {
-        stack[r] = value;
+class ReferenceFrame extends Frame {
+    ReferenceFrame(Scope s, Object[] r, int c) {
+        super(s, r, c);
+    }
+
+    @Override
+    Frame newFrame(final Scope s, final Object[] r, final int c) {
+        return new ReferenceFrame(s, r, c);
+    }
+
+    @Override
+    CaptureReference capture(int s) {
+        synchronized(stack) {
+            Object o = stack[s];
+            if (o instanceof CaptureReference) {
+                return (CaptureReference) o;
+            } else {
+                CaptureReference captured = new CaptureReference(o);
+                stack[s] = captured;
+                return captured;
+            }
+        }
     }
 
+    @Override
+    Object get(final int s) {
+        synchronized(stack) {
+            Object o = stack[s];
+            return o instanceof CaptureReference ? ((CaptureReference) 
o).get() : o;
+        }
+    }
+
+    @Override
+    void set(final int r, final Object value) {
+        synchronized (stack) {
+            Object o = stack[r];
+            if (o instanceof CaptureReference) {
+                if (value != Scope.UNDEFINED && value != Scope.UNDECLARED) {
+                    ((CaptureReference) o).set(value);
+                }
+            } else {
+                stack[r] = value;
+            }
+        }
+    }
 }
+
+class CaptureReference extends AtomicReference<Object> {
+    CaptureReference(Object o) {
+        super(o);
+    }
+}
\ No newline at end of file
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 c1cd0950..cfc2fd54 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -439,6 +439,11 @@ public class Interpreter extends InterpreterBase {
         }
     }
 
+    @Override
+    protected Object visit(final ASTJxltLiteral node, final Object data) {
+        return evalJxltHandle(node);
+    }
+
     /**
      * Evaluates an access identifier based on the 2 main implementations;
      * static (name or numbered identifier) or dynamic (jxlt).
@@ -449,27 +454,49 @@ public class Interpreter extends InterpreterBase {
         if (!(node instanceof ASTIdentifierAccessJxlt)) {
             return node.getIdentifier();
         }
-        final ASTIdentifierAccessJxlt accessJxlt = (ASTIdentifierAccessJxlt) 
node;
-        final String src = node.getName();
+        ASTIdentifierAccessJxlt jxltNode = (ASTIdentifierAccessJxlt) node;
         Throwable cause = null;
-        TemplateEngine.TemplateExpression expr = 
(TemplateEngine.TemplateExpression) accessJxlt.getExpression();
         try {
-            if (expr == null) {
-                final TemplateEngine jxlt = jexl.jxlt();
-                expr = jxlt.parseExpression(node.jexlInfo(), src, frame != 
null ? frame.getScope() : null);
-                accessJxlt.setExpression(expr);
-            }
-            if (expr != null) {
-                final Object name = expr.evaluate(context, frame, options);
-                if (name != null) {
-                    final Integer id = 
ASTIdentifierAccess.parseIdentifier(name.toString());
-                    return id != null ? id : name;
-                }
+            final Object name = evalJxltHandle(jxltNode);
+            if (name != null) {
+                return name;
             }
         } catch (final JxltEngine.Exception xjxlt) {
             cause = xjxlt;
         }
-        return node.isSafe() ? null : unsolvableProperty(node, src, true, 
cause);
+        return node.isSafe() ? null : unsolvableProperty(jxltNode, 
jxltNode.getExpressionSource(), true, cause);
+    }
+
+    /**
+     * Evaluates a JxltHandle node.
+     * <p>This parses and stores the JXLT template if necessary (upon first 
execution)</p>
+     * @param node the node
+     * @return the JXLT template evaluation.
+     * @param <NODE> the node type
+     */
+    private <NODE extends JexlNode & JexlNode.JxltHandle> Object 
evalJxltHandle(NODE node) {
+        JxltEngine.Expression expr = node.getExpression();
+        if (expr == null) {
+            final TemplateEngine jxlt = jexl.jxlt();
+            JexlInfo info = node.jexlInfo();
+            if (this.block != null) {
+                info = new JexlNode.Info(node, info);
+            }
+            expr = jxlt.parseExpression(info, node.getExpressionSource(), 
frame != null ? frame.getScope() : null);
+            node.setExpression(expr);
+        }
+        // internal classes to evaluate in context
+        if (expr instanceof TemplateEngine.TemplateExpression ) {
+           Object eval = ((TemplateEngine.TemplateExpression ) 
expr).evaluate(context, frame, options);
+            if (eval != null) {
+                if (options.isStrictInterpolation()) {
+                    return eval.toString();
+                }
+                final Integer id = 
ASTIdentifierAccess.parseIdentifier(eval.toString());
+                return id != null ? id : eval;
+            }
+        }
+        return null;
     }
 
     /**
@@ -1056,25 +1083,20 @@ public class Interpreter extends InterpreterBase {
          * the ex will traverse up to the interpreter. In cases where this is 
not convenient/possible, JexlException
          * must be caught explicitly and rethrown.
          */
-        final Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        try {
-            final boolean leftValue = arithmetic.toBoolean(left);
-            if (!leftValue) {
-                return Boolean.FALSE;
-            }
-        } catch (final ArithmeticException xrt) {
-            throw new JexlException(node.jjtGetChild(0), "boolean coercion 
error", xrt);
-        }
-        final Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        try {
-            final boolean rightValue = arithmetic.toBoolean(right);
-            if (!rightValue) {
-                return Boolean.FALSE;
+        final int last = node.jjtGetNumChildren();
+        Object argument = null;
+        for (int c = 0; c < last; ++c) {
+            argument = node.jjtGetChild(c).jjtAccept(this, data);
+            try {
+                // short-circuit
+                if (!arithmetic.toBoolean(argument)) {
+                    break;
+                }
+            } catch (final ArithmeticException xrt) {
+                throw new JexlException(node.jjtGetChild(0), "boolean coercion 
error", xrt);
             }
-        } catch (final ArithmeticException xrt) {
-            throw new JexlException(node.jjtGetChild(1), "boolean coercion 
error", xrt);
         }
-        return Boolean.TRUE;
+        return argument;
     }
 
     @Override
@@ -1538,26 +1560,6 @@ public class Interpreter extends InterpreterBase {
         }
     }
 
-    @Override
-    protected Object visit(final ASTJxltLiteral node, final Object data) {
-        final Object cache = node.getExpression();
-        TemplateEngine.TemplateExpression tp;
-        if (cache instanceof TemplateEngine.TemplateExpression) {
-            tp = (TemplateEngine.TemplateExpression) cache;
-        } else {
-            final TemplateEngine jxlt = jexl.jxlt();
-            JexlInfo info = node.jexlInfo();
-            if (this.block != null) {
-                info = new JexlNode.Info(node, info);
-            }
-            tp = jxlt.parseExpression(info, node.getLiteral(), frame != null ? 
frame.getScope() : null);
-            node.setExpression(tp);
-        }
-        if (tp != null) {
-            return tp.evaluate(context, frame, options);
-        }
-        return null;
-    }
 
     @Override
     protected Object visit(final ASTLENode node, final Object data) {
@@ -1786,25 +1788,20 @@ public class Interpreter extends InterpreterBase {
 
     @Override
     protected Object visit(final ASTOrNode node, final Object data) {
-        final Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        try {
-            final boolean leftValue = arithmetic.toBoolean(left);
-            if (leftValue) {
-                return Boolean.TRUE;
-            }
-        } catch (final ArithmeticException xrt) {
-            throw new JexlException(node.jjtGetChild(0), "boolean coercion 
error", xrt);
-        }
-        final Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        try {
-            final boolean rightValue = arithmetic.toBoolean(right);
-            if (rightValue) {
-                return Boolean.TRUE;
+        final int last = node.jjtGetNumChildren();
+        Object argument = null;
+        for (int c = 0; c < last; ++c) {
+            argument = node.jjtGetChild(c).jjtAccept(this, data);
+            try {
+                // short-circuit
+                if (arithmetic.toBoolean(argument)) {
+                    break;
+                }
+            } catch (final ArithmeticException xrt) {
+                throw new JexlException(node.jjtGetChild(0), "boolean coercion 
error", xrt);
             }
-        } catch (final ArithmeticException xrt) {
-            throw new JexlException(node.jjtGetChild(1), "boolean coercion 
error", xrt);
         }
-        return Boolean.FALSE;
+        return argument;
     }
 
     @Override
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Scope.java 
b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
index 41637eb6..1f671e63 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
@@ -114,21 +114,27 @@ public final class Scope {
      * @param args the arguments
      * @return the arguments array
      */
-    public Frame createFrame(final Frame frame, final Object...args) {
+    public Frame createFrame(boolean ref, final Frame frame, final 
Object...args) {
         if (namedVariables == null) {
             return null;
         }
         final Object[] arguments = new Object[namedVariables.size()];
         Arrays.fill(arguments, UNDECLARED);
+        final Frame newFrame;
         if (frame != null && capturedVariables != null && parent != null) {
             for (final Map.Entry<Integer, Integer> capture : 
capturedVariables.entrySet()) {
                 final Integer target = capture.getKey();
                 final Integer source = capture.getValue();
-                final Object arg = frame.get(source);
+                final Object arg = frame.capture(source); //frame.get(source);
                 arguments[target] = arg;
             }
+            newFrame = frame.newFrame(this, arguments, 0);
+        } else {
+            newFrame = ref
+            ? new ReferenceFrame(this, arguments, 0)
+            : new Frame(this, arguments, 0);
         }
-        return new Frame(this, arguments, 0).assign(args);
+        return newFrame.assign(args);
     }
 
     /**
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccess.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccess.java
index 0a19bbf4..7943996c 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccess.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccess.java
@@ -55,8 +55,8 @@ public class ASTIdentifierAccess extends JexlNode {
         }
         return null;
     }
-    private String name;
 
+    private String name;
     private Integer identifier;
 
     ASTIdentifierAccess(final int id) {
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccessJxlt.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccessJxlt.java
index 61981387..7da5382e 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccessJxlt.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccessJxlt.java
@@ -22,13 +22,19 @@ import org.apache.commons.jexl3.JxltEngine;
 /**
  * x.`expr`.
  */
-public class ASTIdentifierAccessJxlt extends ASTIdentifierAccess {
+public class ASTIdentifierAccessJxlt extends ASTIdentifierAccess implements 
JexlNode.JxltHandle{
     protected transient JxltEngine.Expression jxltExpression;
 
     ASTIdentifierAccessJxlt(final int id) {
         super(id);
     }
 
+    @Override
+    public String getExpressionSource() {
+        return getName();
+    }
+
+    @Override
     public JxltEngine.Expression getExpression() {
         return jxltExpression;
     }
@@ -38,6 +44,7 @@ public class ASTIdentifierAccessJxlt extends 
ASTIdentifierAccess {
         return true;
     }
 
+    @Override
     public void setExpression(final JxltEngine.Expression tp) {
         jxltExpression = tp;
     }
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
index 651ec23b..93fc7fea 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
@@ -50,7 +50,7 @@ public class ASTJexlScript extends JexlLexicalNode  {
      * @return the arguments array
      */
     public Frame createFrame(final Frame caller, final Object... values) {
-        return scope != null ? scope.createFrame(caller, values) : null;
+        return scope != null ? 
scope.createFrame(features.supportsReferenceCapture(), caller, values) : null;
     }
 
     /**
@@ -145,6 +145,7 @@ public class ASTJexlScript extends JexlLexicalNode  {
         if (scope == null && jjtGetNumChildren() == 1 && jjtGetChild(0) 
instanceof ASTJexlLambda) {
             final ASTJexlLambda lambda = (ASTJexlLambda) jjtGetChild(0);
             lambda.jjtSetParent(null);
+            lambda.setFeatures(getFeatures());
             return lambda;
         }
         return this;
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTJxltLiteral.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTJxltLiteral.java
index 3b17ddca..e358cc42 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTJxltLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTJxltLiteral.java
@@ -18,7 +18,7 @@ package org.apache.commons.jexl3.parser;
 
 import org.apache.commons.jexl3.JxltEngine;
 
-public final class ASTJxltLiteral extends JexlNode {
+public final class ASTJxltLiteral extends JexlNode implements 
JexlNode.JxltHandle{
     /** Serial uid.*/
     private static final long serialVersionUID = 1L;
     /** The actual literal value. */
@@ -30,6 +30,12 @@ public final class ASTJxltLiteral extends JexlNode {
         super(id);
     }
 
+    @Override
+    public String getExpressionSource() {
+        return literal;
+    }
+
+    @Override
     public JxltEngine.Expression getExpression() {
         return jxltExpression;
     }
@@ -47,6 +53,7 @@ public final class ASTJxltLiteral extends JexlNode {
         return visitor.visit(this, data);
     }
 
+    @Override
     public void setExpression(final JxltEngine.Expression e) {
         this.jxltExpression = e;
     }
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java 
b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
index 5b3ee88a..998af8a4 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
@@ -18,6 +18,7 @@ package org.apache.commons.jexl3.parser;
 
 import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlInfo;
+import org.apache.commons.jexl3.JxltEngine;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
 import org.apache.commons.jexl3.introspection.JexlPropertySet;
@@ -40,6 +41,15 @@ public abstract class JexlNode extends SimpleNode {
      */
     public interface Funcall {}
 
+    /**
+     * Marker interface for nodes hosting a JxltExpression
+     */
+    public interface JxltHandle {
+        String getExpressionSource();
+        JxltEngine.Expression getExpression();
+        void setExpression(JxltEngine.Expression expr);
+    }
+
     /**
      * An info bound to its node.
      * <p>Used to parse expressions for templates.
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 b6a6d896..a8ddf22f 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -59,9 +59,10 @@ public final class Parser extends JexlParser
             this.scope = jexlScope;
             token_source.comparatorNames = 
jexlFeatures.supportsComparatorNames();
             ReInit(jexlSrc);
-            ASTJexlScript script = jexlFeatures.supportsScript()? 
JexlScript(jexlScope) : JexlExpression(jexlScope);
+            ASTJexlScript script = jexlFeatures.supportsScript()
+                ? JexlScript(jexlScope, jexlFeatures)
+                : JexlExpression(jexlScope, jexlFeatures);
             script.jjtSetValue(info.detach());
-            script.setFeatures(jexlFeatures);
             script.setPragmas(pragmas != null
                              ? Collections.unmodifiableMap(pragmas)
                              : Collections.emptyMap());
@@ -347,8 +348,9 @@ TOKEN_MGR_DECLS : {
  *      Statements
  ***************************************/
 
-ASTJexlScript JexlScript(Scope frame) : {
+ASTJexlScript JexlScript(Scope frame, JexlFeatures features) : {
     jjtThis.setScope(frame);
+    jjtThis.setFeatures(features);
 }
 {
    {
@@ -361,8 +363,9 @@ ASTJexlScript JexlScript(Scope frame) : {
    }
 }
 
-ASTJexlScript JexlExpression(Scope frame) #JexlScript : {
+ASTJexlScript JexlExpression(Scope frame, JexlFeatures features) #JexlScript : 
{
     jjtThis.setScope(frame);
+    jjtThis.setFeatures(features);
 }
 {
    {
@@ -423,7 +426,7 @@ void Block() #Block : {}
 
 void FunctionStatement() #JexlLambda : {}
 {
-<FUNCTION> DeclareFunction() { pushScope(); pushUnit(jjtThis); } Parameters() 
( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); popScope(); }
+<FUNCTION> DeclareFunction() { pushScope(); 
jjtThis.setFeatures(this.getFeatures()); pushUnit(jjtThis); } Parameters() ( 
LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); popScope(); }
 }
 
 void ExpressionStatement() #void : {}
@@ -676,14 +679,14 @@ void ConditionalExpression() #void : {}
 
 void ConditionalOrExpression() #void : {}
 {
-  ConditionalAndExpression()
-  ( LOOKAHEAD(2) ( (<OR>|<_OR>) ConditionalAndExpression() #OrNode(2) ) )*
+  ( ConditionalAndExpression()
+  ( LOOKAHEAD(2) ( (<OR>|<_OR>) ConditionalAndExpression()  ) )* ) #OrNode(>1)
 }
 
 void ConditionalAndExpression() #void : {}
 {
-  InclusiveOrExpression()
-  ( LOOKAHEAD(2) ( (<AND>|<_AND>) InclusiveOrExpression() #AndNode(2) ) )*
+  ( InclusiveOrExpression()
+  ( LOOKAHEAD(2) ( (<AND>|<_AND>) InclusiveOrExpression()  ) )* ) #AndNode(>1)
 }
 
 void InclusiveOrExpression() #void : {}
@@ -1055,11 +1058,11 @@ void Lambda() #JexlLambda :
 }
 {
   <FUNCTION> (LOOKAHEAD(<IDENTIFIER>) DeclareFunction())? {
-   pushScope(); pushUnit(jjtThis); } Parameters() ( LOOKAHEAD(3) Block() | 
Expression()) { popUnit(jjtThis); popScope(); }
+   pushScope(); jjtThis.setFeatures(this.getFeatures()); pushUnit(jjtThis); } 
Parameters() ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); 
popScope(); }
   |
-  { pushScope(); pushUnit(jjtThis); } Parameters() (arrow=<LAMBDA> | 
arrow=<FATARROW>) ( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); 
popUnit(jjtThis); popScope(); }
+  { pushScope(); jjtThis.setFeatures(this.getFeatures()); pushUnit(jjtThis); } 
Parameters() (arrow=<LAMBDA> | arrow=<FATARROW>) ( LOOKAHEAD(3) Block() | 
Expression()) { checkLambda(arrow); popUnit(jjtThis); popScope(); }
   |
-  { pushScope(); pushUnit(jjtThis); } Parameter() (arrow=<LAMBDA> | 
arrow=<FATARROW>)( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); 
popUnit(jjtThis); popScope(); }
+  { pushScope(); jjtThis.setFeatures(this.getFeatures()); pushUnit(jjtThis); } 
Parameter() (arrow=<LAMBDA> | arrow=<FATARROW>)( LOOKAHEAD(3) Block() | 
Expression()) { checkLambda(arrow); popUnit(jjtThis); popScope(); }
 }
 
 
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java 
b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index d1b229b7..6d5b9059 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -876,6 +876,7 @@ public class ArithmeticTest extends JexlTestCase {
     public void testAtomicBoolean() {
         // in a condition
         JexlScript e = JEXL.createScript("if (x) 1 else 2;", "x");
+        JexlArithmetic jexla = JEXL.getArithmetic();
         final JexlContext jc = new MapContext();
         final AtomicBoolean ab = new AtomicBoolean(false);
         Object o;
@@ -888,16 +889,16 @@ public class ArithmeticTest extends JexlTestCase {
         e = JEXL.createScript("x && y", "x", "y");
         ab.set(true);
         o = e.execute(jc, ab, Boolean.FALSE);
-        assertFalse((Boolean) o);
+        assertFalse(jexla.toBoolean(o));
         ab.set(true);
         o = e.execute(jc, ab, Boolean.TRUE);
-        assertTrue((Boolean) o);
+        assertTrue(jexla.toBoolean(o));
         ab.set(false);
         o = e.execute(jc, ab, Boolean.FALSE);
-        assertFalse((Boolean) o);
+        assertFalse(jexla.toBoolean(o));
         ab.set(false);
         o = e.execute(jc, ab, Boolean.FALSE);
-        assertFalse((Boolean) o);
+        assertFalse(jexla.toBoolean(o));
         // in arithmetic op
         e = JEXL.createScript("x + y", "x", "y");
         ab.set(true);
diff --git a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java 
b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
index ae8a6fda..8939dbd9 100644
--- a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
@@ -17,6 +17,7 @@
 package org.apache.commons.jexl3;
 
 import static org.apache.commons.jexl3.JexlFeatures.CONST_CAPTURE;
+import static org.apache.commons.jexl3.JexlFeatures.REF_CAPTURE;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -71,8 +72,8 @@ public class FeaturesTest extends JexlTestCase {
     @Test
     public void test410a() {
         final long x = JexlFeatures.createAll().getFlags();
-        assertEquals(CONST_CAPTURE + 1, Long.bitCount(x));
-        assertTrue((x & 1L << CONST_CAPTURE) != 0);
+        assertEquals(REF_CAPTURE + 1, Long.bitCount(x));
+        assertTrue((x & 1L << REF_CAPTURE) != 0);
 
         final JexlFeatures all = JexlFeatures.createAll();
         final JexlEngine jexl = new JexlBuilder().features(all).create();
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index 98ca2d1a..26a8cd44 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -143,8 +143,8 @@ public class Issues400Test {
                 assertTrue(result instanceof Map);
                 final Map<?, ?> map = (Map<?, ?>) result;
                 assertEquals(1, map.size());
-                assertTrue(map.containsKey(1));
-                assertTrue(map.containsValue(1));
+                Object val = jexl.createScript("m -> m[1]").execute(null, map);
+                assertEquals(1, val);
             }
         }
     }
@@ -481,4 +481,58 @@ public class Issues400Test {
         assertEquals(7, m[0].get("type"));
         assertEquals(9, m[1].get("type"));
     }
+
+    @Test public void test425() {
+        final JexlBuilder builder = new 
JexlBuilder().strictInterpolation(true);
+        final JexlEngine jexl = builder.create();
+        JexlScript script;
+        Object result;
+        script = jexl.createScript("let x = 42; let y = `${x}`; y");
+        result = script.execute(null);
+        assertTrue(result instanceof String);
+        assertEquals("42", result);
+    }
+
+    @Test public void test426() {
+        String src = "let x = 10;\n" +
+                "let foo = () -> {\n" +
+                "x += 2;\n" +
+                "}\n" +
+                "x = 40;\n" +
+                "foo();\n" +
+                "x";
+        final JexlBuilder builder = new JexlBuilder().features(new 
JexlFeatures().constCapture(false).referenceCapture(true));
+        final JexlEngine jexl = builder.create();
+        JexlScript script;
+        Object result;
+        script = jexl.createScript(src);
+        result = script.execute(null);
+        assertEquals(42, result);
+    }
+
+    @Test public void test427a() {
+        String src = "(x, y, z) -> x && y && z";
+        final JexlBuilder builder = new JexlBuilder().features(new 
JexlFeatures().constCapture(true));
+        final JexlEngine jexl = builder.create();
+        JexlScript script;
+        Object result;
+        script = jexl.createScript(src);
+        result = script.execute(null, true, "foo", 42);
+        assertEquals(42, result);
+        result = script.execute(null, true, "", 42);
+        assertEquals("", result);
+    }
+
+    @Test public void test427b() {
+        String src = "(x, y, z) -> x || y || z";
+        final JexlBuilder builder = new JexlBuilder().features(new 
JexlFeatures().constCapture(true));
+        final JexlEngine jexl = builder.create();
+        JexlScript script;
+        Object result;
+        script = jexl.createScript(src);
+        result = script.execute(null, 0, "", 42);
+        assertEquals(42, result);
+        result = script.execute(null, true, 42, null);
+        assertEquals(true, result);
+    }
 }


Reply via email to