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 <[email protected]>
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"e;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);
+ }
}