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:33 UTC

[commons-jexl] branch master updated (f094271 -> 51a981c)

This is an automated email from the ASF dual-hosted git repository.

henrib pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git.


    from f094271  JEXL-307: fixing regression, parameter declaration under lexical feature Task #JEXL-307 - Variable redeclaration option
     new fc4b5a4  JEXL-322: read through strings when parsing template exrepssions Task #JEXL-322 - JXLT String literals cannot contain curly braces
     new 51a981c  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

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 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/TemplateEngine.java     |  41 ++++++-
 .../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 +-
 .../org/apache/commons/jexl3/Issues300Test.java    |  34 ++++++
 .../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 ++++-
 31 files changed, 561 insertions(+), 362 deletions(-)
 copy src/main/java/org/apache/commons/jexl3/parser/{ASTBlock.java => JexlLexicalNode.java} (75%)


[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

Posted by he...@apache.org.
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);
+    }
+    
 }


[commons-jexl] 01/02: JEXL-322: read through strings when parsing template exrepssions Task #JEXL-322 - JXLT String literals cannot contain curly braces

Posted by he...@apache.org.
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 fc4b5a4d52c68c6f788526983a6df5bdd4f972b0
Author: henrib <he...@apache.org>
AuthorDate: Mon Jan 27 19:40:02 2020 +0100

    JEXL-322: read through strings when parsing template exrepssions
    Task #JEXL-322 - JXLT String literals cannot contain curly braces
---
 .../commons/jexl3/internal/TemplateEngine.java     | 41 +++++++++++++++++++---
 .../org/apache/commons/jexl3/Issues300Test.java    | 34 ++++++++++++++++++
 2 files changed, 70 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
index 449848d..056c2cc 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
@@ -722,6 +722,37 @@ public final class TemplateEngine extends JxltEngine {
     }
 
     /**
+     * Helper for expression dealing with embedded strings.
+     * @param strb the expression buffer to copy characters into
+     * @param expr the source
+     * @param position the offset into the source
+     * @param c the separator character
+     * @return the new position to read the source from 
+     */
+    private static int append(StringBuilder strb, CharSequence expr, int position, char c) {
+        strb.append(c);
+        if (c != '"' && c != '\'') {
+            return position;
+        } 
+        // read thru strings
+        int end = expr.length();
+        boolean escape= false;
+        int index = position + 1;
+        for (; index < end; ++index) {
+            char ec = expr.charAt(index);
+            strb.append(ec);
+            if (ec == '\\') {
+                escape = !escape;
+            } else if (escape) {
+                escape = false;
+            } else if (ec == c) {
+                break;
+            }
+        }
+        return index;
+    }
+    
+    /**
      * Parses a unified expression.
      * @param info  the source info
      * @param expr  the string expression
@@ -755,7 +786,7 @@ public final class TemplateEngine extends JxltEngine {
                         state = ParseState.ESCAPE;
                     } else {
                         // do buildup expr
-                        strb.append(c);
+                        column = append(strb, expr, column, c);
                     }
                     break;
                 case IMMEDIATE0: // $
@@ -770,7 +801,7 @@ public final class TemplateEngine extends JxltEngine {
                     } else {
                         // revert to CONST
                         strb.append(immediateChar);
-                        strb.append(c);
+                        column = append(strb, expr, column, c);
                         state = ParseState.CONST;
                     }
                     break;
@@ -786,7 +817,7 @@ public final class TemplateEngine extends JxltEngine {
                     } else {
                         // revert to CONST
                         strb.append(deferredChar);
-                        strb.append(c);
+                        column = append(strb, expr, column, c);
                         state = ParseState.CONST;
                     }
                     break;
@@ -811,7 +842,7 @@ public final class TemplateEngine extends JxltEngine {
                             immediate1 += 1;
                         }
                         // do buildup expr
-                        strb.append(c);
+                        column = append(strb, expr, column, c);
                     }
                     break;
                 case DEFERRED1: // #{...
@@ -863,7 +894,7 @@ public final class TemplateEngine extends JxltEngine {
                         }
                     } else {
                         // do buildup expr
-                        strb.append(c);
+                        column = append(strb, expr, column, c);
                     }
                     break;
                 case ESCAPE:
diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
index 913133b..6649f3a 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.jexl3;
 
+import java.io.StringReader;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -344,4 +346,36 @@ public class Issues300Test {
         result = script.execute(ctxt, 21);
         Assert.assertEquals(42, result);
     }
+
+    @Test
+    public void test322() throws Exception {
+        JexlEngine jexl = new JexlBuilder().strict(true).create();
+        JxltEngine jxlt = jexl.createJxltEngine();
+        JexlContext context = new MapContext();
+
+        String[] ins = new String[]{
+            "${'{'}", "${\"{\"}", "${\"{}\"}", "${'{42}'}", "${\"{\\\"\\\"}\"}"
+        };
+        String[] ctls = new String[]{
+            "{", "{", "{}", "{42}", "{\"\"}"
+        };
+        StringWriter strw;
+        JxltEngine.Template template;
+        String output;
+
+        for (int i = 0; i < ins.length; ++i) {
+            String src = ins[i];
+            try {
+                template = jxlt.createTemplate("$$", new StringReader(src));
+            } catch(JexlException xany) {
+                Assert.fail(src);
+                throw xany;
+            }
+            strw = new StringWriter();
+            template.evaluate(context, strw);
+            output = strw.toString();
+            Assert.assertEquals(ctls[i], output);
+        }
+    }
+
 }