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 2020/01/27 18:41:35 UTC
[commons-jexl] 02/02: JEXL-307: Variable state (redefined, shaded)
is determined at parsing time;
- Variable error states trigger parsing failures when set through features -
Variable error states trigger execution failures when set through options
Task #JEXL-307 - Variable redeclaration option
This is an automated email from the ASF dual-hosted git repository.
henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
commit 51a981c900b77109ed090a616b2358b80ccb3e9d
Author: henrib <he...@apache.org>
AuthorDate: Mon Jan 27 19:40:53 2020 +0100
JEXL-307: Variable state (redefined, shaded) is determined at parsing time;
- Variable error states trigger parsing failures when set through features
- Variable error states trigger execution failures when set through options
Task #JEXL-307 - Variable redeclaration option
---
RELEASE-NOTES.txt | 3 +
.../org/apache/commons/jexl3/JexlArithmetic.java | 3 +-
.../java/org/apache/commons/jexl3/JexlEngine.java | 7 --
.../org/apache/commons/jexl3/JexlException.java | 19 ++-
.../java/org/apache/commons/jexl3/JexlInfo.java | 7 ++
.../org/apache/commons/jexl3/internal/Closure.java | 10 +-
.../org/apache/commons/jexl3/internal/Engine.java | 9 +-
.../commons/jexl3/internal/IntegerRange.java | 8 +-
.../apache/commons/jexl3/internal/Interpreter.java | 35 +++---
.../commons/jexl3/internal/InterpreterBase.java | 48 +++-----
.../commons/jexl3/internal/LexicalFrame.java | 36 +++---
.../commons/jexl3/internal/LexicalScope.java | 33 +-----
.../apache/commons/jexl3/internal/LongRange.java | 8 +-
.../org/apache/commons/jexl3/internal/Scope.java | 62 +++++-----
.../commons/jexl3/internal/TemplateScript.java | 54 ++++++---
.../org/apache/commons/jexl3/parser/ASTBlock.java | 34 +-----
.../commons/jexl3/parser/ASTForeachStatement.java | 29 +----
.../apache/commons/jexl3/parser/ASTIdentifier.java | 55 ++++++++-
.../apache/commons/jexl3/parser/ASTJexlScript.java | 41 ++-----
.../parser/{ASTBlock.java => JexlLexicalNode.java} | 26 ++--
.../org/apache/commons/jexl3/parser/JexlNode.java | 56 +++++++++
.../apache/commons/jexl3/parser/JexlParser.java | 76 ++++++++----
.../org/apache/commons/jexl3/parser/Parser.jjt | 2 +-
src/site/xdoc/changes.xml | 9 ++
src/site/xdoc/reference/syntax.xml | 2 +-
.../java/org/apache/commons/jexl3/JXLTTest.java | 131 ++++++++++++++-------
.../org/apache/commons/jexl3/JexlEvalContext.java | 2 -
.../java/org/apache/commons/jexl3/LambdaTest.java | 12 +-
.../java/org/apache/commons/jexl3/LexicalTest.java | 31 ++++-
29 files changed, 491 insertions(+), 357 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index f2aab63..cfdb516 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -74,7 +74,10 @@ New Features in 3.2:
Bugs Fixed in 3.2:
==================
+* JEXL-322: JXLT String literals cannot contain curly brace
* JEXL-321: Empty do-while loop is broken
+* JEXL-320: "mvn test" fails with COMPILATION ERROR in SynchronizedArithmetic.java on Java 11
+* JEXL-319: Apache project documentation gives instructions in subversion
* JEXL-318: Annotation processing may fail in lexical mode
* JEXL-315: JxltEngine literal string strings ending in \ $ or # throw JxltEngine$Exception
* JEXL-314: Comparison NULL values of variables NAME1.NAME2
diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index f92d426..add0d0f 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -151,8 +151,9 @@ public class JexlArithmetic {
*
* @param options the {@link JexlEngine.Options} to use
* @return an arithmetic with those options set
- * @deprecated
+ * @deprecated 3.2
*/
+ @Deprecated
public JexlArithmetic options(JexlEngine.Options options) {
if (options != null) {
Boolean ostrict = options.isStrictArithmetic();
diff --git a/src/main/java/org/apache/commons/jexl3/JexlEngine.java b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
index a5cfcee..28e8808 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
@@ -258,13 +258,6 @@ public abstract class JexlEngine {
* @return true if strict, false otherwise
*/
public abstract boolean isStrict();
-
- /**
- * Checks whether this engine uses safe navigation.
- *
- * @return true if safe, false otherwise
- */
- public abstract boolean isSafe();
/**
* Checks whether this engine will throw JexlException.Cancel (true) or return null (false) when interrupted
diff --git a/src/main/java/org/apache/commons/jexl3/JexlException.java b/src/main/java/org/apache/commons/jexl3/JexlException.java
index 788696b..7c0e334 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlException.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlException.java
@@ -95,7 +95,7 @@ public class JexlException extends RuntimeException {
* @return the information
*/
public JexlInfo getInfo() {
- return getInfo(mark, info);
+ return detailedInfo(mark, info);
}
/**
@@ -105,7 +105,7 @@ public class JexlException extends RuntimeException {
* @return a string builder
*/
private static StringBuilder errorAt(JexlNode node) {
- JexlInfo info = node != null? getInfo(node, node.jexlInfo()) : null;
+ JexlInfo info = node != null? detailedInfo(node, node.jexlInfo()) : null;
StringBuilder msg = new StringBuilder();
if (info != null) {
msg.append(info.toString());
@@ -122,8 +122,21 @@ public class JexlException extends RuntimeException {
* @param node the node
* @param info the information
* @return the information or null
+ * @deprecated 3.2
+ */
+ @Deprecated
+ public static JexlInfo getInfo(JexlNode node, JexlInfo info) {
+ return detailedInfo(node, info);
+ }
+
+ /**
+ * Gets the most specific information attached to a node.
+ *
+ * @param node the node
+ * @param info the information
+ * @return the information or null
*/
- private static JexlInfo getInfo(JexlNode node, JexlInfo info) {
+ private static JexlInfo detailedInfo(JexlNode node, JexlInfo info) {
if (info != null && node != null) {
final Debugger dbg = new Debugger();
if (dbg.debug(node)) {
diff --git a/src/main/java/org/apache/commons/jexl3/JexlInfo.java b/src/main/java/org/apache/commons/jexl3/JexlInfo.java
index 296138b..b454159 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlInfo.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlInfo.java
@@ -186,6 +186,13 @@ public class JexlInfo {
}
/**
+ * @return this instance or a copy without any decorations
+ */
+ public JexlInfo detach() {
+ return this;
+ }
+
+ /**
* Gets the info from a script.
* @param script the script
* @return the info
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Closure.java b/src/main/java/org/apache/commons/jexl3/internal/Closure.java
index 839b1a0..a5a54d4 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Closure.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Closure.java
@@ -87,18 +87,20 @@ public class Closure extends Script {
}
/**
- * Sets the hoisted index of a given symbol, ie the target index of a parent hoisted symbol in this closure's frame.
- * <p>This is meant to allow a locally defined function to "see" and call itself as a local (hoisted) variable;
+ * Sets the captured index of a given symbol, ie the target index of a parent
+ * captured symbol in this closure's frame.
+ * <p>This is meant to allow a locally defined function to "see" and call
+ * itself as a local (captured) variable;
* in other words, this allows recursive call of a function.
* @param symbol the symbol index (in the caller of this closure)
* @param value the value to set in the local frame
*/
- public void setHoisted(int symbol, Object value) {
+ public void setCaptured(int symbol, Object value) {
if (script instanceof ASTJexlLambda) {
ASTJexlLambda lambda = (ASTJexlLambda) script;
Scope scope = lambda.getScope();
if (scope != null) {
- Integer reg = scope.getHoisted(symbol);
+ Integer reg = scope.getCaptured(symbol);
if (reg != null) {
frame.set(reg, value);
}
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 d3ac4c3..48d9897 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -260,11 +260,6 @@ public class Engine extends JexlEngine {
}
@Override
- public boolean isSafe() {
- return this.safe;
- }
-
- @Override
public boolean isCancellable() {
return this.cancellable;
}
@@ -666,8 +661,8 @@ public class Engine extends JexlEngine {
}
ASTIdentifier identifier = (ASTIdentifier) node;
int symbol = identifier.getSymbol();
- // symbols that are hoisted are considered "global" variables
- if (symbol >= 0 && script != null && !script.isHoistedSymbol(symbol)) {
+ // symbols that are captured are considered "global" variables
+ if (symbol >= 0 && script != null && !script.isCapturedSymbol(symbol)) {
collector.collect(null);
} else {
// start collecting from identifier
diff --git a/src/main/java/org/apache/commons/jexl3/internal/IntegerRange.java b/src/main/java/org/apache/commons/jexl3/internal/IntegerRange.java
index a4093b8..29dbbff 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/IntegerRange.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/IntegerRange.java
@@ -139,15 +139,13 @@ public abstract class IntegerRange implements Collection<Integer> {
T[] copy = array;
if (ct.isAssignableFrom(Integer.class)) {
if (array.length < length) {
- copy = ct == Object.class
- ? (T[]) new Object[length]
- : (T[]) Array.newInstance(ct, length);
+ copy = (T[]) Array.newInstance(ct, length);
}
for (int a = 0; a < length; ++a) {
Array.set(copy, a, min + a);
}
- if (length < array.length) {
- array[length] = null;
+ if (length < copy.length) {
+ copy[length] = null;
}
return copy;
}
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 8b116b0..d0dd721 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -24,6 +24,7 @@ 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;
@@ -122,7 +123,7 @@ public class Interpreter extends InterpreterBase {
protected final Frame frame;
/** Block micro-frames. */
protected LexicalFrame block = null;
-
+
/**
* The thread local interpreter.
*/
@@ -216,10 +217,8 @@ public class Interpreter extends InterpreterBase {
} finally {
synchronized(this) {
if (functors != null) {
- if (AUTOCLOSEABLE != null) {
- for (Object functor : functors.values()) {
- closeIfSupported(functor);
- }
+ for (Object functor : functors.values()) {
+ closeIfSupported(functor);
}
functors.clear();
functors = null;
@@ -615,7 +614,7 @@ public class Interpreter extends InterpreterBase {
if (frame.has(symbol)) {
return frame.get(symbol);
}
- } else if (!block.declareSymbol(symbol)) {
+ } else if (!defineVariable(node, block)) {
return redefinedVariable(node, node.getName());
}
frame.set(symbol, null);
@@ -677,12 +676,12 @@ public class Interpreter extends InterpreterBase {
ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
final int symbol = loopVariable.getSymbol();
final boolean lexical = options.isLexical();// && node.getSymbolCount() > 0;
+ final LexicalFrame locals = lexical? new LexicalFrame(frame, block) : null;
final boolean loopSymbol = symbol >= 0 && loopVariable instanceof ASTVar;
if (lexical) {
// create lexical frame
- LexicalFrame locals = new LexicalFrame(frame, block);
// it may be a local previously declared
- if (loopSymbol && !locals.declareSymbol(symbol)) {
+ if (loopSymbol && !defineVariable((ASTVar) loopVariable, locals)) {
return redefinedVariable(node, loopVariable.getName());
}
block = locals;
@@ -709,7 +708,7 @@ public class Interpreter extends InterpreterBase {
// clean up but remain current
block.pop();
// unlikely to fail
- if (loopSymbol && !block.declareSymbol(symbol)) {
+ if (loopSymbol && !defineVariable((ASTVar) loopVariable, locals)) {
return redefinedVariable(node, loopVariable.getName());
}
}
@@ -1013,7 +1012,7 @@ public class Interpreter extends InterpreterBase {
*/
protected Object runClosure(Closure closure, Object data) {
ASTJexlScript script = closure.getScript();
- block = new LexicalFrame(frame, block).declareArgs();
+ block = new LexicalFrame(frame, block).defineArgs();
try {
JexlNode body = script.jjtGetChild(script.jjtGetNumChildren() - 1);
return interpret(body);
@@ -1027,7 +1026,7 @@ public class Interpreter extends InterpreterBase {
if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
return new Closure(this, (ASTJexlLambda) script);
} else {
- block = new LexicalFrame(frame, block).declareArgs();
+ block = new LexicalFrame(frame, block).defineArgs();
try {
final int numChildren = script.jjtGetNumChildren();
Object result = null;
@@ -1309,10 +1308,10 @@ public class Interpreter extends InterpreterBase {
symbol = var.getSymbol();
if (symbol >= 0 && options.isLexical()) {
if (var instanceof ASTVar) {
- if (!block.declareSymbol(symbol)) {
+ if (!defineVariable((ASTVar) var, block)) {
return redefinedVariable(var, var.getName());
}
- } else if (isSymbolShaded(symbol, block)) {
+ } else if (options.isLexicalShade() && var.isShaded()) {
return undefinedVariable(var, var.getName());
}
}
@@ -1335,9 +1334,9 @@ public class Interpreter extends InterpreterBase {
}
}
frame.set(symbol, right);
- // make the closure accessible to itself, ie hoist the currently set variable after frame creation
+ // make the closure accessible to itself, ie capture the currently set variable after frame creation
if (right instanceof Closure) {
- ((Closure) right).setHoisted(symbol, right);
+ ((Closure) right).setCaptured(symbol, right);
}
return right; // 1
}
@@ -1792,7 +1791,11 @@ public class Interpreter extends InterpreterBase {
TemplateEngine.TemplateExpression tp = (TemplateEngine.TemplateExpression) node.jjtGetValue();
if (tp == null) {
TemplateEngine jxlt = jexl.jxlt();
- tp = jxlt.parseExpression(node.jexlInfo(), node.getLiteral(), frame != null ? frame.getScope() : null);
+ JexlInfo info = node.jexlInfo();
+ if (this.block != null) {
+ info = new JexlNode.Info(node, info);
+ }
+ tp = jxlt.parseExpression(info, node.getLiteral(), frame != null ? frame.getScope() : null);
node.jjtSetValue(tp);
}
if (tp != null) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
index 97c936d..1a69717 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -38,6 +38,7 @@ import org.apache.commons.jexl3.parser.ASTIdentifier;
import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
import org.apache.commons.jexl3.parser.ASTMethodNode;
import org.apache.commons.jexl3.parser.ASTReference;
+import org.apache.commons.jexl3.parser.ASTVar;
import org.apache.commons.jexl3.parser.JexlNode;
import org.apache.commons.jexl3.parser.ParserVisitor;
@@ -130,19 +131,6 @@ public abstract class InterpreterBase extends ParserVisitor {
functions = ii.functions;
functors = ii.functors;
}
-
-
- /** Java7 AutoCloseable interface defined?. */
- protected static final Class<?> AUTOCLOSEABLE;
- static {
- Class<?> c;
- try {
- c = Class.forName("java.lang.AutoCloseable");
- } catch (ClassNotFoundException xclass) {
- c = null;
- }
- AUTOCLOSEABLE = c;
- }
/**
* Attempt to call close() if supported.
@@ -151,7 +139,6 @@ public abstract class InterpreterBase extends ParserVisitor {
*/
protected void closeIfSupported(Object closeable) {
if (closeable != null) {
- //if (AUTOCLOSEABLE == null || AUTOCLOSEABLE.isAssignableFrom(closeable.getClass())) {
JexlMethod mclose = uberspect.getMethod(closeable, "close", EMPTY_PARAMS);
if (mclose != null) {
try {
@@ -160,7 +147,6 @@ public abstract class InterpreterBase extends ParserVisitor {
logger.warn(xignore);
}
}
- //}
}
}
@@ -241,26 +227,22 @@ public abstract class InterpreterBase extends ParserVisitor {
}
return namespace;
}
-
+
/**
- * Checks whether a symbol is a shade or actually accessible.
- * @param symbol the symbol
- * @param block the block to check from (canoot be null)
- * @return true is symbol is just a shade, false otherwise
+ * Defines a variable.
+ * @param var the variable to define
+ * @param frame the frame in which it will be defined
+ * @return true if definition succeeded, false otherwise
*/
- protected boolean isSymbolShaded(int symbol, LexicalScope block) {
- if (!options.isLexicalShade()) {
+ protected boolean defineVariable(ASTVar var, LexicalFrame frame) {
+ int symbol = var.getSymbol();
+ if (symbol < 0) {
return false;
}
- // if not in lexical block, undefined if (in its symbol) shade
- LexicalScope b = block;
- while (b != null) {
- if (b.hasSymbol(symbol)) {
- return false;
- }
- b = b.previous;
+ if (var.isRedefined()) {
+ return false;
}
- return true;
+ return frame.defineSymbol(symbol, var.isCaptured());
}
/**
@@ -273,11 +255,11 @@ public abstract class InterpreterBase extends ParserVisitor {
protected Object getVariable(Frame frame, LexicalScope block, ASTIdentifier identifier) {
int symbol = identifier.getSymbol();
// if we have a symbol, we have a scope thus a frame
+ if (options.isLexicalShade() && identifier.isShaded()) {
+ return undefinedVariable(identifier, identifier.getName());
+ }
if (symbol >= 0) {
if (frame.has(symbol)) {
- if (options.isLexical() && isSymbolShaded(symbol, block)) {
- return undefinedVariable(identifier, identifier.getName());
- }
Object value = frame.get(symbol);
if (value != Scope.UNDEFINED) {
return value;
diff --git a/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java b/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java
index 6bb5f1e..54c570b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java
@@ -20,7 +20,7 @@ import java.util.ArrayDeque;
import java.util.Deque;
/**
- * The set of valued symbols declared in a lexical scope.
+ * The set of valued symbols defined in a lexical frame.
* <p>The symbol identifiers are determined by the functional scope.
*/
public class LexicalFrame extends LexicalScope {
@@ -28,13 +28,15 @@ public class LexicalFrame extends LexicalScope {
private final Frame frame;
/** The stack of values in the lexical frame. */
private Deque<Object> stack = null;
+ /** Previous frame. */
+ protected LexicalFrame previous;
/**
* Lexical frame ctor.
* @param scriptf the script frame
- * @param previous the previous lexical frame
+ * @param outerf the previous lexical frame
*/
- public LexicalFrame(Frame scriptf, LexicalFrame previous) {
- super(previous);
+ public LexicalFrame(Frame scriptf, LexicalFrame outerf) {
+ this.previous = outerf;
this.frame = scriptf;
}
@@ -43,16 +45,17 @@ public class LexicalFrame extends LexicalScope {
* @param src the frame to copy
*/
public LexicalFrame(LexicalFrame src) {
- super(src.symbols, src.moreSymbols, src.previous);
+ super(src.symbols, src.moreSymbols);
frame = src.frame;
+ previous = src.previous;
stack = src.stack != null? new ArrayDeque<Object>(src.stack) : null;
}
-
+
/**
- * Declare the arguments.
- * @return the number of arguments
+ * Define the arguments.
+ * @return this frame
*/
- public LexicalFrame declareArgs() {
+ public LexicalFrame defineArgs() {
if (frame != null) {
int argc = frame.getScope().getArgCount();
for(int a = 0; a < argc; ++a) {
@@ -62,10 +65,15 @@ public class LexicalFrame extends LexicalScope {
return this;
}
- @Override
- public boolean declareSymbol(int symbol) {
- boolean declared = super.declareSymbol(symbol);
- if (declared && frame.getScope().isHoistedSymbol(symbol)) {
+ /**
+ * Defines a symbol.
+ * @param symbol the symbol to define
+ * @param capture whether this redefines a captured symbol
+ * @return true if symbol is defined, false otherwise
+ */
+ public boolean defineSymbol(int symbol, boolean capture) {
+ boolean declared = addSymbol(symbol);
+ if (declared && capture) {
if (stack == null) {
stack = new ArrayDeque<Object>() ;
}
@@ -98,7 +106,7 @@ public class LexicalFrame extends LexicalScope {
}
moreSymbols.clear();
}
- // restore values of hoisted symbols that were overwritten
+ // restore values of captured symbols that were overwritten
if (stack != null) {
while(!stack.isEmpty()) {
Object value = stack.pop();
diff --git a/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java b/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java
index bc2b6a8..95a6041 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java
@@ -29,30 +29,22 @@ public class LexicalScope {
protected long symbols = 0L;
/** Symbols after 64. */
protected BitSet moreSymbols = null;
- /** Previous block. */
- protected final LexicalScope previous;
-
/**
* Create a scope.
- * @param scope the previous scope
*/
- public LexicalScope(LexicalScope scope) {
- previous = scope;
- }
+ public LexicalScope() {}
/**
* Frame copy ctor base.
* @param s the symbols mask
* @param ms the more symbols bitset
- * @param pscope the previous scope
*/
- protected LexicalScope(long s, BitSet ms, LexicalScope pscope) {
- previous = pscope;
+ protected LexicalScope(long s, BitSet ms) {
symbols = s;
moreSymbols = ms != null? (BitSet) ms.clone() : null;
}
-
+
/**
* Ensure more symbpls can be stored.
* @return the set of more symbols
@@ -78,28 +70,11 @@ public class LexicalScope {
}
/**
- * Declares a local symbol.
- *
- * @param symbol the symbol index
- * @return true if was not already declared, false if lexical clash (error)
- */
- public boolean declareSymbol(int symbol) {
- LexicalScope walk = previous;
- while (walk != null) {
- if (walk.hasSymbol(symbol)) {
- return false;
- }
- walk = walk.previous;
- }
- return addSymbol(symbol);
- }
-
- /**
* Adds a symbol in this scope.
* @param symbol the symbol
* @return true if registered, false if symbol was already registered
*/
- protected final boolean addSymbol(int symbol) {
+ public final boolean addSymbol(int symbol) {
if (symbol < LONGBITS) {
if ((symbols & (1L << symbol)) != 0L) {
return false;
diff --git a/src/main/java/org/apache/commons/jexl3/internal/LongRange.java b/src/main/java/org/apache/commons/jexl3/internal/LongRange.java
index 605ae9c..4de4035 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/LongRange.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/LongRange.java
@@ -141,15 +141,13 @@ public abstract class LongRange implements Collection<Long> {
T[] copy = array;
if (ct.isAssignableFrom(Long.class)) {
if (array.length < length) {
- copy = ct == Object.class
- ? (T[]) new Object[length]
- : (T[]) Array.newInstance(ct, length);
+ copy = (T[]) Array.newInstance(ct, length);
}
for (int a = 0; a < length; ++a) {
Array.set(copy, a, min + a);
}
- if (length < array.length) {
- array[length] = null;
+ if (length < copy.length) {
+ copy[length] = null;
}
return copy;
}
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 0016b8a..2a5b9b9 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
@@ -63,9 +63,9 @@ public final class Scope {
*/
private Map<String, Integer> namedVariables = null;
/**
- * The map of local hoisted variables to parent scope variables, ie closure.
+ * The map of local captured variables to parent scope variables, ie closure.
*/
- private Map<Integer, Integer> hoistedVariables = null;
+ private Map<Integer, Integer> capturedVariables = null;
/**
* The empty string array.
*/
@@ -115,7 +115,7 @@ public final class Scope {
/**
* Checks whether an identifier is a local variable or argument, ie a symbol.
- * If this fails, attempt to solve by hoisting parent stacked.
+ * If this fails, look in parents for symbol that can be captured.
* @param name the symbol name
* @return the symbol index
*/
@@ -126,35 +126,35 @@ public final class Scope {
/**
* Checks whether an identifier is a local variable or argument, ie a symbol.
* @param name the symbol name
- * @param hoist whether solving by hoisting parent stacked is allowed
+ * @param capture whether solving by capturing a parent symbol is allowed
* @return the symbol index
*/
- private Integer getSymbol(String name, boolean hoist) {
+ private Integer getSymbol(String name, boolean capture) {
Integer register = namedVariables != null ? namedVariables.get(name) : null;
- if (register == null && hoist && parent != null) {
+ if (register == null && capture && parent != null) {
Integer pr = parent.getSymbol(name, true);
if (pr != null) {
- if (hoistedVariables == null) {
- hoistedVariables = new LinkedHashMap<Integer, Integer>();
+ if (capturedVariables == null) {
+ capturedVariables = new LinkedHashMap<Integer, Integer>();
}
if (namedVariables == null) {
namedVariables = new LinkedHashMap<String, Integer>();
}
register = namedVariables.size();
namedVariables.put(name, register);
- hoistedVariables.put(register, pr);
+ capturedVariables.put(register, pr);
}
}
return register;
}
/**
- * Checks whether a given symbol is hoisted.
+ * Checks whether a given symbol is captured.
* @param symbol the symbol number
- * @return true if hoisted, false otherwise
+ * @return true if captured, false otherwise
*/
- public boolean isHoistedSymbol(int symbol) {
- return hoistedVariables != null && hoistedVariables.containsKey(symbol);
+ public boolean isCapturedSymbol(int symbol) {
+ return capturedVariables != null && capturedVariables.containsKey(symbol);
}
/**
@@ -197,14 +197,14 @@ public final class Scope {
register = namedVariables.size();
namedVariables.put(name, register);
vars += 1;
- // check if local is redefining hoisted
+ // check if local is redefining captured
if (parent != null) {
Integer pr = parent.getSymbol(name, true);
if (pr != null) {
- if (hoistedVariables == null) {
- hoistedVariables = new LinkedHashMap<Integer, Integer>();
+ if (capturedVariables == null) {
+ capturedVariables = new LinkedHashMap<Integer, Integer>();
}
- hoistedVariables.put(register, pr);
+ capturedVariables.put(register, pr);
}
}
}
@@ -213,7 +213,7 @@ public final class Scope {
/**
* Creates a frame by copying values up to the number of parameters.
- * <p>This captures the hoisted variables values.</p>
+ * <p>This captures the captured variables values.</p>
* @param frame the caller frame
* @param args the arguments
* @return the arguments array
@@ -222,10 +222,10 @@ public final class Scope {
if (namedVariables != null) {
Object[] arguments = new Object[namedVariables.size()];
Arrays.fill(arguments, UNDECLARED);
- if (frame != null && hoistedVariables != null && parent != null) {
- for (Map.Entry<Integer, Integer> hoist : hoistedVariables.entrySet()) {
- Integer target = hoist.getKey();
- Integer source = hoist.getValue();
+ if (frame != null && capturedVariables != null && parent != null) {
+ for (Map.Entry<Integer, Integer> capture : capturedVariables.entrySet()) {
+ Integer target = capture.getKey();
+ Integer source = capture.getValue();
Object arg = frame.get(source);
arguments[target] = arg;
}
@@ -237,16 +237,16 @@ public final class Scope {
}
/**
- * Gets the hoisted index of a given symbol, ie the target index of a symbol in a child frame.
+ * Gets the captured index of a given symbol, ie the target index of a symbol in a child frame.
* @param symbol the symbol index
- * @return the target symbol index or null if the symbol is not hoisted
+ * @return the target symbol index or null if the symbol is not captured
*/
- public Integer getHoisted(int symbol) {
- if (hoistedVariables != null) {
- for (Map.Entry<Integer, Integer> hoist : hoistedVariables.entrySet()) {
- Integer source = hoist.getValue();
+ public Integer getCaptured(int symbol) {
+ if (capturedVariables != null) {
+ for (Map.Entry<Integer, Integer> capture : capturedVariables.entrySet()) {
+ Integer source = capture.getValue();
if (source == symbol) {
- return hoist.getKey();
+ return capture.getKey();
}
}
}
@@ -300,7 +300,7 @@ public final class Scope {
}
/**
- * Gets this script local variable, i.e. symbols assigned to local variables excluding hoisted variables.
+ * Gets this script local variable, i.e. symbols assigned to local variables excluding captured variables.
* @return the local variable names
*/
public String[] getLocalVariables() {
@@ -308,7 +308,7 @@ public final class Scope {
List<String> locals = new ArrayList<String>(vars);
for (Map.Entry<String, Integer> entry : namedVariables.entrySet()) {
int symnum = entry.getValue();
- if (symnum >= parms && (hoistedVariables == null || !hoistedVariables.containsKey(symnum))) {
+ if (symnum >= parms && (capturedVariables == null || !capturedVariables.containsKey(symnum))) {
locals.add(entry.getKey());
}
}
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 760f876..eed231c 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
@@ -30,6 +30,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.parser.ASTArguments;
import org.apache.commons.jexl3.parser.ASTFunctionNode;
import org.apache.commons.jexl3.parser.ASTIdentifier;
@@ -118,20 +119,19 @@ public final class TemplateScript implements JxltEngine.Template {
script = jxlt.getEngine().parse(info.at(1, 1), false, strb.toString(), scope).script();
// seek the map of expression number to scope so we can parse Unified
// expression blocks with the appropriate symbols
- Map<Integer, Scope> mscope = new TreeMap<Integer, Scope>();
- collectPrintScope(script.script(), null, mscope);
+ Map<Integer, JexlNode.Info> minfo = new TreeMap<Integer, JexlNode.Info>();
+ collectPrintScope(script.script(), minfo);
// jexl:print(...) expression counter
int jpe = 0;
// create the exprs using the intended scopes
for (int b = 0; b < blocks.size(); ++b) {
Block block = blocks.get(b);
if (block.getType() == BlockType.VERBATIM) {
+ JexlNode.Info ji = minfo.get(jpe);
uexprs.add(
- jxlt.parseExpression(
- info.at(block.getLine(), 1),
- block.getBody(),
- mscope.get(jpe++))
+ jxlt.parseExpression(ji, block.getBody(), scopeOf(ji))
);
+ jpe += 1;
}
}
source = blocks.toArray(new Block[blocks.size()]);
@@ -159,14 +159,29 @@ public final class TemplateScript implements JxltEngine.Template {
}
/**
+ * Gets the scope from an info.
+ * @param info the node info
+ * @return the scope
+ */
+ private static Scope scopeOf(JexlNode.Info info) {
+ JexlNode walk = info.getNode();
+ while(walk != null) {
+ if (walk instanceof ASTJexlScript) {
+ return ((ASTJexlScript) walk).getScope();
+ }
+ walk = walk.jjtGetParent();
+ }
+ return null;
+ }
+
+ /**
* Collects the scope surrounding a call to jexl:print(i).
* <p>This allows to later parse the blocks with the known symbols
* in the frame visible to the parser.
* @param node the visited node
- * @param scope the current scope
- * @param mscope the map of printed expression number to scope
+ * @param minfo the map of printed expression number to node info
*/
- static void collectPrintScope(JexlNode node, Scope scope, Map<Integer, Scope> mscope) {
+ private static void collectPrintScope(JexlNode node, Map<Integer, JexlNode.Info> minfo) {
int nc = node.jjtGetNumChildren();
if (node instanceof ASTFunctionNode) {
if (nc == 2) {
@@ -179,17 +194,15 @@ public final class TemplateScript implements JxltEngine.Template {
JexlNode arg0 = argNode.jjtGetChild(0);
if (arg0 instanceof ASTNumberLiteral) {
int exprNumber = ((ASTNumberLiteral) arg0).getLiteral().intValue();
- mscope.put(exprNumber, scope);
+ minfo.put(exprNumber, new JexlNode.Info(nameNode));
return;
}
}
}
}
- } else if (node instanceof ASTJexlScript) {
- scope = ((ASTJexlScript) node).getScope();
}
for (int c = 0; c < nc; ++c) {
- collectPrintScope(node.jjtGetChild(c), scope, mscope);
+ collectPrintScope(node.jjtGetChild(c), minfo);
}
}
@@ -230,13 +243,24 @@ public final class TemplateScript implements JxltEngine.Template {
}
return strb.toString();
}
-
+
@Override
public TemplateScript prepare(JexlContext context) {
+ Engine jexl = jxlt.getEngine();
Frame frame = script.createFrame((Object[]) null);
+ Interpreter interpreter = new TemplateInterpreter(jexl, context, frame, null, null);
TemplateExpression[] immediates = new TemplateExpression[exprs.length];
for (int e = 0; e < exprs.length; ++e) {
- immediates[e] = exprs[e].prepare(frame, context);
+ try {
+ immediates[e] = exprs[e].prepare(interpreter);
+ } catch (JexlException xjexl) {
+ JexlException xuel = TemplateEngine.createException(xjexl.getInfo(), "prepare", exprs[e], xjexl);
+ if (jexl.isSilent()) {
+ jexl.logger.warn(xuel.getMessage(), xuel.getCause());
+ return null;
+ }
+ throw xuel;
+ }
}
return new TemplateScript(jxlt, prefix, source, script, immediates);
}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java b/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java
index 03a00d9..2aebbce 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java
@@ -16,14 +16,11 @@
*/
package org.apache.commons.jexl3.parser;
-import org.apache.commons.jexl3.internal.LexicalScope;
-
/**
- * Declares a local variable.
+ * Declares a block.
*/
-public class ASTBlock extends JexlNode implements JexlParser.LexicalUnit {
- private LexicalScope locals = null;
-
+public class ASTBlock extends JexlLexicalNode {
+
public ASTBlock(int id) {
super(id);
}
@@ -36,27 +33,4 @@ public class ASTBlock extends JexlNode implements JexlParser.LexicalUnit {
public Object jjtAccept(ParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
-
- @Override
- public boolean declareSymbol(int symbol) {
- if (locals == null) {
- locals = new LexicalScope(null);
- }
- return locals.declareSymbol(symbol);
- }
-
- @Override
- public int getSymbolCount() {
- return locals == null? 0 : locals.getSymbolCount();
- }
-
- @Override
- public boolean hasSymbol(int symbol) {
- return locals == null? false : locals.hasSymbol(symbol);
- }
-
- @Override
- public void clearUnit() {
- locals = null;
- }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
index 1ad61c0..d9683e9 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
@@ -16,13 +16,10 @@
*/
package org.apache.commons.jexl3.parser;
-import org.apache.commons.jexl3.internal.LexicalScope;
-
/**
- * Declares a local variable.
+ * Declares a for each loop.
*/
-public class ASTForeachStatement extends JexlNode implements JexlParser.LexicalUnit {
- private LexicalScope locals = null;
+public class ASTForeachStatement extends JexlLexicalNode {
public ASTForeachStatement(int id) {
super(id);
@@ -36,27 +33,5 @@ public class ASTForeachStatement extends JexlNode implements JexlParser.LexicalU
public Object jjtAccept(ParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
-
- @Override
- public boolean declareSymbol(int symbol) {
- if (locals == null) {
- locals = new LexicalScope(null);
- }
- return locals.declareSymbol(symbol);
- }
-
- @Override
- public int getSymbolCount() {
- return locals == null? 0 : locals.getSymbolCount();
- }
- @Override
- public boolean hasSymbol(int symbol) {
- return locals == null? false : locals.hasSymbol(symbol);
- }
-
- @Override
- public void clearUnit() {
- locals = null;
- }
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java
index 782ebf5..0848306 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java
@@ -22,6 +22,14 @@ package org.apache.commons.jexl3.parser;
public class ASTIdentifier extends JexlNode {
protected String name = null;
protected int symbol = -1;
+ protected int flags = 0;
+
+ /** The redefined variable flag. */
+ private static final int REDEFINED = 0;
+ /** The shaded variable flag. */
+ private static final int SHADED = 1;
+ /** The captured variable flag. */
+ private static final int CAPTURED = 2;
ASTIdentifier(int id) {
super(id);
@@ -47,11 +55,56 @@ public class ASTIdentifier extends JexlNode {
symbol = r;
name = identifier;
}
-
+
public int getSymbol() {
return symbol;
}
+
+ /**
+ * Sets the value of a flag in a mask.
+ * @param ordinal the flag ordinal
+ * @param mask the flags mask
+ * @param value true or false
+ * @return the new flags mask value
+ */
+ private static int set(int ordinal, int mask, boolean value) {
+ return value? mask | (1 << ordinal) : mask & ~(1 << ordinal);
+ }
+ /**
+ * Checks the value of a flag in the mask.
+ * @param ordinal the flag ordinal
+ * @param mask the flags mask
+ * @return the mask value with this flag or-ed in
+ */
+ private static boolean isSet(int ordinal, int mask) {
+ return (mask & 1 << ordinal) != 0;
+ }
+
+ public void setRedefined(boolean f) {
+ flags = set(REDEFINED, flags, f);
+ }
+
+ public boolean isRedefined() {
+ return isSet(REDEFINED, flags);
+ }
+
+ public void setShaded(boolean f) {
+ flags = set(SHADED, flags, f);
+ }
+
+ public boolean isShaded() {
+ return isSet(SHADED, flags);
+ }
+
+ public void setCaptured(boolean f) {
+ flags = set(CAPTURED, flags, f);
+ }
+
+ public boolean isCaptured() {
+ return isSet(CAPTURED, flags);
+ }
+
public String getName() {
return name;
}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
index 51c5d3b..f5f47df 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
@@ -20,20 +20,17 @@ import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.internal.Scope;
import java.util.Map;
import org.apache.commons.jexl3.internal.Frame;
-import org.apache.commons.jexl3.internal.LexicalScope;
/**
* Enhanced script to allow parameters declaration.
*/
-public class ASTJexlScript extends JexlNode implements JexlParser.LexicalUnit {
+public class ASTJexlScript extends JexlLexicalNode {
/** The pragmas. */
private Map<String, Object> pragmas = null;
/** Features. */
private JexlFeatures features = null;
/** The script scope. */
private Scope scope = null;
- /** The local symbol set. */
- private LexicalScope locals = null;
public ASTJexlScript(int id) {
super(id);
@@ -42,30 +39,7 @@ public class ASTJexlScript extends JexlNode implements JexlParser.LexicalUnit {
public ASTJexlScript(Parser p, int id) {
super(p, id);
}
-
- @Override
- public boolean declareSymbol(int symbol) {
- if (locals == null) {
- locals = new LexicalScope(null);
- }
- return locals.declareSymbol(symbol);
- }
-
- @Override
- public int getSymbolCount() {
- return locals == null? 0 : locals.getSymbolCount();
- }
-
- @Override
- public boolean hasSymbol(int symbol) {
- return locals == null? false : locals.hasSymbol(symbol);
- }
-
- @Override
- public void clearUnit() {
- locals = null;
- }
-
+
/**
* Consider script with no parameters that return lambda as parametric-scripts.
* @return the script
@@ -84,7 +58,8 @@ public class ASTJexlScript extends JexlNode implements JexlParser.LexicalUnit {
public Object jjtAccept(ParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
- /**
+
+ /**
* Sets this script pragmas.
* @param thePragmas the pragmas
*/
@@ -186,11 +161,11 @@ public class ASTJexlScript extends JexlNode implements JexlParser.LexicalUnit {
}
/**
- * Checks whether a given symbol is hoisted.
+ * Checks whether a given symbol is captured.
* @param symbol the symbol number
- * @return true if hoisted, false otherwise
+ * @return true if captured, false otherwise
*/
- public boolean isHoistedSymbol(int symbol) {
- return scope != null? scope.isHoistedSymbol(symbol) : false;
+ public boolean isCapturedSymbol(int symbol) {
+ return scope != null? scope.isCapturedSymbol(symbol) : false;
}
}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java b/src/main/java/org/apache/commons/jexl3/parser/JexlLexicalNode.java
similarity index 75%
copy from src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java
copy to src/main/java/org/apache/commons/jexl3/parser/JexlLexicalNode.java
index 03a00d9..8540b65 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlLexicalNode.java
@@ -19,30 +19,26 @@ package org.apache.commons.jexl3.parser;
import org.apache.commons.jexl3.internal.LexicalScope;
/**
- * Declares a local variable.
+ * Base class for AST nodes behaving as lexical units.
+ * @since 3.2
*/
-public class ASTBlock extends JexlNode implements JexlParser.LexicalUnit {
+public class JexlLexicalNode extends JexlNode implements JexlParser.LexicalUnit {
private LexicalScope locals = null;
- public ASTBlock(int id) {
+ public JexlLexicalNode(int id) {
super(id);
}
- public ASTBlock(Parser p, int id) {
+ public JexlLexicalNode(Parser p, int id) {
super(p, id);
}
-
- @Override
- public Object jjtAccept(ParserVisitor visitor, Object data) {
- return visitor.visit(this, data);
- }
@Override
public boolean declareSymbol(int symbol) {
if (locals == null) {
- locals = new LexicalScope(null);
+ locals = new LexicalScope();
}
- return locals.declareSymbol(symbol);
+ return locals.addSymbol(symbol);
}
@Override
@@ -54,9 +50,9 @@ public class ASTBlock extends JexlNode implements JexlParser.LexicalUnit {
public boolean hasSymbol(int symbol) {
return locals == null? false : locals.hasSymbol(symbol);
}
-
+
@Override
- public void clearUnit() {
- locals = null;
+ public LexicalScope getLexicalScope() {
+ return locals;
}
-}
\ No newline at end of file
+}
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 93b2bc3..940f17a 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
@@ -270,4 +270,60 @@ public abstract class JexlNode extends SimpleNode {
}
return false;
}
+
+ /**
+ * An info bound to its node.
+ * <p>Used to parse expressions for templates.
+ */
+ public static class Info extends JexlInfo {
+ JexlNode node = null;
+
+ /**
+ * Default ctor.
+ * @param jnode the node
+ */
+ public Info(JexlNode jnode) {
+ this(jnode, jnode.jexlInfo());
+ }
+
+ /**
+ * Copy ctor.
+ * @param jnode the node
+ * @param info the
+ */
+ public Info(JexlNode jnode, JexlInfo info) {
+ this(jnode, info.getName(), info.getLine(), info.getColumn());
+ }
+
+ /**
+ * Full detail ctor.
+ * @param jnode the node
+ * @param name the file name
+ * @param l the line
+ * @param c the column
+ */
+ private Info(JexlNode jnode, String name, int l, int c) {
+ super(name, l, c);
+ node = jnode;
+ }
+
+ /**
+ * @return the node this info is bound to
+ */
+ public JexlNode getNode() {
+ return node;
+ }
+
+ @Override
+ public JexlInfo at(int l, int c) {
+ return new Info(node, getName(), l, c);
+ }
+
+ @Override
+ public JexlInfo detach() {
+ node = null;
+ return this;
+ }
+ }
+
}
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 75de958..42fc47f 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -21,6 +21,7 @@ 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 java.io.BufferedReader;
import java.io.IOException;
@@ -75,10 +76,6 @@ public abstract class JexlParser extends StringParser {
*/
protected final Deque<Integer> loopCounts = new ArrayDeque<Integer>();
/**
- * Lexical unit merge, next block push is swallowed.
- */
- protected boolean mergeBlock = false;
- /**
* The current lexical block.
*/
protected LexicalUnit block = null;
@@ -105,16 +102,13 @@ public abstract class JexlParser extends StringParser {
* @return true if declared, false otherwise
*/
boolean hasSymbol(int symbol);
-
+
/**
* @return the number of local variables declared in this unit
*/
int getSymbolCount();
-
- /**
- * Clears this unit.
- */
- void clearUnit();
+
+ LexicalScope getLexicalScope();
}
/**
@@ -131,7 +125,6 @@ public abstract class JexlParser extends StringParser {
loopCount = 0;
blocks.clear();
block = null;
- mergeBlock = false;
}
/**
* Utility function to create '.' separated string from a list of string.
@@ -262,10 +255,34 @@ public abstract class JexlParser extends StringParser {
} else {
block = null;
}
- //unit.clearUnit();
}
}
-
+
+ /**
+ * Checks if a symbol is defined in lexical scopes.
+ * <p>This works with with parsed scripts in template resolution only.
+ * @param info an info linked to a node
+ * @param symbol
+ * @return true if symbol accessible in lexical scope
+ */
+ private boolean isSymbolDeclared(JexlNode.Info info, int symbol) {
+ JexlNode walk = info.getNode();
+ while(walk != null) {
+ if (walk instanceof JexlParser.LexicalUnit) {
+ LexicalScope scope = ((JexlParser.LexicalUnit) walk).getLexicalScope();
+ if (scope != null && scope.hasSymbol(symbol)) {
+ return true;
+ }
+ // stop at first new scope reset, aka lambda
+ if (walk instanceof ASTJexlLambda) {
+ break;
+ }
+ }
+ walk = walk.jjtGetParent();
+ }
+ return false;
+ }
+
/**
* Checks whether an identifier is a local variable or argument, ie a symbol, stored in a register.
* @param identifier the identifier
@@ -277,7 +294,10 @@ public abstract class JexlParser extends StringParser {
Integer symbol = frame.getSymbol(name);
if (symbol != null) {
boolean declared = true;
- if (getFeatures().isLexical()) {
+ if (frame.isCapturedSymbol(symbol)) {
+ // captured are declared in all cases
+ identifier.setCaptured(true);
+ } else {
declared = block.hasSymbol(symbol);
// one of the lexical blocks above should declare it
if (!declared) {
@@ -288,12 +308,17 @@ public abstract class JexlParser extends StringParser {
}
}
}
+ if (!declared && info instanceof JexlNode.Info) {
+ declared = isSymbolDeclared((JexlNode.Info) info, symbol);
+ }
}
- if (declared) {
- identifier.setSymbol(symbol, name);
- } else if (getFeatures().isLexicalShade()) {
- // can not reuse a local as a global
- throw new JexlException(identifier, name + ": variable is not defined");
+ identifier.setSymbol(symbol, name);
+ if (!declared) {
+ identifier.setShaded(true);
+ if (getFeatures().isLexicalShade()) {
+ // can not reuse a local as a global
+ throw new JexlException(identifier, name + ": variable is not defined");
+ }
}
}
}
@@ -324,7 +349,7 @@ public abstract class JexlParser extends StringParser {
*/
private boolean declareSymbol(int symbol) {
if (blocks != null) {
- for(LexicalUnit lu : blocks) {
+ for (LexicalUnit lu : blocks) {
if (lu.hasSymbol(symbol)) {
return false;
}
@@ -353,9 +378,16 @@ public abstract class JexlParser extends StringParser {
}
int symbol = frame.declareVariable(name);
var.setSymbol(symbol, name);
+ if (frame.isCapturedSymbol(symbol)) {
+ var.setCaptured(true);
+ }
// lexical feature error
- if (!declareSymbol(symbol) && getFeatures().isLexical()) {
- throw new JexlException(var, name + ": variable is already declared");
+ if (!declareSymbol(symbol)) {
+ if (getFeatures().isLexical()) {
+ throw new JexlException(var, name + ": variable is already declared");
+ } else {
+ var.setRedefined(true);
+ }
}
}
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 e3d5e03..89390dd 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -61,7 +61,7 @@ public final class Parser extends JexlParser
frame = scope;
ReInit(new java.io.StringReader(jexlSrc));
ASTJexlScript script = jexlFeatures.supportsScript()? JexlScript(scope) : JexlExpression(scope);
- script.jjtSetValue(info);
+ script.jjtSetValue(info.detach());
script.setFeatures(jexlFeatures);
script.setPragmas(pragmas != null
? Collections.<String,Object>unmodifiableMap(pragmas)
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index db4d582..6c4793b 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -26,9 +26,18 @@
</properties>
<body>
<release version="3.2" date="unreleased">
+ <action dev="henrib" type="fix" issue="JEXL-322" due-to="Constantin Hirsch">
+ JXLT String literals cannot contain curly braces
+ </action>
<action dev="henrib" type="fix" issue="JEXL-321" due-to="Dmitri Blinov">
Empty do-while loop is broken
</action>
+ <action dev="henrib" type="fix" issue="JEXL-320" due-to="David Costanzo">
+ "mvn test" fails with COMPILATION ERROR in SynchronizedArithmetic.java on Java 11
+ </action>
+ <action dev="henrib" type="fix" issue="JEXL-319" due-to="David Costanzo">
+ Apache project documentation gives instructions in subversion
+ </action>
<action dev="henrib" type="fix" issue="JEXL-318" due-to="Dmitri Blinov">
Annotation processing may fail in lexical mode
</action>
diff --git a/src/site/xdoc/reference/syntax.xml b/src/site/xdoc/reference/syntax.xml
index e447787..07d4175 100644
--- a/src/site/xdoc/reference/syntax.xml
+++ b/src/site/xdoc/reference/syntax.xml
@@ -192,7 +192,7 @@
<p>Note that functions can use local variables and parameters from their declaring script.
Those variables values are bound to the function environment at definition time.</p>
<code>var t = 20; var s = function(x, y) {x + y + t}; t = 54; s(15, 7)</code>
- The function closure hoists 't' when defined; the result of the evaluation will
+ The function closure captures 't' when defined; the result of the evaluation will
lead to <code>15 + 7 + 20 = 42</code>.
</td>
</tr>
diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
index 3986c2e..95d1dd3 100644
--- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
@@ -16,10 +16,10 @@
*/
package org.apache.commons.jexl3;
+import org.apache.commons.jexl3.internal.Debugger;
+import org.apache.commons.jexl3.internal.Engine;
import org.apache.commons.jexl3.internal.TemplateDebugger;
import org.apache.commons.jexl3.internal.TemplateScript;
-import org.apache.commons.jexl3.internal.Debugger;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -27,26 +27,54 @@ import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
-
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
+
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
/**
* Test cases for the UnifiedEL.
*/
@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+@RunWith(Parameterized.class)
public class JXLTTest extends JexlTestCase {
- private static final JexlEngine ENGINE = new JexlBuilder().silent(false).cache(128).strict(true).create();
- private static final JxltEngine JXLT = ENGINE.createJxltEngine();
- private static final Log LOG = LogFactory.getLog(JxltEngine.class);
+ private static final Log LOGGER = LogFactory.getLog(JxltEngine.class);
private final MapContext vars = new MapContext();
private JexlEvalContext context = null;
-
+ private final JexlEngine ENGINE;
+ private final JxltEngine JXLT;
+
+ public JXLTTest(JexlEngine jexl) {
+ super("JXLTTest");
+ ENGINE = jexl;
+ JXLT = ENGINE.createJxltEngine();
+ }
+
+ @Parameterized.Parameters
+ public static List<JexlEngine> engines() {
+ JexlFeatures f = new JexlFeatures();
+ f.lexical(true).lexicalShade(true);
+ return Arrays.<JexlEngine>asList(new JexlEngine[] {
+ new JexlBuilder().silent(false)
+ .lexical(true).lexicalShade(true)
+ .cache(128).strict(true).create(),
+
+ new JexlBuilder().features(f).silent(false)
+ .lexical(true).lexicalShade(true)
+ .cache(128).strict(true).create(),
+
+ new JexlBuilder().silent(false)
+ .cache(128).strict(true).create(),
+ });
+ }
+
@Before
@Override
public void setUp() throws Exception {
@@ -61,6 +89,16 @@ public class JXLTTest extends JexlTestCase {
debuggerCheck(ENGINE);
super.tearDown();
}
+
+ private boolean isLexicalShade() {
+ JexlOptions options = context.getEngineOptions();
+ if (options.isLexicalShade()) {
+ return true;
+ }
+ options = new JexlOptions();
+ ((Engine) ENGINE).optionsSet(options);
+ return options.isLexicalShade();
+ }
private static String refactor(TemplateDebugger td, JxltEngine.Template ts) {
boolean dbg = td.debug((TemplateScript)ts);
@@ -109,10 +147,6 @@ public class JXLTTest extends JexlTestCase {
}
}
- public JXLTTest() {
- super("JXLTTest");
- }
-
@Test
public void testStatement() throws Exception {
Froboz froboz = new Froboz(32);
@@ -339,7 +373,7 @@ public class JXLTTest extends JexlTestCase {
} catch (JxltEngine.Exception xjexl) {
// expected
String xmsg = xjexl.getMessage();
- LOG.warn(xmsg);
+ LOGGER.warn(xmsg);
}
}
@@ -353,7 +387,7 @@ public class JXLTTest extends JexlTestCase {
} catch (JxltEngine.Exception xjexl) {
// expected
String xmsg = xjexl.getMessage();
- LOG.warn(xmsg);
+ LOGGER.warn(xmsg);
}
}
@@ -368,7 +402,7 @@ public class JXLTTest extends JexlTestCase {
} catch (JxltEngine.Exception xjexl) {
// expected
String xmsg = xjexl.getMessage();
- LOG.warn(xmsg);
+ LOGGER.warn(xmsg);
}
}
@@ -382,7 +416,7 @@ public class JXLTTest extends JexlTestCase {
} catch (JxltEngine.Exception xjexl) {
// expected
String xmsg = xjexl.getMessage();
- LOG.warn(xmsg);
+ LOGGER.warn(xmsg);
}
}
@@ -700,7 +734,7 @@ public class JXLTTest extends JexlTestCase {
@Test
public void testInterpolation() throws Exception {
String expr = "`Hello \n${user}`";
- JexlScript script = JEXL.createScript(expr);
+ JexlScript script = ENGINE.createScript(expr);
context.set("user", "Dimitri");
Object value = script.execute(context);
Assert.assertEquals(expr, "Hello \nDimitri", value);
@@ -711,62 +745,70 @@ public class JXLTTest extends JexlTestCase {
@Test
public void testInterpolationGlobal() throws Exception {
+ if (isLexicalShade()) {
+ context.set("user", null);
+ }
String expr = "user='Dimitri'; `Hello \n${user}`";
- Object value = JEXL.createScript(expr).execute(context);
+ Object value = ENGINE.createScript(expr).execute(context);
Assert.assertEquals(expr, "Hello \nDimitri", value);
}
@Test
public void testInterpolationLocal() throws Exception {
String expr = "var user='Henrib'; `Hello \n${user}`";
- Object value = JEXL.createScript(expr).execute(context);
+ Object value = ENGINE.createScript(expr).execute(context);
Assert.assertEquals(expr, "Hello \nHenrib", value);
}
@Test
public void testInterpolationLvsG() throws Exception {
+ if (isLexicalShade()) {
+ context.set("user", null);
+ }
String expr = "user='Dimitri'; var user='Henrib'; `H\\\"ello \n${user}`";
- Object value = JEXL.createScript(expr).execute(context);
+ Object value = ENGINE.createScript(expr).execute(context);
Assert.assertEquals(expr, "H\"ello \nHenrib", value);
}
@Test
public void testInterpolationLvsG2() throws Exception {
+ if (isLexicalShade()) {
+ context.set("user", null);
+ }
String expr = "user='Dimitri'; var user='Henrib'; `H\\`ello \n${user}`";
- Object value = JEXL.createScript(expr).execute(context);
+ Object value = ENGINE.createScript(expr).execute(context);
Assert.assertEquals(expr, "H`ello \nHenrib", value);
}
@Test
public void testInterpolationParameter() throws Exception {
String expr = "(user)->{`Hello \n${user}`}";
- Object value = JEXL.createScript(expr).execute(context, "Henrib");
+ Object value = ENGINE.createScript(expr).execute(context, "Henrib");
Assert.assertEquals(expr, "Hello \nHenrib", value);
- value = JEXL.createScript(expr).execute(context, "Dimitri");
+ value = ENGINE.createScript(expr).execute(context, "Dimitri");
Assert.assertEquals(expr, "Hello \nDimitri", value);
}
-//
-//
-// @Test
-// public void testDeferredTemplate() throws Exception {
-// JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(
-// "select * from \n"+
-// "##for(var c : tables) {\n"+
-// "#{c} \n"+
-// "##}\n"+
-// "where $(w}\n"
-// ));
-// StringWriter strw = new StringWriter();
-// context.set("tables", new String[]{"table1", "table2"});
-// t = t.prepare(context);
-// vars.clear();
-// context.set("w" ,"x=1");
-// t.evaluate(context, strw);
-// String output = strw.toString();
-// Assert.assertEquals("fourty-two", output);
-//
-// }
-
+
+ @Test
+ public void testImmediateTemplate() throws Exception {
+ context.set("tables", new String[]{"table1", "table2"});
+ context.set("w" ,"x=1");
+ JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(
+ "select * from \n"+
+ "$$var comma = false; \n"+
+ "$$for(var c : tables) { \n"+
+ "$$ if (comma) $jexl.write(','); else comma = true;\n"+
+ "${c}"+
+ "\n$$}\n"+
+ "where ${w}\n"
+ ));
+ StringWriter strw = new StringWriter();
+ //vars.clear();
+ t.evaluate(context, strw);
+ String output = strw.toString();
+ Assert.assertTrue(output.contains("table1") && output.contains("table2"));
+ }
+
public static class Executor311 {
private final String name;
@@ -959,4 +1001,5 @@ public class JXLTTest extends JexlTestCase {
output = strw.toString();
Assert.assertEquals(s315, output);
}
+
}
diff --git a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
index f0c233c..1c8b3af 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
@@ -36,8 +36,6 @@ public class JexlEvalContext implements
/** The options. */
private final JexlOptions options = new JexlOptions();
-
-
/**
* Default constructor.
*/
diff --git a/src/test/java/org/apache/commons/jexl3/LambdaTest.java b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
index eb8bf7e..c24cfd1 100644
--- a/src/test/java/org/apache/commons/jexl3/LambdaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
@@ -168,9 +168,9 @@ public class LambdaTest extends JexlTestCase {
hvars = s15.getVariables();
Assert.assertEquals(1, hvars.size());
- // declaring a local that overrides hoisted
+ // declaring a local that overrides captured
// in 3.1, such a local was considered local
- // per 3.2, this local is considered hoisted
+ // per 3.2, this local is considered captured
strs = "(x)->{ (y)->{ var z = 169; var x; x + y } }";
s42 = jexl.createScript(strs);
result = s42.execute(ctx, 15);
@@ -181,7 +181,7 @@ public class LambdaTest extends JexlTestCase {
Assert.assertEquals(1, localv.length);
hvars = s15.getVariables();
Assert.assertEquals(1, hvars.size());
- // evidence this is not (strictly) a local since it inherited a hoisted value
+ // evidence this is not (strictly) a local since it inherited a captured value
result = ((JexlScript) s15).execute(ctx, 27);
Assert.assertEquals(42, result);
}
@@ -204,7 +204,7 @@ public class LambdaTest extends JexlTestCase {
public void testRecurse2() throws Exception {
JexlEngine jexl = createEngine();
JexlContext jc = new MapContext();
- // adding some hoisted vars to get it confused
+ // adding some captured vars to get it confused
try {
JexlScript script = jexl.createScript(
"var y = 1; var z = 1; "
@@ -221,7 +221,7 @@ public class LambdaTest extends JexlTestCase {
public void testRecurse3() throws Exception {
JexlEngine jexl = createEngine();
JexlContext jc = new MapContext();
- // adding some hoisted vars to get it confused
+ // adding some captured vars to get it confused
try {
JexlScript script = jexl.createScript(
"var y = 1; var z = 1;var foo = (x)->{y + z}; "
@@ -350,7 +350,7 @@ public class LambdaTest extends JexlTestCase {
Assert.assertEquals(8, result);
}
- // redefining an hoisted var is not resolved correctly in left hand side;
+ // redefining an captured var is not resolved correctly in left hand side;
// declare the var in local frame, resolved in local frame instead of parent
// @Test
// public void test271e() throws Exception {
diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
index 2d7b820..b525417 100644
--- a/src/test/java/org/apache/commons/jexl3/LexicalTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
@@ -286,14 +286,23 @@ public class LexicalTest {
String ctl = "<report>\n\n3\n</report>\n";
Assert.assertEquals(ctl, output);
}
+
+ public static class DebugContext extends MapContext {
+ public DebugContext() {
+ super();
+ }
+ public Object debug(Object arg) {
+ return arg;
+ }
+ }
@Test
public void testLexical5() throws Exception {
JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
- JexlContext ctxt = new MapContext();
+ JexlContext ctxt = new DebugContext();
JexlScript script;
Object result;
- script = jexl.createScript("var x = 42; var z = 169; var y = () -> { {var x = -42; }; return x; }; y()");
+ script = jexl.createScript("var x = 42; var y = () -> { {var x = debug(-42); }; return x; }; y()");
try {
result = script.execute(ctxt);
Assert.assertEquals(42, result);
@@ -381,10 +390,10 @@ public class LexicalTest {
@Test
public void testScopeFrame() throws Exception {
- LexicalScope scope = new LexicalScope(null);
+ LexicalScope scope = new LexicalScope();
for(int i = 0; i < 128; i += 2) {
- Assert.assertTrue(scope.declareSymbol(i));
- Assert.assertFalse(scope.declareSymbol(i));
+ Assert.assertTrue(scope.addSymbol(i));
+ Assert.assertFalse(scope.addSymbol(i));
}
for(int i = 0; i < 128; i += 2) {
Assert.assertTrue(scope.hasSymbol(i));
@@ -739,4 +748,16 @@ public class LexicalTest {
Object result = script.execute(null);
Assert.assertEquals(result, 42);
}
+
+ @Test
+ public void testHoisted() throws Exception {
+ JexlFeatures f = new JexlFeatures();
+ f.lexical(true);
+ JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
+ JexlScript script = jexl.createScript("var x = 10; var a = function(var b) {for (var q : 1 ..10) {return x + b}}; a(32)");
+ JexlContext jc = new MapContext();
+ Object result = script.execute(null);
+ Assert.assertEquals(result, 42);
+ }
+
}