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);
+    }
+    
 }