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"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); + } }