You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2025/07/27 09:52:29 UTC
(commons-jexl) branch JEXL-442 updated: JEXL-442: parse Jxlt (interpolation) based nodes at script parsing time; - pass the scope down Jxlt parsing to solve local variables; - enabled 'child' parser to allow sharing of scopes between the outer script and the inner interpolation expressions;
This is an automated email from the ASF dual-hosted git repository.
henrib pushed a commit to branch JEXL-442
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
The following commit(s) were added to refs/heads/JEXL-442 by this push:
new 483bfe74 JEXL-442: parse Jxlt (interpolation) based nodes at script parsing time; - pass the scope down Jxlt parsing to solve local variables; - enabled 'child' parser to allow sharing of scopes between the outer script and the inner interpolation expressions;
483bfe74 is described below
commit 483bfe74b92efd32f0b9cfe85db9ca16bdffd716
Author: Henrib <hb...@gmail.com>
AuthorDate: Sun Jul 27 11:52:20 2025 +0200
JEXL-442: parse Jxlt (interpolation) based nodes at script parsing time;
- pass the scope down Jxlt parsing to solve local variables;
- enabled 'child' parser to allow sharing of scopes between the outer script and the inner interpolation expressions;
---
.../org/apache/commons/jexl3/internal/Engine.java | 5 +-
.../org/apache/commons/jexl3/internal/Frame.java | 21 +--
.../apache/commons/jexl3/internal/Interpreter.java | 64 +--------
.../org/apache/commons/jexl3/internal/Scope.java | 26 ++--
.../commons/jexl3/internal/TemplateEngine.java | 42 +++---
.../commons/jexl3/internal/TemplateScript.java | 8 +-
.../jexl3/parser/ASTIdentifierAccessJxlt.java | 20 ++-
.../commons/jexl3/parser/ASTJxltLiteral.java | 29 ++--
.../org/apache/commons/jexl3/parser/JexlNode.java | 19 +--
.../apache/commons/jexl3/parser/JexlParser.java | 152 ++++++++++++---------
.../commons/jexl3/parser/JexlScriptParser.java | 14 ++
.../org/apache/commons/jexl3/parser/Parser.jjt | 14 +-
.../apache/commons/jexl3/PropertyAccessTest.java | 22 ++-
13 files changed, 213 insertions(+), 223 deletions(-)
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index 519e4811..b6a7adb0 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -796,7 +796,7 @@ public class Engine extends JexlEngine implements JexlUberspect.ConstantResolver
* @throws JexlException if any error occurred during parsing
*/
protected ASTJexlScript parse(final JexlInfo info, final boolean expr, final String src, final Scope scope) {
- return parse(info, expr? this.expressionFeatures : this.scriptFeatures, src, scope);
+ return parser.jxltParse(info, expr? this.expressionFeatures : this.scriptFeatures, src, scope);
}
/**
@@ -852,14 +852,13 @@ public class Engine extends JexlEngine implements JexlUberspect.ConstantResolver
* @param info the JexlInfo
* @param expr whether to parse an expression or a script
* @param src the source to parse
- * @param scope the scope, may be null
+ * @param scope the scope, maybe null
* @return the parsed tree
*/
protected ASTJexlScript jxltParse(final JexlInfo info, final boolean expr, final String src, final Scope scope) {
return parse(info, expr, src, scope);
}
-
/**
* Processes jexl.module.ns pragma.
*
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 430388ea..eed58905 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Frame.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Frame.java
@@ -24,8 +24,6 @@ import java.util.concurrent.atomic.AtomicReference;
* @since 3.0
*/
public class Frame {
- /** The parent frame. */
- private final Frame parent;
/** The scope. */
private final Scope scope;
/** The actual stack frame. */
@@ -39,8 +37,7 @@ public class Frame {
* @param r the stack frame
* @param c the number of curried parameters
*/
- protected Frame(final Frame f, Scope s, final Object[] r, final int c) {
- parent = f;
+ protected Frame(Scope s, final Object[] r, final int c) {
scope = s;
stack = r;
curried = c;
@@ -80,7 +77,7 @@ public class Frame {
* @return a new instance of frame
*/
Frame newFrame(final Scope s, final Object[] r, final int c) {
- return new Frame(this, s, r, c);
+ return new Frame(s, r, c);
}
/**
@@ -119,14 +116,6 @@ public class Frame {
return scope;
}
- /**
- * Gets the parent frame.
- * @return the parent frame or null if this is the root frame
- */
- public Frame getParent() {
- return parent;
- }
-
/**
* Gets this script unbound parameters, i.e. parameters not bound through curry().
* @return the parameter names
@@ -170,13 +159,13 @@ public class Frame {
* Pass-by-reference frame.
*/
class ReferenceFrame extends Frame {
- ReferenceFrame(final Frame f, final Scope s, final Object[] r, final int c) {
- super(f, s, r, c);
+ ReferenceFrame(final Scope s, final Object[] r, final int c) {
+ super(s, r, c);
}
@Override
Frame newFrame(final Scope s, final Object[] r, final int c) {
- return new ReferenceFrame(this, s, r, c);
+ return new ReferenceFrame(s, r, c);
}
@Override
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 0381fb44..36ee9b9a 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -28,7 +28,6 @@ import org.apache.commons.jexl3.JexlArithmetic;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
-import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.JexlOperator;
import org.apache.commons.jexl3.JexlOptions;
import org.apache.commons.jexl3.JexlScript;
@@ -161,7 +160,7 @@ public class Interpreter extends InterpreterBase {
final int symbol = methodIdentifier.getSymbol();
methodName = methodIdentifier.getName();
functor = null;
- // is it a global or local variable ?
+ // is it a global or local variable?
if (target == context) {
if (frame != null && frame.has(symbol)) {
functor = frame.get(symbol);
@@ -192,7 +191,7 @@ public class Interpreter extends InterpreterBase {
// solving the call site
final CallDispatcher call = new CallDispatcher(node, cacheable);
try {
- // do we have a cached version method/function name ?
+ // do we have a cached version method/function name?
final Object eval = call.tryEval(target, methodName, argv);
if (JexlEngine.TRY_FAILED != eval) {
return eval;
@@ -254,11 +253,11 @@ public class Interpreter extends InterpreterBase {
return ((JexlMethod) functor).invoke(target, argv);
}
final String mCALL = "call";
- // may be a generic callable, try a 'call' method
+ // maybe a generic callable, try a 'call' method
if (call.isTargetMethod(functor, mCALL, argv)) {
return call.eval(mCALL);
}
- // functor is a var, may be method is a global one ?
+ // functor is a var, may be method is a global one?
if (isavar) {
if (call.isContextMethod(methodName, argv)) {
return call.eval(methodName);
@@ -373,27 +372,6 @@ public class Interpreter extends InterpreterBase {
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
- */
- public <NODE extends JexlNode & JexlNode.JxltHandle> void parseJxltHandle(final NODE node) {
- if (node.getExpression() == null) {
- final TemplateEngine jxlt = jexl.jxlt();
- JexlInfo info = node.jexlInfo();
- if (this.block != null) {
- info = new JexlNode.Info(node, info);
- }
- Scope newScope = new Scope(frame != null ? frame.getScope() : null);
- final JxltEngine.Expression expr = jxlt.parseExpression(info, node.getExpressionSource(), newScope);
- node.setExpression(expr);
- node.setScope(newScope);
- }
- }
-
/**
* Evaluates a JxltHandle node.
* <p>This parses and stores the JXLT template if necessary (upon first execution)</p>
@@ -403,27 +381,9 @@ public class Interpreter extends InterpreterBase {
*/
private <NODE extends JexlNode & JexlNode.JxltHandle> Object evalJxltHandle(final 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);
- }
- Scope newScope = new Scope(frame != null ? frame.getScope() : null);
- expr = jxlt.parseExpression(info, node.getExpressionSource(), newScope);
- node.setExpression(expr);
- node.setScope(newScope);
- }
// internal classes to evaluate in context
if (expr instanceof TemplateEngine.TemplateExpression) {
- Scope exprScope = node.getScope();
- String[] symbols = exprScope.getSymbols();
- Object[] values = new Object[symbols.length];
- for (int v = 0; v < values.length; ++v) {
- values[v] = evalSymbol(symbols[v]);
- }
- Frame exprFrame = new Frame(frame, exprScope, values, 0);
- final Object eval = ((TemplateEngine.TemplateExpression) expr).evaluate(context, exprFrame, options);
+ final Object eval = ((TemplateEngine.TemplateExpression) expr).evaluate(context, frame, options);
if (eval != null) {
final String inter = eval.toString();
if (options.isStrictInterpolation()) {
@@ -436,20 +396,6 @@ public class Interpreter extends InterpreterBase {
return null;
}
- Object evalSymbol(String symbol) {
- Frame f = frame;
- while (f != null) {
- Scope scope = f.getScope();
- Integer index = scope.getSymbol(symbol);
- if (index != null && f.has(index)) {
- Object value = f.get(index);
- return value;
- }
- f = f.getParent();
- }
- return null;
- }
-
/**
* Executes an assignment with an optional side effect operator.
* @param node the node
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 35ba4a5d..b90d3840 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
@@ -67,7 +67,7 @@ public final class Scope {
*/
private Map<String, Integer> namedVariables;
/**
- * The map of local captured variables to parent scope variables, ie closure.
+ * The map of local captured variables to parent scope variables, i.e., closure.
*/
private Map<Integer, Integer> capturedVariables;
/**
@@ -131,8 +131,8 @@ public final class Scope {
newFrame = frame.newFrame(this, arguments, 0);
} else {
newFrame = ref
- ? new ReferenceFrame(frame, this, arguments, 0)
- : new Frame(frame, this, arguments, 0);
+ ? new ReferenceFrame(this, arguments, 0)
+ : new Frame(this, arguments, 0);
}
return newFrame.assign(args);
}
@@ -140,7 +140,7 @@ public final class Scope {
/**
* Declares a parameter.
* <p>
- * This method creates an new entry in the symbol map.
+ * This method creates a new entry in the symbol map.
* </p>
* @param param the parameter name
* @return the register index storing this variable
@@ -161,7 +161,7 @@ public final class Scope {
/**
* Declares a local variable.
* <p>
- * This method creates an new entry in the symbol map.
+ * This method creates a new entry in the symbol map.
* </p>
* @param varName the variable name
* @return the register index storing this variable
@@ -196,7 +196,7 @@ public final class Scope {
}
/**
- * Gets the captured index of a given symbol, ie the target index of a symbol in a child scope.
+ * Gets the captured index of a given symbol, i.e., the target index of a symbol in a child scope.
* @param symbol the symbol index
* @return the target symbol index or null if the symbol is not captured
*/
@@ -213,7 +213,7 @@ public final class Scope {
}
/**
- * Gets the index of a captured symbol, ie the source index of a symbol in a parent scope.
+ * Gets the index of a captured symbol, i.e., the source index of a symbol in a parent scope.
* @param symbol the symbol index
* @return the source symbol index or -1 if the symbol is not captured
*/
@@ -223,7 +223,7 @@ public final class Scope {
}
/**
- * Gets this script captured symbols names, i.e. local variables defined in outer scopes and used
+ * Gets this script captured symbols names, i.e., local variables defined in outer scopes and used
* by this scope.
* @return the captured names
*/
@@ -262,7 +262,7 @@ public final class Scope {
}
/**
- * Gets this script parameters, i.e. symbols assigned before creating local variables.
+ * Gets this script parameters, i.e., symbols assigned before creating local variables.
* @return the parameter names
*/
public String[] getParameters() {
@@ -295,8 +295,8 @@ public final class Scope {
}
/**
- * Checks whether an identifier is a local variable or argument, ie a symbol.
- * If this fails, look in parents for symbol that can be captured.
+ * Checks whether an identifier is a local variable or argument, i.e., a symbol.
+ * If this fails, look within parents for a symbol that can be captured.
* @param name the symbol name
* @return the symbol index
*/
@@ -305,7 +305,7 @@ public final class Scope {
}
/**
- * Checks whether an identifier is a local variable or argument, ie a symbol.
+ * Checks whether an identifier is a local variable or argument, i.e., a symbol.
* @param name the symbol name
* @param capture whether solving by capturing a parent symbol is allowed
* @return the symbol index
@@ -330,7 +330,7 @@ public final class Scope {
}
/**
- * Gets this script symbols names, i.e. parameters and local variables.
+ * Gets this script symbols names, i.e., parameters and local variables.
* @return the symbol names
*/
public String[] getSymbols() {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
index 73a9ad4b..64e91583 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
@@ -47,7 +47,7 @@ public final class TemplateEngine extends JxltEngine {
* Abstract the source fragments, verbatim or immediate typed text blocks.
*/
static final class Block {
- /** The type of block, verbatim or directive. */
+ /** The type of block: verbatim or directive. */
private final BlockType type;
/** The block start line info. */
private final int line;
@@ -109,7 +109,7 @@ public final class TemplateEngine extends JxltEngine {
enum BlockType {
/** Block is to be output "as is" but may be a unified expression. */
VERBATIM,
- /** Block is a directive, ie a fragment of JEXL code. */
+ /** Block is a directive, i.e., a fragment of JEXL code. */
DIRECTIVE
}
@@ -149,7 +149,7 @@ public final class TemplateEngine extends JxltEngine {
for (final TemplateExpression expr : exprs) {
value = expr.evaluate(interpreter);
if (value != null) {
- strb.append(value.toString());
+ strb.append(value);
}
}
return strb.toString();
@@ -234,7 +234,7 @@ public final class TemplateEngine extends JxltEngine {
@Override
public StringBuilder asString(final StringBuilder strb) {
if (value != null) {
- strb.append(value.toString());
+ strb.append(value);
}
return strb;
}
@@ -310,7 +310,7 @@ public final class TemplateEngine extends JxltEngine {
}
/**
- * Builds an TemplateExpression from a source, performs checks.
+ * Builds a TemplateExpression from a source, performs checks.
* @param el the unified el instance
* @param source the source TemplateExpression
* @return an TemplateExpression
@@ -524,15 +524,15 @@ public final class TemplateEngine extends JxltEngine {
private enum ParseState {
/** Parsing a constant. */
CONST,
- /** Parsing after $ . */
+ /** Parsing after <code>$</code> . */
IMMEDIATE0,
- /** Parsing after # . */
+ /** Parsing after <code>#</code> . */
DEFERRED0,
- /** Parsing after ${ . */
+ /** Parsing after <code>${</code> . */
IMMEDIATE1,
- /** Parsing after #{ . */
+ /** Parsing after <code>#{</code> . */
DEFERRED1,
- /** Parsing after \ . */
+ /** Parsing after <code>\</code> . */
ESCAPE
}
@@ -578,7 +578,7 @@ public final class TemplateEngine extends JxltEngine {
* @param context the context storing global variables
* @param options flags and properties that can alter the evaluation behavior.
* @return the expression value
- * @throws JexlException
+ * @throws JexlException if expression evaluation fails
*/
protected final Object evaluate(final JexlContext context, final Frame frame, final JexlOptions options) {
try {
@@ -668,7 +668,7 @@ public final class TemplateEngine extends JxltEngine {
* @param context the context storing global variables
* @param opts flags and properties that can alter the evaluation behavior.
* @return the expression value
- * @throws JexlException
+ * @throws JexlException if expression preparation fails
*/
protected final TemplateExpression prepare(final JexlContext context, final Frame frame, final JexlOptions opts) {
try {
@@ -693,7 +693,7 @@ public final class TemplateEngine extends JxltEngine {
asString(strb);
if (source != this) {
strb.append(" /*= ");
- strb.append(source.toString());
+ strb.append(source);
strb.append(" */");
}
return strb.toString();
@@ -748,7 +748,7 @@ public final class TemplateEngine extends JxltEngine {
strb.append(action);
if (expr != null) {
strb.append(" '");
- strb.append(expr.toString());
+ strb.append(expr);
strb.append("'");
}
final Throwable cause = xany.getCause();
@@ -827,7 +827,7 @@ public final class TemplateEngine extends JxltEngine {
/** The first character for deferred expressions. */
final char deferredChar;
- /** Whether expressions can use JEXL script or only expressions (ie, no for, var, etc). */
+ /** Whether expressions can use JEXL script or only expressions (i.e., no for, var, etc). */
final boolean noscript;
/**
@@ -863,13 +863,17 @@ public final class TemplateEngine extends JxltEngine {
@Override
public JxltEngine.Expression createExpression(final JexlInfo jexlInfo, final String expression) {
+ return createExpression(jexlInfo, expression, null);
+ }
+
+ public JxltEngine.Expression createExpression(final JexlInfo jexlInfo, final String expression, final Scope scope) {
final JexlInfo info = jexlInfo == null ? jexl.createInfo() : jexlInfo;
Exception xuel = null;
TemplateExpression stmt = null;
try {
stmt = cache.get(expression);
if (stmt == null) {
- stmt = parseExpression(info, expression, null);
+ stmt = parseExpression(info, expression, scope);
cache.put(expression, stmt);
}
} catch (final JexlException xjexl) {
@@ -1115,7 +1119,7 @@ public final class TemplateEngine extends JxltEngine {
* @param source the source reader
* @return the list of blocks
*/
- protected List<Block> readTemplate(final String prefix, final Reader source) {
+ List<Block> readTemplate(final String prefix, final Reader source) {
final ArrayList<Block> blocks = new ArrayList<>();
final BufferedReader reader;
if (source instanceof BufferedReader) {
@@ -1159,7 +1163,7 @@ public final class TemplateEngine extends JxltEngine {
// still a directive
strb.append(line.subSequence(prefixLen, line.length()));
}
- } else if (type == BlockType.VERBATIM) {
+ } else { //if (type == BlockType.VERBATIM) {
// switch to directive if necessary
prefixLen = startsWith(line, prefix);
if (prefixLen >= 0) {
@@ -1191,7 +1195,7 @@ public final class TemplateEngine extends JxltEngine {
* @param pattern the pattern to match at start of sequence
* @return the first position after end of pattern if it matches, -1 otherwise
*/
- protected int startsWith(final CharSequence sequence, final CharSequence pattern) {
+ int startsWith(final CharSequence sequence, final CharSequence pattern) {
final int length = sequence.length();
int s = 0;
while (s < length && Character.isSpaceChar(sequence.charAt(s))) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
index 3db934e7..a210a56e 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
@@ -180,7 +180,13 @@ public final class TemplateScript implements JxltEngine.Template {
// no node info means this verbatim is surrounded by comments markers;
// expr at this index is never called
if (ji != null) {
- te = jxlt.parseExpression(ji, block.getBody(), scopeOf(ji));
+ Scope es = scopeOf(ji);
+ if (es == null) {
+ // if the scope is null, it means the verbatim is at the top level
+ // of the script, so we use the script scope
+ es = scope;
+ }
+ te = jxlt.parseExpression(ji, block.getBody(), es);
} else {
te = jxlt.new ConstantExpression(block.getBody(), null);
}
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 212cc496..59f9972d 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccessJxlt.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccessJxlt.java
@@ -17,15 +17,16 @@
package org.apache.commons.jexl3.parser;
+import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JxltEngine;
import org.apache.commons.jexl3.internal.Scope;
+import org.apache.commons.jexl3.internal.TemplateEngine;
/**
* x.`expr`.
*/
public class ASTIdentifierAccessJxlt extends ASTIdentifierAccess implements JexlNode.JxltHandle {
protected transient JxltEngine.Expression jxltExpression;
- private Scope scope;
ASTIdentifierAccessJxlt(final int id) {
super(id);
@@ -51,9 +52,16 @@ public class ASTIdentifierAccessJxlt extends ASTIdentifierAccess implements Jexl
jxltExpression = tp;
}
- @Override
- public Scope getScope() { return scope; }
-
- @Override
- public void setScope(final Scope scope) { this.scope = scope; }
+ public void setIdentifier(final String src, final Scope scope) {
+ super.setIdentifier(src);
+ if (src != null && !src.isEmpty()) {
+ JexlEngine jexl = JexlEngine.getThreadEngine();
+ if (jexl != null) {
+ JxltEngine jxlt = jexl.createJxltEngine();
+ if (jxlt instanceof TemplateEngine) {
+ this.jxltExpression = ((TemplateEngine) jxlt).createExpression(jexlInfo(), src, scope);
+ }
+ }
+ }
+ }
}
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 1e52d487..fda769c5 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTJxltLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTJxltLiteral.java
@@ -16,18 +16,18 @@
*/
package org.apache.commons.jexl3.parser;
+import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JxltEngine;
import org.apache.commons.jexl3.internal.Scope;
+import org.apache.commons.jexl3.internal.TemplateEngine;
-public final class ASTJxltLiteral extends JexlNode implements JexlNode.JxltHandle{
+public final class ASTJxltLiteral extends JexlNode implements JexlNode.JxltHandle {
/** Serial uid.*/
private static final long serialVersionUID = 1L;
/** The actual literal value. */
private String literal;
/** The expression (parsed). */
private transient JxltEngine.Expression jxltExpression;
- /** The scope of the expression. */
- private Scope scope;
/**
* Creates a Jxlt literal node.
@@ -42,16 +42,6 @@ public final class ASTJxltLiteral extends JexlNode implements JexlNode.JxltHandl
return literal;
}
- @Override
- public Scope getScope() {
- return scope;
- }
-
- @Override
- public void setScope(final Scope scope) {
- this.scope = scope;
- }
-
@Override
public JxltEngine.Expression getExpression() {
return jxltExpression;
@@ -75,8 +65,17 @@ public final class ASTJxltLiteral extends JexlNode implements JexlNode.JxltHandl
this.jxltExpression = e;
}
- void setLiteral(final String literal) {
- this.literal = literal;
+ void setLiteral(final String src, final Scope scope) {
+ this.literal = src;
+ if (src != null && !src.isEmpty()) {
+ JexlEngine jexl = JexlEngine.getThreadEngine();
+ if (jexl != null) {
+ JxltEngine jxlt = jexl.createJxltEngine();
+ if (jxlt instanceof TemplateEngine) {
+ this.jxltExpression = ((TemplateEngine) jxlt).createExpression(jexlInfo(), src, scope);
+ }
+ }
+ }
}
@Override
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 dc75ab2e..9c60b382 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
@@ -21,7 +21,6 @@ import org.apache.commons.jexl3.JexlCache;
import org.apache.commons.jexl3.JexlContext.NamespaceFunctor;
import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.JxltEngine;
-import org.apache.commons.jexl3.internal.Scope;
import org.apache.commons.jexl3.introspection.JexlMethod;
import org.apache.commons.jexl3.introspection.JexlPropertyGet;
import org.apache.commons.jexl3.introspection.JexlPropertySet;
@@ -59,18 +58,6 @@ public abstract class JexlNode extends SimpleNode implements JexlCache.Reference
* @param expr a TemplateEngine.TemplateExpression instance
*/
void setExpression(JxltEngine.Expression expr);
-
- /**
- * Sets the scope of the expression.
- * @param scope the scope
- */
- void setScope(Scope scope);
-
- /**
- * Gets the scope of the expression.
- * @return the scope
- */
- Scope getScope();
}
@Override
@@ -142,7 +129,7 @@ public abstract class JexlNode extends SimpleNode implements JexlCache.Reference
*/
private static final long serialVersionUID = 1L;
- // line + column encoded: up to 4096 columns (ie 20 bits for line + 12 bits for column)
+ // line + column encoded: up to 4096 columns (i.e., 20 bits for line + 12 bits for column)
private int lc = -1;
public JexlNode(final int id) {
@@ -288,7 +275,7 @@ public abstract class JexlNode extends SimpleNode implements JexlCache.Reference
for(int s = 0; s < nsiblings; ++s) {
final JexlNode sibling = parent.jjtGetChild(s);
if (sibling == this) {
- // the next chid offset of this nodes parent
+ // the next child offset of this node_s parent
rhs = s + 1;
break;
}
@@ -350,7 +337,7 @@ public abstract class JexlNode extends SimpleNode implements JexlCache.Reference
if (lc >= 0) {
final int c = lc & 0xfff;
final int l = lc >> 0xc;
- // at least an info with line/column number
+ // at least an info instance with line/column number
return info != null ? info.at(info.getLine() + l - 1, c) : new JexlInfo(name, l, c);
}
// weird though; no jjSetFirstToken(...) ever called?
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
index c378770e..da33ecbe 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -32,6 +32,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
@@ -183,7 +184,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
* <p>Each parameter is associated with a register and is materialized
* as an offset in the registers array used during evaluation.</p>
*/
- protected Scope scope;
+ protected final AtomicReference<Scope> scopeReference;
/**
* When parsing inner functions/lambda, need to stack the scope (sic).
*/
@@ -246,7 +247,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
/**
* The current lexical block.
*/
- protected LexicalUnit block;
+ protected AtomicReference<LexicalUnit> blockReference;
/**
* Stack of lexical blocks.
*/
@@ -255,6 +256,10 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
* The map of lexical to functional blocks.
*/
protected final Map<LexicalUnit, Scope> blockScopes;
+ /**
+ * The parent parser if any.
+ */
+ protected final JexlParser parent;
/**
* Creates a new parser.
@@ -263,11 +268,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
* </p>
*/
protected JexlParser() {
- featureController = new FeatureController(JexlEngine.DEFAULT_FEATURES);
- scopes = new ArrayDeque<>();
- loopCounts = new ArrayDeque<>();
- blocks = new ArrayDeque<>();
- blockScopes = new IdentityHashMap<>();
+ this(null);
}
/**
@@ -279,19 +280,31 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
protected JexlParser(JexlParser parser) {
this.info = null;
this.source = null;
- featureController = parser.featureController;
- scope = parser.scope;
- scopes = parser.scopes;
- pragmas = parser.pragmas;
- namespaces = parser.namespaces;
- loopCount = parser.loopCount;
- loopCounts = parser.loopCounts;
- block = parser.block;
- blocks = parser.blocks;
- blockScopes = parser.blockScopes;
- fqcnResolver = parser.fqcnResolver;
- imports = parser.imports;
- autoSemicolon = parser.autoSemicolon;
+ if (parser != null) {
+ parent = parser;
+ featureController = parser.featureController;
+ scopeReference = parser.scopeReference;
+ scopes = parser.scopes;
+ pragmas = parser.pragmas;
+ namespaces = parser.namespaces;
+ loopCount = parser.loopCount;
+ loopCounts = parser.loopCounts;
+ blockReference = parser.blockReference;
+ blocks = parser.blocks;
+ blockScopes = parser.blockScopes;
+ fqcnResolver = parser.fqcnResolver;
+ imports = parser.imports;
+ autoSemicolon = parser.autoSemicolon;
+ } else {
+ parent = null;
+ featureController = new FeatureController(JexlEngine.DEFAULT_FEATURES);
+ scopeReference = new AtomicReference<>();
+ blockReference = new AtomicReference<>();
+ scopes = new ArrayDeque<>();
+ loopCounts = new ArrayDeque<>();
+ blocks = new ArrayDeque<>();
+ blockScopes = new IdentityHashMap<>();
+ }
}
/**
@@ -458,6 +471,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
* @return the image
*/
protected String checkVariable(final ASTIdentifier identifier, final String name) {
+ final Scope scope = scopeReference.get();
if (scope != null) {
final Integer symbol = scope.getSymbol(name);
if (symbol != null) {
@@ -467,7 +481,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
// captured are declared in all cases
identifier.setCaptured(true);
} else {
- LexicalUnit unit = block;
+ LexicalUnit unit = blockReference.get();
declared = unit.hasSymbol(symbol);
// one of the lexical blocks above should declare it
if (!declared) {
@@ -508,18 +522,20 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
protected void cleanup(final JexlFeatures features) {
info = null;
source = null;
- scope = null;
- scopes.clear();
- pragmas = null;
- namespaces = null;
- fqcnResolver = null;
- imports.clear();
- loopCounts.clear();
- loopCount = 0;
- blocks.clear();
- block = null;
- blockScopes.clear();
- setFeatures(features);
+ if (parent == null ) {
+ scopeReference.set(null);
+ scopes.clear();
+ pragmas = null;
+ namespaces = null;
+ fqcnResolver = null;
+ imports.clear();
+ loopCounts.clear();
+ loopCount = 0;
+ blocks.clear();
+ blockReference.set(null);
+ blockScopes.clear();
+ setFeatures(features);
+ }
}
/**
@@ -540,8 +556,10 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
protected void declareFunction(final ASTVar variable, final Token token) {
final String name = token.image;
// function foo() ... <=> const foo = ()->...
+ Scope scope = scopeReference.get();
if (scope == null) {
scope = new Scope(null);
+ scopeReference.set(scope);
}
final int symbol = scope.declareVariable(name);
variable.setSymbol(symbol, name);
@@ -552,6 +570,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
// function is const fun...
if (declareSymbol(symbol)) {
scope.addLexical(symbol);
+ LexicalUnit block = blockReference.get();
block.setConstant(symbol);
} else {
if (getFeatures().isLexical()) {
@@ -576,12 +595,15 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
if (!allowVariable(identifier)) {
throwFeatureException(JexlFeatures.LOCAL_VAR, token);
}
+ Scope scope = scopeReference.get();
if (scope == null) {
scope = new Scope(null, (String[]) null);
+ scopeReference.set(scope);
}
final int symbol = scope.declareParameter(identifier);
// not sure how declaring a parameter could fail...
// lexical feature error
+ LexicalUnit block = blockReference.get();
if (!block.declareSymbol(symbol)) {
if (lexical || getFeatures().isLexical()) {
final JexlInfo xinfo = info.at(token.beginLine, token.beginColumn);
@@ -661,6 +683,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
break;
}
}
+ LexicalUnit block = blockReference.get();
return block == null || block.declareSymbol(symbol);
}
@@ -680,8 +703,10 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
if (!allowVariable(name)) {
throwFeatureException(JexlFeatures.LOCAL_VAR, token);
}
+ Scope scope = scopeReference.get();
if (scope == null) {
scope = new Scope(null);
+ scopeReference.set(scope);
}
final int symbol = scope.declareVariable(name);
variable.setSymbol(symbol, name);
@@ -701,6 +726,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
} else if (lexical) {
scope.addLexical(symbol);
if (constant) {
+ LexicalUnit block = blockReference.get();
block.setConstant(symbol);
}
}
@@ -722,7 +748,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
* @return the named register map
*/
protected Scope getScope() {
- return scope;
+ return scopeReference.get();
}
/**
@@ -736,7 +762,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
* @return the named register map
*/
protected LexicalUnit getUnit() {
- return block;
+ return blockReference.get();
}
/**
* Default implementation does nothing but is overridden by generated code.
@@ -755,10 +781,11 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
*/
private boolean isConstant(final int symbol) {
if (symbol >= 0) {
+ LexicalUnit block = blockReference.get();
if (block != null && block.hasSymbol(symbol)) {
return block.isConstant(symbol);
}
- Scope blockScope = blockScopes.get(block);
+ Scope blockScope = blockScopes.get(blockReference.get());
int lexical = symbol;
for (final LexicalUnit unit : blocks) {
final Scope unitScope = blockScopes.get(unit);
@@ -865,6 +892,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
* @return true if a variable with that name was declared
*/
protected boolean isVariable(final String name) {
+ Scope scope = scopeReference.get();
return scope != null && scope.getSymbol(name) != null;
}
@@ -906,6 +934,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
}
final ASTJexlScript script = (ASTJexlScript) node;
// reaccess in case local variables have been declared
+ Scope scope = scopeReference.get();
if (script.getScope() != scope) {
script.setScope(scope);
}
@@ -929,23 +958,21 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
// heavy check
featureController.controlNode(node);
}
-//
-// JxltEngine.Expression parseJxlt(final String src) {
-// if (src != null && !src.isEmpty()) {
-// JexlEngine jexl = JexlEngine.getThreadEngine();
-// if (jexl != null) {
-// JxltEngine jxlt = jexl.createJxltEngine();
-// if (jxlt instanceof TemplateEngine) {
-// JxltEngine.Expression jxltExpression = ((TemplateEngine) jxlt).createExpression(null, src);
-// return jxltExpression;
-// }
-// }
-// }
-// return null;
-// }
/**
- * Called by parser at beginning of node construction.
+ * Parses an embedded Jexl expression within an interpolation node.
+ * <p>This creates a sub-parser that shares the scopes and of the parent parser.</p>
+ * @param info the JexlInfo
+ * @param src the source to parse
+ * @return the parsed tree
+ */
+ @Override
+ public ASTJexlScript jxltParse(JexlInfo info, JexlFeatures features, String src, Scope scope) {
+ return new Parser(this).parse(info, features, src, scope);
+ }
+
+ /**
+ * Called by parser at the beginning of a node construction.
* @param node the node
*/
protected void jjtreeOpenNodeScope(final JexlNode node) {
@@ -975,11 +1002,8 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
* Pops back to previous local variable scope.
*/
protected void popScope() {
- if (!scopes.isEmpty()) {
- scope = scopes.pop();
- } else {
- scope = null;
- }
+ Scope scope = scopes.isEmpty() ? null : scopes.pop();
+ scopeReference.set(scope);
if (!loopCounts.isEmpty()) {
loopCount = loopCounts.pop();
}
@@ -990,26 +1014,26 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
* @param unit restores the previous lexical scope
*/
protected void popUnit(final LexicalUnit unit) {
+ LexicalUnit block = blockReference.get();
if (block == unit){
blockScopes.remove(unit);
- if (!blocks.isEmpty()) {
- block = blocks.pop();
- } else {
- block = null;
- }
+ blockReference.set(blocks.isEmpty()? null : blocks.pop());
}
}
/**
* Create a new local variable scope and push it as current.
*/
- protected void pushScope() {
+ protected Scope pushScope() {
+ Scope scope = scopeReference.get();
if (scope != null) {
scopes.push(scope);
}
scope = new Scope(scope, (String[]) null);
+ scopeReference.set(scope);
loopCounts.push(loopCount);
loopCount = 0;
+ return scope;
}
/**
@@ -1017,11 +1041,13 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
* @param unit the new lexical unit
*/
protected void pushUnit(final LexicalUnit unit) {
+ Scope scope = scopeReference.get();
blockScopes.put(unit, scope);
+ LexicalUnit block = blockReference.get();
if (block != null) {
blocks.push(block);
}
- block = unit;
+ blockReference.set(unit);
}
protected void pushLoop() {
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlScriptParser.java b/src/main/java/org/apache/commons/jexl3/parser/JexlScriptParser.java
index 80277455..d857e200 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlScriptParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlScriptParser.java
@@ -38,4 +38,18 @@ public interface JexlScriptParser {
*/
ASTJexlScript parse(final JexlInfo info, final JexlFeatures features, final String src, final Scope scope);
+ /**
+ * Parses an embedded JXLT script or expression, an interpolation expression.
+ *
+ * @param info information structure
+ * @param features the set of parsing features
+ * @param src the expression to parse
+ * @param scope the script frame
+ * @return the parsed tree
+ * @throws JexlException if any error occurred during parsing
+ */
+ default ASTJexlScript jxltParse(JexlInfo info, JexlFeatures features, String src, Scope scope) {
+ return parse(info, features, src, scope);
+ }
+
}
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 be51f942..edc6e234 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -70,12 +70,14 @@ public final class Parser extends JexlParser
public ASTJexlScript parse(JexlInfo jexlInfo, JexlFeatures jexlFeatures, String jexlSrc, Scope jexlScope) {
JexlFeatures previous = getFeatures();
try {
- setFeatures(jexlFeatures);
// lets do the 'Unique Init' in here to be safe - it's a pain to remember
this.info = jexlInfo != null? jexlInfo : new JexlInfo();
this.source = jexlSrc;
- this.pragmas = null;
- this.scope = jexlScope;
+ if (this.parent == null) {
+ setFeatures(jexlFeatures);
+ this.pragmas = null;
+ }
+ this.scopeReference.set(jexlScope);
token_source.comparatorNames = jexlFeatures.supportsComparatorNames();
ReInit(jexlSrc);
ASTJexlScript script = jexlFeatures.supportsScript()
@@ -1061,7 +1063,7 @@ void JxltLiteral() #JxltLiteral :
}
{
t=<JXLT_LITERAL>
- { jjtThis.setLiteral(src = Parser.buildString(t.image, true)); }
+ { jjtThis.setLiteral(src = Parser.buildString(t.image, true), getScope()); }
}
void RegexLiteral() :
@@ -1218,7 +1220,7 @@ void IdentifierAccess() #void :
|
t=<STRING_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true)); } #IdentifierAccess
|
- t=<JXLT_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true)); } #IdentifierAccessJxlt
+ t=<JXLT_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true), getScope()); } #IdentifierAccessJxlt
)
|
<QDOT> (
@@ -1226,7 +1228,7 @@ void IdentifierAccess() #void :
|
t=<STRING_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true)); } #IdentifierAccessSafe
|
- t=<JXLT_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true)); } #IdentifierAccessSafeJxlt
+ t=<JXLT_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true), getScope()); } #IdentifierAccessSafeJxlt
)
}
diff --git a/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java b/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
index d1338841..a1531e25 100644
--- a/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
@@ -17,8 +17,10 @@
package org.apache.commons.jexl3;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashMap;
import java.util.Map;
@@ -346,17 +348,25 @@ class PropertyAccessTest extends JexlTestCase {
result = script.execute(ctx, "querty");
assertEquals("oops", result);
- // fails with jxlt & lenient navigation
+ // parsing fails with jxlt & lenient navigation
stmt = "(x)->{ x?.`c${la--ss` ?? 'oops' }";
- script = engine.createScript(stmt);
- result = script.execute(ctx, "querty");
- assertEquals("oops", result);
+ try {
+ script = engine.createScript(stmt);
+ result = script.execute(ctx, "querty");
+ } catch (final JexlException xany) {
+ assertNotNull(xany.getMessage());
+ assertTrue(xany.getMessage().contains("c${la--ss"));
+ }
- // fails with jxlt & strict navigation
+ // parsing fails with jxlt & strict navigation
stmt = "(x)->{ x.`c${la--ss` ?? 'oops' }";
+ try {
script = engine.createScript(stmt);
result = script.execute(ctx, "querty");
- assertEquals("oops", result);
+ } catch (final JexlException xany) {
+ assertNotNull(xany.getMessage());
+ assertTrue(xany.getMessage().contains("c${la--ss"));
+ }
}
@Test