Skip to content
Permalink
Browse files
Merge pull request #96 from apache/JEXL-367
JEXL-367
  • Loading branch information
henrib committed May 12, 2022
2 parents 57b6a5a + 247bbdd commit 53b18ef85564e32ff387e4009dc2fc8912479fb7
Showing 8 changed files with 249 additions and 32 deletions.
@@ -59,7 +59,8 @@ public final class JexlFeatures {
private static final String[] F_NAMES = {
"register", "reserved variable", "local variable", "assign/modify",
"global assign/modify", "array reference", "create instance", "loop", "function",
"method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade"
"method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade",
"thin-arrow", "fat-arrow"
};
/** Registers feature ordinal. */
private static final int REGISTER = 0;
@@ -93,6 +94,10 @@ public final class JexlFeatures {
public static final int LEXICAL = 14;
/** Lexical shade feature ordinal. */
public static final int LEXICAL_SHADE = 15;
/** Fat-arrow lambda syntax. */
public static final int THIN_ARROW = 16;
/** Fat-arrow lambda syntax. */
public static final int FAT_ARROW = 17;

/**
* Creates an all-features-enabled instance.
@@ -109,7 +114,8 @@ public JexlFeatures() {
| (1L << STRUCTURED_LITERAL)
| (1L << PRAGMA)
| (1L << ANNOTATION)
| (1L << SCRIPT);
| (1L << SCRIPT)
| (1L << THIN_ARROW);
reservedNames = Collections.emptySet();
nameSpaces = TEST_STR_FALSE;
}
@@ -433,6 +439,46 @@ public boolean supportsLambda() {
return getFeature(LAMBDA);
}

/**
* Sets whether thin-arrow lambda syntax is enabled.
* <p>
* When disabled, parsing a script/expression using syntactic thin-arrow (-&lt;)
* will throw a parsing exception.
* @param flag true to enable, false to disable
* @return this features instance
*/
public JexlFeatures thinArrow(final boolean flag) {
setFeature(THIN_ARROW, flag);
return this;
}

/**
* @return true if thin-arrow lambda syntax is enabled, false otherwise
*/
public boolean supportsThinArrow() {
return getFeature(THIN_ARROW);
}

/**
* Sets whether fat-arrow lambda syntax is enabled.
* <p>
* When disabled, parsing a script/expression using syntactic fat-arrow (=&lt;)
* will throw a parsing exception.
* @param flag true to enable, false to disable
* @return this features instance
*/
public JexlFeatures fatArrow(final boolean flag) {
setFeature(FAT_ARROW, flag);
return this;
}

/**
* @return true if fat-arrow lambda syntax is enabled, false otherwise
*/
public boolean supportsFatArrow() {
return getFeature(FAT_ARROW);
}

/**
* Sets whether pragma constructs are enabled.
* <p>
@@ -18,6 +18,7 @@


import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.JexlScript;
import org.apache.commons.jexl3.parser.*;
@@ -47,6 +48,8 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
protected int indent = 2;
/** accept() relative depth. */
protected int depth = Integer.MAX_VALUE;
/** Arrow symbol. */
protected String arrow = "->";

/**
* Creates a Debugger.
@@ -68,14 +71,45 @@ public void reset() {
depth = Integer.MAX_VALUE;
}

/**
* Tries (hard) to find the features used to parse a node.
* @param node the node
* @return the features or null
*/
protected JexlFeatures getFeatures(JexlNode node) {
JexlNode walk = node;
while(walk != null) {
if (walk instanceof ASTJexlScript) {
ASTJexlScript script = (ASTJexlScript) walk;
return script.getFeatures();
}
walk = walk.jjtGetParent();
}
return null;
}

/**
* Sets the arrow style (fat or thin) depending on features.
* @param node the node to start seeking features from.
*/
protected void setArrowSymbol(JexlNode node) {
JexlFeatures features = getFeatures(node);
if (features != null && features.supportsFatArrow() && !features.supportsThinArrow()) {
arrow = "=>";
} else {
arrow = "->";
}
}

/**
* Position the debugger on the root of an expression.
* @param jscript the expression
* @return true if the expression was a {@link Script} instance, false otherwise
*/
public boolean debug(final JexlExpression jscript) {
if (jscript instanceof Script) {
return debug(((Script) jscript).script);
Script script = (Script) jscript;
return debug(script.script);
}
return false;
}
@@ -87,7 +121,8 @@ public boolean debug(final JexlExpression jscript) {
*/
public boolean debug(final JexlScript jscript) {
if (jscript instanceof Script) {
return debug(((Script) jscript).script);
Script script = (Script) jscript;
return debug(script.script);
}
return false;
}
@@ -111,6 +146,7 @@ public boolean debug(final JexlNode node, final boolean r) {
start = 0;
end = 0;
indentLevel = 0;
setArrowSymbol(node);
if (node != null) {
builder.setLength(0);
cause = node;
@@ -144,6 +180,7 @@ public String data(final JexlNode node) {
start = 0;
end = 0;
indentLevel = 0;
setArrowSymbol(node);
if (node != null) {
builder.setLength(0);
cause = node;
@@ -675,15 +712,22 @@ private static boolean isLambdaExpr(ASTJexlLambda lambda) {
@Override
protected Object visit(final ASTJexlScript node, Object arg) {
Object data = arg;
boolean named = false;
// if lambda, produce parameters
if (node instanceof ASTJexlLambda) {
final ASTJexlLambda lambda = (ASTJexlLambda) node;
final JexlNode parent = node.jjtGetParent();
// use lambda syntax if not assigned
boolean expr = isLambdaExpr(lambda);
final boolean named = parent instanceof ASTAssignment;
if (named && !expr) {
named = node.jjtGetChild(0) instanceof ASTVar;
final boolean assigned = parent instanceof ASTAssignment || named;
if (assigned && !expr) {
builder.append("function");
if (named) {
ASTVar avar = (ASTVar) node.jjtGetChild(0);
builder.append(' ');
builder.append(avar.getName());
}
}
builder.append('(');
final String[] params = lambda.getParameters();
@@ -695,11 +739,11 @@ protected Object visit(final ASTJexlScript node, Object arg) {
}
}
builder.append(')');
if (named && !expr) {
if (assigned && !expr) {
// block follows
builder.append(' ');
} else {
builder.append("->");
builder.append(arrow);
// add a space if lambda expr otherwise block follows
if (expr) {
builder.append(' ');
@@ -711,7 +755,7 @@ protected Object visit(final ASTJexlScript node, Object arg) {
if (num == 1 && !(node instanceof ASTJexlLambda)) {
data = accept(node.jjtGetChild(0), data);
} else {
for (int i = 0; i < num; ++i) {
for (int i = named? 1 : 0; i < num; ++i) {
final JexlNode child = node.jjtGetChild(i);
acceptStatement(child, data);
}
@@ -983,7 +983,18 @@ protected Object runClosure(final Closure closure, final Object data) {
@Override
protected Object visit(final ASTJexlScript script, final Object data) {
if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
return new Closure(this, (ASTJexlLambda) script);
Closure closure = new Closure(this, (ASTJexlLambda) script);
// if the function is named, assign in the local frame
JexlNode child0 = script.jjtGetChild(0);
if (child0 instanceof ASTVar) {
ASTVar var = (ASTVar) child0;
this.visit(var, data);
int symbol = var.getSymbol();
frame.set(symbol, closure);
// make the closure accessible to itself, ie capture the currently set variable after frame creation
closure.setCaptured(symbol, closure);
}
return closure;
}
block = new LexicalFrame(frame, block).defineArgs();
try {
@@ -20,8 +20,8 @@
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.internal.Scope;
import org.apache.commons.jexl3.internal.LexicalScope;
import org.apache.commons.jexl3.internal.Scope;

import java.io.BufferedReader;
import java.io.IOException;
@@ -205,7 +205,7 @@ protected JexlFeatures getFeatures() {
* regain access after parsing to known which / how-many registers are needed. </p>
* @return the named register map
*/
protected Scope getFrame() {
protected Scope getScope() {
return scope;
}

@@ -381,6 +381,31 @@ private boolean declareSymbol(final int symbol) {
return block == null || block.declareSymbol(symbol);
}

/**
* Declares a local function.
* @param variable the identifier used to declare
* @param token the variable name toekn
*/
protected void declareFunction(final ASTVar variable, final Token token, Scope scope) {
final String name = token.image;
// function foo() ... <=> const foo = ()->...
if (scope == null) {
scope = new Scope(null);
}
final int symbol = scope.declareVariable(name, true, true);
variable.setSymbol(symbol, name);
if (scope.isCapturedSymbol(symbol)) {
variable.setCaptured(true);
}
// lexical feature error
if (!declareSymbol(symbol)) {
if (getFeatures().isLexical()) {
throw new JexlException(variable, name + ": variable is already declared");
}
variable.setRedefined(true);
}
}

/**
* Declares a local variable.
* <p> This method creates an new entry in the symbol map. </p>
@@ -395,7 +420,7 @@ protected void declareVariable(final ASTVar variable, final Token token, boolean
throwFeatureException(JexlFeatures.LOCAL_VAR, token);
}
if (scope == null) {
scope = new Scope(null, (String[]) null);
scope = new Scope(null);
}
final int symbol = scope.declareVariable(name, lexical, constant);
variable.setSymbol(symbol, name);
@@ -580,7 +605,6 @@ protected void jjtreeCloseNodeScope(final JexlNode node) {
if (script.getScope() != scope) {
script.setScope(scope);
}
popScope();
} else if (ASSIGN_NODES.contains(node.getClass())) {
final JexlNode lv = node.jjtGetChild(0);
if (!lv.isLeftValue()) {
@@ -600,6 +624,23 @@ protected void jjtreeCloseNodeScope(final JexlNode node) {
featureController.controlNode(node);
}

/**
* Check fat vs thin arrow syntax feature.
* @param token the arrow token
*/
protected void checkLambda(Token token) {
final String arrow = token.image;
if ("->".equals(arrow)) {
if (!getFeatures().supportsThinArrow()) {
throwFeatureException(JexlFeatures.THIN_ARROW, token);
}
return;
}
if ("=>".equals(arrow) && !getFeatures().supportsFatArrow()) {
throwFeatureException(JexlFeatures.FAT_ARROW, token);
}
}

/**
* Throws Ambiguous exception.
* <p>Seeks the end of the ambiguous statement to recover.

0 comments on commit 53b18ef

Please sign in to comment.