Skip to content
Permalink
Browse files
JEXL-365: modify parser to accommodate lambda expression syntax;
- minor Debugger improvement to more accurately generate lambdas;
- added tests;
  • Loading branch information
henrib committed Apr 11, 2022
1 parent 9f8f795 commit bb0103e7d26744499196dc34cdf012ca67622f83
Showing 6 changed files with 197 additions and 73 deletions.
@@ -38,6 +38,7 @@ allow fine-tuning the scripting integration into any project.

New Features in 3.3:
====================
* JEXL-365: Lambda expressions
* JEXL-363: Allow retrieving captured variables in script
* JEXL-359: Allow per-operator arithmetic handling of null arguments
* JEXL-357: Configure accessible packages/classes/methods/fields
@@ -297,6 +297,40 @@ protected Object accept(final JexlNode node, final Object data) {
return value;
}

/**
* Whether a node is a statement (vs an expression).
* @param child the node
* @return true if node is a statement
*/
private static boolean isStatement(JexlNode child) {
return child instanceof ASTJexlScript
|| child instanceof ASTJexlLambda
|| child instanceof ASTBlock
|| child instanceof ASTIfStatement
|| child instanceof ASTForeachStatement
|| child instanceof ASTWhileStatement
|| child instanceof ASTDoWhileStatement
|| child instanceof ASTAnnotation;
}

/**
* Whether a script or expression ends with a semicolumn.
* @param cs the string
* @return true if a semicolumn is the last non-whitespace character
*/
private static boolean semicolTerminated(CharSequence cs) {
for(int i = cs.length() - 1; i >= 0; --i) {
char c = cs.charAt(i);
if (c == ';') {
return true;
}
if (!Character.isWhitespace(c)) {
break;
}
}
return false;
}

/**
* Adds a statement node to the rebuilt expression.
* @param child the child node
@@ -316,13 +350,7 @@ protected Object acceptStatement(final JexlNode child, final Object data) {
final Object value = accept(child, data);
depth += 1;
// blocks, if, for & while don't need a ';' at end
if (!(child instanceof ASTJexlScript
|| child instanceof ASTBlock
|| child instanceof ASTIfStatement
|| child instanceof ASTForeachStatement
|| child instanceof ASTWhileStatement
|| child instanceof ASTDoWhileStatement
|| child instanceof ASTAnnotation)) {
if (!isStatement(child) && !semicolTerminated(builder)) {
builder.append(';');
if (indent > 0) {
builder.append('\n');
@@ -333,6 +361,7 @@ protected Object acceptStatement(final JexlNode child, final Object data) {
return value;
}


/**
* Checks if a terminal node is the cause to debug & adds its representation to the rebuilt expression.
* @param node the child node
@@ -629,8 +658,8 @@ protected Object visit(final ASTGTNode node, final Object data) {
*/
protected boolean needQuotes(final String str) {
return QUOTED_IDENTIFIER.matcher(str).find()
|| "size".equals(str)
|| "empty".equals(str);
|| "size".equals(str)
|| "empty".equals(str);
}

@Override
@@ -701,19 +730,25 @@ protected String visitParameter(final String p, final Object data) {
return p;
}

private static boolean isLambdaExpr(ASTJexlLambda lambda) {
return lambda.jjtGetNumChildren() == 1 && !isStatement(lambda.jjtGetChild(0));
}

