Skip to content
Permalink
Browse files
JEXL-367: adding fat-arrow vs thin-arrow lambda syntax; add named fun…
…ction syntax;
  • Loading branch information
henrib committed May 4, 2022
1 parent 3e5c862 commit b7a89c985324f94376da51278c93cd347fe11ac8
Showing 8 changed files with 244 additions and 21 deletions.
@@ -497,7 +497,7 @@ protected boolean isNumberable(final Object o) {
}

/**
* Given a Number, return back the value using the smallest type the result
* Given a Number, return the value using the smallest type the result
* will fit into.
* <p>This works hand in hand with parameter 'widening' in java
* method calls, e.g. a call to substring(int,int) with an int and a long
@@ -1390,8 +1390,8 @@ protected int compare(final Object left, final Object right, final String operat
return 0;
}
if (isNumberable(left) || isNumberable(right)) {
final long lhs = toLong(left);
final long rhs = toLong(right);
final long lhs = toLong(left instanceof String? Double.parseDouble((String) left) : left);
final long rhs = toLong(right instanceof String? Double.parseDouble((String) right) : right);
if (lhs < rhs) {
return -1;
}
@@ -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 (->)
* 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 (=>)
* 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);
}
@@ -980,7 +980,15 @@ 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);
frame.set(var.getSymbol(), 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;
@@ -376,6 +376,27 @@ 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;
final int symbol = scope.declareVariable(name);
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>
@@ -561,6 +582,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.
@@ -135,7 +135,8 @@ TOKEN_MGR_DECLS : {
| < FALSE : "false" > { popDot(); }
| < RETURN : "return" > { popDot(); }
| < FUNCTION : "function" > { popDot(); }
| < LAMBDA : "->" | "=>" > { popDot(); }
| < LAMBDA : "->" > { popDot(); }
| < FATARROW : "=>" > { popDot(); }
| < BREAK : "break" > { popDot(); }
| < CONTINUE : "continue" > { popDot(); }
| < PRAGMA : "#pragma" > { popDot(); }
@@ -443,6 +444,13 @@ void DeclareVar(boolean lexical) #Var :
{
t=<IDENTIFIER> { declareVariable(jjtThis, t, lexical); }
}
void DeclareFunction(Scope scope) #Var :
{
Token t;
}
{
t=<IDENTIFIER> { declareFunction(jjtThis, t, scope); }
}

void Pragma() #void :
{
@@ -860,23 +868,26 @@ void Parameters() #void : {}

void LambdaLookahead() #void : {}
{
<FUNCTION> Parameters()
<FUNCTION> (<IDENTIFIER>)? Parameters()
|
Parameters() <LAMBDA>
Parameters() (<LAMBDA> | <FATARROW>)
|
Parameter() <LAMBDA>
Parameter() (<LAMBDA> | <FATARROW>)
}

void Lambda() #JexlLambda :
{
Token arrow;
Token name;
Scope scope = getFrame();
pushFrame();
}
{
{ pushUnit(jjtThis); } <FUNCTION> Parameters() ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); }
{ pushUnit(jjtThis); } <FUNCTION> (LOOKAHEAD(<IDENTIFIER>) DeclareFunction(scope))? Parameters() ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); }
|
{ pushUnit(jjtThis); } Parameters() <LAMBDA> ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); }
{ pushUnit(jjtThis); } Parameters() (arrow=<LAMBDA> | arrow=<FATARROW>) ( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); }
|
{ pushUnit(jjtThis); } Parameter() <LAMBDA> ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); }
{ pushUnit(jjtThis); } Parameter() (arrow=<LAMBDA> | arrow=<FATARROW>)( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); }
}


@@ -768,4 +768,16 @@ private Object run361d(JexlEngine jexl) {
Object result = script.execute(null);
return result;
}


@Test public void test367() {
String text = "var toto; function foo(x) { x }; var tata = 3; foo(3)";
JexlEngine jexl = new JexlBuilder().safe(true).create();
JexlScript script = jexl.createScript(text);
Object result = script.execute(null);
Assert.assertEquals(3, result);
String s0 = script.getParsedText();
String s1 = script.getSourceText();
Assert.assertNotEquals(s0, s1);
}
}

0 comments on commit b7a89c9

Please sign in to comment.