Skip to content

Commit

Permalink
JEXL-425, JEXL-426, JEXL-427 : - prototype code respectively through …
Browse files Browse the repository at this point in the history
…option flag (strictInterpolation) and feature flag (referenceCapture) ;

- made short-circuit operators behavior closer to EcmaScript;
  • Loading branch information
henrib committed Aug 27, 2024
1 parent d49fec4 commit ba11d0e
Show file tree
Hide file tree
Showing 17 changed files with 343 additions and 121 deletions.
8 changes: 8 additions & 0 deletions src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
Original file line number Diff line number Diff line change
Expand Up @@ -2070,6 +2070,14 @@ public BigInteger toBigInteger(final Object val) {
+ 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>
Expand Down
27 changes: 18 additions & 9 deletions src/main/java/org/apache/commons/jexl3/JexlBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -367,15 +367,15 @@ public Boolean debug() {
}

/**
* 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() {
Expand Down Expand Up @@ -675,6 +675,15 @@ public JexlBuilder strict(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;
Expand Down
29 changes: 27 additions & 2 deletions src/main/java/org/apache/commons/jexl3/JexlFeatures.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -352,6 +354,22 @@ public JexlFeatures constCapture(final boolean flag) {
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) {
Expand Down Expand Up @@ -744,6 +762,13 @@ public boolean supportsConstCapture() {
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
Expand Down
24 changes: 22 additions & 2 deletions src/main/java/org/apache/commons/jexl3/JexlOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@
* <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. */
Expand All @@ -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;
Expand Down Expand Up @@ -291,6 +296,13 @@ public boolean isStrictArithmetic() {
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
Expand Down Expand Up @@ -345,7 +357,7 @@ public void setCancellable(final boolean flag) {
* @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);
}

/**
Expand Down Expand Up @@ -459,6 +471,14 @@ public void setStrictArithmetic(final boolean stricta) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ void captureSelfIfRecursive(final Frame parentFrame, final int symbol) {
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);
Expand Down
95 changes: 84 additions & 11 deletions src/main/java/org/apache/commons/jexl3/internal/Frame.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -58,11 +59,31 @@ Frame assign(final Object... values) {
}
// 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
Expand All @@ -72,6 +93,15 @@ Object get(final int s) {
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
Expand Down Expand Up @@ -117,14 +147,57 @@ Object[] nocycleStack(final Closure closure) {
}
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);
}
}
Loading

0 comments on commit ba11d0e

Please sign in to comment.