@Override
protected Object visit(final ASTJexlScript node, Object arg) {
Object data = arg;
// 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) {
if (named && !expr) {
builder.append("function");
}
builder.append('(');
final String[] params = node.getParameters();
final String[] params = lambda.getParameters();
if (params != null && params.length > 0) {
builder.append(visitParameter(params[0], data));
for (int p = 1; p < params.length; ++p) {
@@ -722,12 +757,16 @@ protected Object visit(final ASTJexlScript node, Object arg) {
}
}
builder.append(')');
if (named) {
if (named && !expr) {
// block follows
builder.append(' ');
} else {
builder.append("->");
// add a space if lambda expr otherwise block follows
if (expr) {
builder.append(' ');
}
}
// we will need a block...
}
// no parameters or done with them
final int num = node.jjtGetNumChildren();
@@ -314,7 +314,7 @@ protected Object getVariable(final Frame frame, final LexicalScope block, final
return undefinedVariable(identifier, name);
}
// a local var ?
if ((symbol >= 0) && frame.has(symbol)) {
if (symbol >= 0 && frame.has(symbol)) {
final Object value = frame.get(symbol);
// not out of scope with no lexical shade ?
if (value != Scope.UNDEFINED) {
@@ -516,61 +516,61 @@ void AssignmentExpression() #void : {}
void ConditionalExpression() #void : {}
{
ConditionalOrExpression()
(
( LOOKAHEAD(2) (
<QMARK> Expression() <COLON> Expression() #TernaryNode(3)
|
<ELVIS> Expression() #TernaryNode(2)
|
<NULLP> Expression() #NullpNode(2)
)?
) )?
}

void ConditionalOrExpression() #void : {}
{
ConditionalAndExpression()
( (<OR>|<_OR>) ConditionalAndExpression() #OrNode(2) )*
( LOOKAHEAD(2) ( (<OR>|<_OR>) ConditionalAndExpression() #OrNode(2) ) )*
}

void ConditionalAndExpression() #void : {}
{
InclusiveOrExpression()
( (<AND>|<_AND>) InclusiveOrExpression() #AndNode(2) )*
( LOOKAHEAD(2) ( (<AND>|<_AND>) InclusiveOrExpression() #AndNode(2) ) )*
}

void InclusiveOrExpression() #void : {}
{
ExclusiveOrExpression()
( <or> ExclusiveOrExpression() #BitwiseOrNode(2) )*
( LOOKAHEAD(2) ( <or> ExclusiveOrExpression() #BitwiseOrNode(2) ) )*
}

void ExclusiveOrExpression() #void : {}
{
AndExpression()
( <xor> AndExpression() #BitwiseXorNode(2) )*
( LOOKAHEAD(2) ( <xor> AndExpression() #BitwiseXorNode(2) ) )*
}

void AndExpression() #void : {}
{
EqualityExpression()
( <and> EqualityExpression() #BitwiseAndNode(2) )*
( LOOKAHEAD(2) (<and> EqualityExpression() #BitwiseAndNode(2) ) )*
}

void EqualityExpression() #void : {}
{
RelationalExpression()
(
( LOOKAHEAD(2) (
(<eq> | <EQ>) RelationalExpression() #EQNode(2)
|
(<ne> | <NE>) RelationalExpression() #NENode(2)
|
<range> RelationalExpression() #RangeNode(2) // range
)?
) )?
}

void RelationalExpression() #void : {}
{
AdditiveExpression()
(
( LOOKAHEAD(2) (
(<lt> |<LT>) AdditiveExpression() #LTNode(2)
|
(<gt> | <GT>) AdditiveExpression() #GTNode(2)
@@ -590,7 +590,7 @@ void RelationalExpression() #void : {}
<eeq> AdditiveExpression() #EWNode(2) // ends with
|
<ene> AdditiveExpression() #NEWNode(2) // not ends with
)?
) )?
}

/***************************************
@@ -610,13 +610,13 @@ void AdditiveExpression() #void : {}
void MultiplicativeExpression() #void : {}
{
UnaryExpression()
(
( LOOKAHEAD(2) (
<mult> UnaryExpression() #MulNode(2)
|
(<div>|<DIV>) UnaryExpression() #DivNode(2)
|
(<mod>|<MOD>) UnaryExpression() #ModNode(2)
)*
) )*
}

void UnaryExpression() #void : {}
@@ -844,11 +844,11 @@ void Lambda() #JexlLambda :
pushFrame();
}
{
{ pushUnit(jjtThis); } <FUNCTION> Parameters() Block() { popUnit(jjtThis); }
{ pushUnit(jjtThis); } <FUNCTION> Parameters() ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); }
|
{ pushUnit(jjtThis); } Parameters() <LAMBDA> Block() { popUnit(jjtThis); }
{ pushUnit(jjtThis); } Parameters() <LAMBDA> ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); }
|
{ pushUnit(jjtThis); } Parameter() <LAMBDA> Block() { popUnit(jjtThis); }
{ pushUnit(jjtThis); } Parameter() <LAMBDA> ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); }
}


0 comments on commit bb0103e

Please sign in to comment.