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 2019/10/29 15:57:55 UTC

[commons-jexl] 02/07: JEXL-307: added lexical and lexical shade option; JEXL-307: added lexical feature, controlling var redefinition at parsing time; JEXL-307: refactored option (JexlOption) and added feature to deal with lexical scope; JEXL-307: refactored test code to use new option classes; JEXL-307: added lexical scope and frame handling interpreting scripts, lambdas, for-loops; JEXL-314: changed JexlArithmetic/Interpreter to handle NullOperand exception when needed 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 d15901734478efeac605d3dc55c4b25a479c28d4
Author: henrib <he...@apache.org>
AuthorDate: Sun Oct 27 13:59:37 2019 +0100

    JEXL-307: added lexical and lexical shade option;
    JEXL-307: added lexical feature, controlling var redefinition at parsing time;
    JEXL-307: refactored option (JexlOption) and added feature to deal with lexical scope;
    JEXL-307: refactored test code to use new option classes;
    JEXL-307: added lexical scope and frame handling interpreting scripts, lambdas, for-loops;
    JEXL-314: changed JexlArithmetic/Interpreter to handle NullOperand exception when needed
    Task #JEXL-307 - Variable redeclaration option
---
 RELEASE-NOTES.txt                                  |   8 +-
 pom.xml                                            | 173 +++++++-------
 .../org/apache/commons/jexl3/JexlArithmetic.java   |  87 +++++--
 .../java/org/apache/commons/jexl3/JexlBuilder.java |  56 ++++-
 .../java/org/apache/commons/jexl3/JexlContext.java |  19 +-
 .../java/org/apache/commons/jexl3/JexlEngine.java  |  10 +-
 .../org/apache/commons/jexl3/JexlException.java    |  74 ++++--
 .../org/apache/commons/jexl3/JexlFeatures.java     |  21 +-
 .../java/org/apache/commons/jexl3/JexlOptions.java | 192 +++++++++++++++
 .../org/apache/commons/jexl3/internal/Closure.java |  24 +-
 .../org/apache/commons/jexl3/internal/Engine.java  |  85 +++++--
 .../org/apache/commons/jexl3/internal/Frame.java   | 126 ++++++++++
 .../apache/commons/jexl3/internal/Interpreter.java | 259 +++++++++++++--------
 .../commons/jexl3/internal/InterpreterBase.java    | 151 ++++++++----
 .../commons/jexl3/internal/LexicalScope.java       | 118 ++++++++++
 .../org/apache/commons/jexl3/internal/Options.java | 229 ++++++++++++++++++
 .../org/apache/commons/jexl3/internal/Scope.java   | 119 +---------
 .../org/apache/commons/jexl3/internal/Script.java  |   8 +-
 .../jexl3/internal/TemplateInterpreter.java        |   4 +-
 .../commons/jexl3/internal/TemplateScript.java     |   4 +-
 .../jexl3/internal/introspection/ClassMap.java     |   2 +-
 .../internal/introspection/ConstructorMethod.java  |   3 +-
 .../jexl3/internal/introspection/Introspector.java |  18 +-
 .../internal/introspection/MethodExecutor.java     |   2 +-
 .../jexl3/internal/introspection/Uberspect.java    |  12 +-
 .../org/apache/commons/jexl3/parser/ASTBlock.java  |  62 +++++
 .../commons/jexl3/parser/ASTForeachStatement.java  |  62 +++++
 .../apache/commons/jexl3/parser/ASTJexlScript.java |  50 +++-
 .../org/apache/commons/jexl3/parser/JexlNode.java  |  10 +-
 .../apache/commons/jexl3/parser/JexlParser.java    | 167 +++++++++++--
 .../org/apache/commons/jexl3/parser/Parser.jjt     |  25 +-
 src/site/xdoc/changes.xml                          |   9 +
 src/site/xdoc/reference/syntax.xml                 |   3 +
 .../org/apache/commons/jexl3/AnnotationTest.java   |  29 +--
 .../org/apache/commons/jexl3/ArithmeticTest.java   |  43 ++--
 .../apache/commons/jexl3/BitwiseOperatorTest.java  |   2 +-
 .../org/apache/commons/jexl3/ClassCreatorTest.java |   5 +-
 .../org/apache/commons/jexl3/ExceptionTest.java    |  35 +--
 .../org/apache/commons/jexl3/FeaturesTest.java     |   2 +-
 src/test/java/org/apache/commons/jexl3/IfTest.java |  59 ++---
 .../org/apache/commons/jexl3/Issues100Test.java    |  11 +-
 .../org/apache/commons/jexl3/Issues200Test.java    |   8 +-
 .../org/apache/commons/jexl3/Issues300Test.java    |  83 +++++++
 .../java/org/apache/commons/jexl3/IssuesTest.java  |  39 ++--
 .../java/org/apache/commons/jexl3/JXLTTest.java    |  34 ++-
 .../org/apache/commons/jexl3/JexlEvalContext.java  | 158 ++-----------
 .../java/org/apache/commons/jexl3/JexlTest.java    |  21 +-
 .../java/org/apache/commons/jexl3/LexicalTest.java | 228 ++++++++++++++++++
 .../java/org/apache/commons/jexl3/PragmaTest.java  |  41 ++++
 .../org/apache/commons/jexl3/ReadonlyContext.java  |  47 +---
 .../java/org/apache/commons/jexl3/VarTest.java     |   7 +-
 .../org/apache/commons/jexl3/junit/Asserter.java   |  11 +-
 52 files changed, 2274 insertions(+), 781 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 3373d7c..6a6ee8d 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -36,7 +36,8 @@ It is nevertheless possible and we are sorry if it causes problems to any of you
 
 What's new in 3.2:
 ==================
-
+* Lexical scoping for local variables and parameters allowing at parse and/or runtime to reduce scripting errors and bugs and align with
+  known development languages best practice (C, Java, ...).
 * Interpolated strings in identifiers, as in x.`${prefix}_${suffix}` that in many cases would be equivalent to
   x[prefix + '_' + suffix] or x[`${prefix}_${suffix}`].
 * Safe-navigation mode and operator '?.' allowing lenient handling of non-existent or null properties so that an expression
@@ -47,7 +48,7 @@ What's new in 3.2:
 
 New Features in 3.2:
 ====================
-
+* JEXL-307:      Variable redeclaration option
 * JEXL-295:      Add unary plus operator
 * JEXL-292:      Allow specifying custom Permissions class for Uberspect to be used later by Introspector
 * JEXL-288:      Annotation can not be specified for a standalone statement
@@ -72,7 +73,8 @@ New Features in 3.2:
 
 Bugs Fixed in 3.2:
 ==================
-
+* JEXL-315:      JxltEngine literal string strings ending in \ $ or # throw JxltEngine$Exception
+* JEXL-314:      Comparison NULL values of variables NAME1.NAME2
 * JEXL-312:      @NoJexl fails to disallow method call
 * JEXL-311:      Jxlt template scripts fail using verbatim expressions embedded in lambdas
 * JEXL-309:      Line numbers are not correct when template report errors
diff --git a/pom.xml b/pom.xml
index 23a9bac..b52a569 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,31 +16,55 @@
    limitations under the License.
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-parent</artifactId>
-        <version>48</version>
+        <version>49</version>
     </parent>
-    <modelVersion>4.0.0</modelVersion>
+    
     <groupId>org.apache.commons</groupId>
     <artifactId>commons-jexl3</artifactId>
     <version>3.2-SNAPSHOT</version>
     <name>Apache Commons JEXL</name>
-    <inceptionYear>2001</inceptionYear>
     <description>The Apache Commons JEXL library is an implementation of the JSTL Expression Language with extensions.</description>
     <url>https://commons.apache.org/proper/commons-jexl/</url>
+    <inceptionYear>2001</inceptionYear>
 
-    <issueManagement>
-        <system>jira</system>
-        <url>https://issues.apache.org/jira/browse/JEXL</url>
-    </issueManagement>
-
+    <properties>
+        <maven.compiler.source>1.6</maven.compiler.source>
+        <maven.compiler.target>1.6</maven.compiler.target>
+        <commons.componentid>jexl3</commons.componentid>
+        <commons.module.name>org.apache.commons.jexl3</commons.module.name>
+        <commons.release.version>3.1</commons.release.version>
+        <commons.site.path>jexl</commons.site.path>
+        <commons.scmPubUrl>https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-jexl</commons.scmPubUrl>
+        <commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory>
+        <!-- The RC version used in the staging repository URL. -->
+        <commons.rc.version>RC1</commons.rc.version>
+        <commons.release.2.version>2.1.1</commons.release.2.version>
+        <!-- Override the default, which is ${project.artifactId}-${commons.release.[23].version} -->
+        <commons.release.name>commons-jexl-${commons.release.version}</commons.release.name>
+        <commons.release.2.name>commons-jexl-${commons.release.2.version}</commons.release.2.name>
+        <commons.release.3.name>commons-jexl-${commons.release.3.version}</commons.release.3.name>
+        <commons.release.3.desc>Legacy</commons.release.3.desc>
+        <commons.release.3.version>1.1</commons.release.3.version>
+        <commons.release.3.binary.suffix />
+        <commons.jira.id>JEXL</commons.jira.id>
+        <commons.jira.pid>12310479</commons.jira.pid>
+        <checkstyle.plugin.version>2.17</checkstyle.plugin.version>
+        <checksyle.version>6.13</checksyle.version>
+    </properties>
+    
     <scm>
         <connection>scm:git:https://gitbox.apache.org/repos/asf/commons-jexl.git</connection>
         <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/commons-jexl.git</developerConnection>
         <url>https://gitbox.apache.org/repos/asf/commons-jexl.git</url>
-    </scm>
-
+    </scm> 
+    <issueManagement>
+        <system>jira</system>
+        <url>https://issues.apache.org/jira/browse/JEXL</url>
+    </issueManagement>
     <distributionManagement>
       <site>
         <id>apache.website</id>
@@ -49,55 +73,6 @@
       </site>
     </distributionManagement>
 
-    <developers>
-        <developer>
-            <name>dIon Gillard</name>
-            <id>dion</id>
-            <email>dion AT apache DOT org</email>
-            <organization>The Apache Software Foundation</organization>
-        </developer>
-        <developer>
-            <name>Geir Magnusson Jr.</name>
-            <id>geirm</id>
-            <email>geirm AT apache DOT org</email>
-            <organization>independent</organization>
-        </developer>
-        <developer>
-            <name>Tim O'Brien</name>
-            <id>tobrien</id>
-            <email>tobrien AT apache DOT org</email>
-            <organization>independent</organization>
-        </developer>
-        <developer>
-            <name>Peter Royal</name>
-            <id>proyal</id>
-            <email>proyal AT apache DOT org</email>
-            <organization>The Apache Software Foundation</organization>
-        </developer>
-        <developer>
-            <name>James Strachan</name>
-            <id>jstrachan</id>
-            <email>jstrachan AT apache DOT org</email>
-            <organization>SpiritSoft, Inc.</organization>
-        </developer>
-        <developer>
-            <name>Rahul Akolkar</name>
-            <id>rahul</id>
-            <email>rahul AT apache DOT org</email>
-            <organization>The Apache Software Foundation</organization>
-        </developer>
-        <developer>
-            <name>Sebastian Bazley</name>
-            <id>sebb</id>
-            <email>sebb AT apache DOT org</email>
-        </developer>
-        <developer>
-            <name>Henri Biestro</name>
-            <id>henrib</id>
-            <email>henrib AT apache DOT org</email>
-        </developer>
-    </developers>
-
     <dependencies>
         <dependency>
             <groupId>commons-logging</groupId>
@@ -112,31 +87,6 @@
         </dependency>
     </dependencies>
 
-    <properties>
-        <maven.compiler.source>1.6</maven.compiler.source>
-        <maven.compiler.target>1.6</maven.compiler.target>
-        <commons.componentid>jexl3</commons.componentid>
-        <commons.module.name>org.apache.commons.jexl3</commons.module.name>
-        <commons.release.version>3.1</commons.release.version>
-        <commons.site.path>jexl</commons.site.path>
-        <commons.scmPubUrl>https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-jexl</commons.scmPubUrl>
-        <commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory>
-        <!-- The RC version used in the staging repository URL. -->
-        <commons.rc.version>RC1</commons.rc.version>
-        <commons.release.2.version>2.1.1</commons.release.2.version>
-        <!-- Override the default, which is ${project.artifactId}-${commons.release.[23].version} -->
-        <commons.release.name>commons-jexl-${commons.release.version}</commons.release.name>
-        <commons.release.2.name>commons-jexl-${commons.release.2.version}</commons.release.2.name>
-        <commons.release.3.name>commons-jexl-${commons.release.3.version}</commons.release.3.name>
-        <commons.release.3.desc>Legacy</commons.release.3.desc>
-        <commons.release.3.version>1.1</commons.release.3.version>
-        <commons.release.3.binary.suffix />
-        <commons.jira.id>JEXL</commons.jira.id>
-        <commons.jira.pid>12310479</commons.jira.pid>
-        <checkstyle.plugin.version>2.17</checkstyle.plugin.version>
-        <checksyle.version>6.13</checksyle.version>
-    </properties>
-
     <build>
         <plugins>
             <plugin>
@@ -369,4 +319,59 @@
             </plugin>
         </plugins>
     </reporting>
+    
+    <developers>
+        <developer>
+            <name>dIon Gillard</name>
+            <id>dion</id>
+            <email>dion AT apache DOT org</email>
+            <organization>The Apache Software Foundation</organization>
+        </developer>
+        <developer>
+            <name>Geir Magnusson Jr.</name>
+            <id>geirm</id>
+            <email>geirm AT apache DOT org</email>
+            <organization>independent</organization>
+        </developer>
+        <developer>
+            <name>Tim O'Brien</name>
+            <id>tobrien</id>
+            <email>tobrien AT apache DOT org</email>
+            <organization>independent</organization>
+        </developer>
+        <developer>
+            <name>Peter Royal</name>
+            <id>proyal</id>
+            <email>proyal AT apache DOT org</email>
+            <organization>The Apache Software Foundation</organization>
+        </developer>
+        <developer>
+            <name>James Strachan</name>
+            <id>jstrachan</id>
+            <email>jstrachan AT apache DOT org</email>
+            <organization>SpiritSoft, Inc.</organization>
+        </developer>
+        <developer>
+            <name>Rahul Akolkar</name>
+            <id>rahul</id>
+            <email>rahul AT apache DOT org</email>
+            <organization>The Apache Software Foundation</organization>
+        </developer>
+        <developer>
+            <name>Sebastian Bazley</name>
+            <id>sebb</id>
+            <email>sebb AT apache DOT org</email>
+        </developer>
+        <developer>
+            <name>Henri Biestro</name>
+            <id>henrib</id>
+            <email>henrib AT apache DOT org</email>
+        </developer>
+    </developers>
+    
+    <contributors>
+        <contributor>
+            <name>Dmitri Blinov</name>
+        </contributor>
+    </contributors>
 </project>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index ca25585..d9778fc 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -103,7 +103,7 @@ public class JexlArithmetic {
         this.mathContext = bigdContext == null ? MathContext.DECIMAL128 : bigdContext;
         this.mathScale = bigdScale == Integer.MIN_VALUE ? BIGD_SCALE : bigdScale;
     }
-
+    
     /**
      * Apply options to this arithmetic which eventually may create another instance.
      * @see #createWithOptions(boolean, java.math.MathContext, int)
@@ -111,39 +111,73 @@ public class JexlArithmetic {
      * @param options the {@link JexlEngine.Options} to use
      * @return an arithmetic with those options set
      */
-    public JexlArithmetic options(JexlEngine.Options options) {
-        Boolean ostrict = options.isStrictArithmetic();
-        if (ostrict == null) {
-            ostrict = isStrict();
-        }
-        MathContext bigdContext = options.getArithmeticMathContext();
-        if (bigdContext == null) {
-            bigdContext = getMathContext();
-        }
-        int bigdScale = options.getArithmeticMathScale();
-        if (bigdScale == Integer.MIN_VALUE) {
-            bigdScale = getMathScale();
+    public JexlArithmetic options(JexlOptions options) {
+        if (options != null) {
+            boolean ostrict = options.isStrictArithmetic();
+            MathContext bigdContext = options.getMathContext();
+            if (bigdContext == null) {
+                bigdContext = getMathContext();
+            }
+            int bigdScale = options.getMathScale();
+            if (bigdScale == Integer.MIN_VALUE) {
+                bigdScale = getMathScale();
+            }
+            if (ostrict != isStrict()
+                || bigdScale != getMathScale()
+                || bigdContext != getMathContext()) {
+                return createWithOptions(ostrict, bigdContext, bigdScale);
+            }
         }
-        if (ostrict != isStrict()
-            || bigdScale != getMathScale()
-            || bigdContext != getMathContext()) {
-            return createWithOptions(ostrict, bigdContext, bigdScale);
+        return this;
+    }
+    
+    /**
+     * Apply options to this arithmetic which eventually may create another instance.
+     * @see #createWithOptions(boolean, java.math.MathContext, int)
+     *
+     * @param options the {@link JexlEngine.Options} to use
+     * @return an arithmetic with those options set
+     * @deprecated
+     */
+    public JexlArithmetic options(JexlEngine.Options options) {
+        if (options != null) {
+            Boolean ostrict = options.isStrictArithmetic();
+            if (ostrict == null) {
+                ostrict = isStrict();
+            }
+            MathContext bigdContext = options.getArithmeticMathContext();
+            if (bigdContext == null) {
+                bigdContext = getMathContext();
+            }
+            int bigdScale = options.getArithmeticMathScale();
+            if (bigdScale == Integer.MIN_VALUE) {
+                bigdScale = getMathScale();
+            }
+            if (ostrict != isStrict()
+                || bigdScale != getMathScale()
+                || bigdContext != getMathContext()) {
+                return createWithOptions(ostrict, bigdContext, bigdScale);
+            }
         }
         return this;
     }
-
+        
     /**
      * Apply options to this arithmetic which eventually may create another instance.
      * @see #createWithOptions(boolean, java.math.MathContext, int)
      *
-     * @param context the context that may extend {@link JexlEngine.Options} to use
+     * @param context the context that may extend {@link JexlContext.OptionsHandle} to use
      * @return a new arithmetic instance or this
      * @since 3.1
      */
     public JexlArithmetic options(JexlContext context) {
-        return context instanceof JexlEngine.Options
-               ? options((JexlEngine.Options) context)
-               : this;
+        if (context instanceof JexlContext.OptionsHandle) {
+            return options(((JexlContext.OptionsHandle) context).getEngineOptions());
+        }
+        if (context instanceof JexlEngine.Options) {
+            return options((JexlEngine.Options) context);
+        }
+        return this;
     }
 
     /**
@@ -802,7 +836,10 @@ public class JexlArithmetic {
      * @return the negated value
      */
     public Object negate(Object val) {
-        if (val instanceof Integer) {
+        if (val == null) {
+            controlNullOperand();
+            return null;
+        } else if (val instanceof Integer) {
             return -((Integer) val);
         } else if (val instanceof Double) {
             return - ((Double) val);
@@ -847,6 +884,10 @@ public class JexlArithmetic {
      * @return the positive value
      */
     public Object positivize(Object val) {
+        if (val == null) {
+            controlNullOperand();
+            return null;
+        } 
         if (val instanceof Short) {
             return ((Short) val).intValue();
         }
diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index ad69d67..a02bfb2 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -18,6 +18,7 @@
 package org.apache.commons.jexl3;
 
 import org.apache.commons.jexl3.internal.Engine;
+import org.apache.commons.jexl3.internal.Options;
 import org.apache.commons.jexl3.introspection.JexlSandbox;
 import org.apache.commons.jexl3.introspection.JexlUberspect;
 import org.apache.commons.logging.Log;
@@ -90,6 +91,9 @@ public class JexlBuilder {
 
     /** Whether interrupt throws JexlException.Cancel. */
     private Boolean cancellable = null;
+    
+    /** The options. */
+    private final Options options = new Options();
 
     /** Whether getVariables considers all potential equivalent syntactic forms. */
     private Boolean collectAll = null;
@@ -151,6 +155,11 @@ public class JexlBuilder {
         return this.strategy;
     }
 
+    /** @return the current set of options */
+    public JexlOptions options() {
+        return options;
+    }
+
     /**
      * Sets the JexlArithmetic instance the engine will use.
      *
@@ -159,6 +168,9 @@ public class JexlBuilder {
      */
     public JexlBuilder arithmetic(JexlArithmetic a) {
         this.arithmetic = a;
+        options.setStrictArithmetic(a.isStrict());
+        options.setMathContext(a.getMathContext());
+        options.setMathScale(a.getMathScale());
         return this;
     }
 
@@ -203,11 +215,11 @@ public class JexlBuilder {
     /**
      * Sets the o.a.c.Log instance to use.
      *
-     * @param l the logger
+     * @param log the logger
      * @return this builder
      */
-    public JexlBuilder logger(Log l) {
-        this.logger = l;
+    public JexlBuilder logger(Log log) {
+        this.logger = log;
         return this;
     }
 
@@ -260,7 +272,39 @@ public class JexlBuilder {
     public Charset charset() {
         return charset;
     }
-
+    
+   /**
+     * Sets whether the engine will resolve antish variable names.
+     *
+     * @param flag true means antish resolution is enabled, false disables it
+     * @return this builder
+     */
+    public JexlBuilder antish(boolean flag) {
+        options.setAntish(flag);
+        return this;
+    }
+    
+    /** @return whether antish resolution is enabled */
+    public boolean antish() {
+        return options.isAntish();
+    }
+       
+    /**
+     * Sets whether the engine is in lexical mode.
+     *
+     * @param flag true means lexical function scope is in effect, false implies non-lexical scoping 
+     * @return this builder
+     */
+    public JexlBuilder lexical(boolean flag) {
+        options.setLexical(true);
+        return this;
+    }
+    
+    /** @return whether lexical scope is enabled */
+    public boolean lexical() {
+        return options.isLexical();
+    }
+    
     /**
      * Sets whether the engine will throw JexlException during evaluation when an error is triggered.
      *
@@ -269,6 +313,7 @@ public class JexlBuilder {
      */
     public JexlBuilder silent(boolean flag) {
         this.silent = flag;
+        options.setSilent(flag);
         return this;
     }
 
@@ -286,6 +331,7 @@ public class JexlBuilder {
      */
     public JexlBuilder strict(boolean flag) {
         this.strict = flag;
+        options.setStrict(flag);
         return this;
     }
 
@@ -305,6 +351,7 @@ public class JexlBuilder {
      */
     public JexlBuilder safe(boolean flag) {
         this.safe = flag;
+        options.setSafe(flag);
         return this;
     }
 
@@ -339,6 +386,7 @@ public class JexlBuilder {
      */
     public JexlBuilder cancellable(boolean flag) {
         this.cancellable = flag;
+        options.setCancellable(flag);
         return this;
     }
 
diff --git a/src/main/java/org/apache/commons/jexl3/JexlContext.java b/src/main/java/org/apache/commons/jexl3/JexlContext.java
index 0ab9194..fdc85a3 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlContext.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlContext.java
@@ -121,7 +121,7 @@ public interface JexlContext {
     }
 
     /**
-     * A marker interface of the JexlContext that allows to process annotations.
+     * A marker interface of the JexlContext that processes annotations.
      * It is used by the interpreter during evaluation to execute annotation evaluations.
      * <p>If the JexlContext is not an instance of an AnnotationProcessor, encountering an annotation will generate
      * an error or a warning depending on the engine strictness.
@@ -143,5 +143,20 @@ public interface JexlContext {
          */
         Object processAnnotation(String name, Object[] args, Callable<Object> statement) throws Exception;
     }
-
+    
+    /**
+     * A marker interface of the JexlContext that exposes runtime evaluation options.
+     */
+    interface OptionsHandle {
+        /**
+         * Retrieves the current set of options though the context.
+         * <p>
+         * This method will be called once at beginning of evaluation and the
+         * JexlOptions instance kept as a property of the evaluator;
+         * the JexlOptions instance is free to alter its boolean flags during
+         * execution.
+         * @return the engine options
+         */
+        JexlOptions getEngineOptions();
+    }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/JexlEngine.java b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
index 2b371b8..bc41a82 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
@@ -107,6 +107,7 @@ public abstract class JexlEngine {
     /**
      * Script evaluation options.
      * <p>The JexlContext used for evaluation can implement this interface to alter behavior.</p>
+     * @deprecated 3.2
      */
     public interface Options {
 
@@ -159,7 +160,7 @@ public abstract class JexlEngine {
          */
         int getArithmeticMathScale();
     }
-
+   
     /** Default features. */
     public static final JexlFeatures DEFAULT_FEATURES = new JexlFeatures();
 
@@ -257,6 +258,13 @@ 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 cccccbe..815adba 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlException.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlException.java
@@ -467,6 +467,32 @@ public class JexlException extends RuntimeException {
             return parserError(JexlFeatures.stringify(code), getDetail());
         }
     }
+    
+    /**
+     * The various type of variable issues.
+     */
+    public static enum VariableIssue {
+        /** The variable is undefined. */
+        UNDEFINED,
+        /** The variable is already declared. */
+        REDEFINED,
+        /** The variable has a null value. */
+        NULLVALUE;
+        
+        /**
+         * Stringifies the variable issue.
+         * @param var the variable name
+         * @return the issue message
+         */
+        public String message(String var) {
+            switch(this) {
+                case NULLVALUE : return "variable '" + var + "' is null";
+                case REDEFINED : return "variable '" + var + "' is already defined";
+                case UNDEFINED :
+                default: return "variable '" + var + "' is undefined";
+            }
+        }
+    }
 
     /**
      * Thrown when a variable is unknown.
@@ -477,7 +503,20 @@ public class JexlException extends RuntimeException {
         /**
          * Undefined variable flag.
          */
-        private final boolean undefined;
+        private final VariableIssue issue; 
+        
+        /**
+         * Creates a new Variable exception instance.
+         *
+         * @param node the offending ASTnode
+         * @param var  the unknown variable
+         * @param vi   the variable issue
+         */
+        public Variable(JexlNode node, String var, VariableIssue vi) {
+            super(node, var, null);
+            issue = vi;
+        }
+        
         /**
          * Creates a new Variable exception instance.
          *
@@ -486,8 +525,7 @@ public class JexlException extends RuntimeException {
          * @param undef whether the variable is undefined or evaluated as null
          */
         public Variable(JexlNode node, String var, boolean undef) {
-            super(node, var, null);
-            undefined = undef;
+            this(node, var,  undef ? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
         }
 
         /**
@@ -496,7 +534,7 @@ public class JexlException extends RuntimeException {
          * @return true if undefined, false otherwise
          */
         public boolean isUndefined() {
-            return undefined;
+            return issue == VariableIssue.UNDEFINED;
         }
 
         /**
@@ -508,7 +546,7 @@ public class JexlException extends RuntimeException {
 
         @Override
         protected String detailedMessage() {
-            return (undefined? "undefined" : "null value") + " variable " + getVariable();
+            return issue.message(getVariable());
         }
     }
 
@@ -520,15 +558,22 @@ public class JexlException extends RuntimeException {
      * @param undef whether the variable is null or undefined
      * @return the error message
      */
+    @Deprecated
     public static String variableError(JexlNode node, String variable, boolean undef) {
+        return variableError(node, variable, undef? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
+    }
+       
+    /**
+     * Generates a message for a variable error.
+     *
+     * @param node the node where the error occurred
+     * @param variable the variable
+     * @param issue  the variable kind of issue
+     * @return the error message
+     */
+    public static String variableError(JexlNode node, String variable, VariableIssue issue) {
         StringBuilder msg = errorAt(node);
-        if (undef) {
-            msg.append("undefined");
-        } else {
-            msg.append("null value");
-        }
-        msg.append(" variable ");
-        msg.append(variable);
+        msg.append(issue.message(variable));
         return msg.toString();
     }
 
@@ -598,7 +643,7 @@ public class JexlException extends RuntimeException {
 
         @Override
         protected String detailedMessage() {
-            return (undefined? "undefined" : "null value") + " property " + getProperty();
+            return (undefined? "undefined" : "null value") + " property '" + getProperty() + "'";
         }
     }
 
@@ -617,8 +662,9 @@ public class JexlException extends RuntimeException {
         } else {
             msg.append("null value");
         }
-        msg.append(" property ");
+        msg.append(" property '");
         msg.append(pty);
+        msg.append('\'');
         return msg.toString();
     }
     
diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index 8e1a6cf..12d1e6e 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -29,6 +29,7 @@ import java.util.TreeSet;
  * <li>Registers: register syntax (#number), used internally for {g,s}etProperty
  * <li>Reserved Names: a set of reserved variable names that can not be used as local variable (or parameter) names
  * <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)
+ * <li>Lexical: lexical scope, prevents redefining local variables 
  * <li>Side Effect : assigning/modifying values on any variables or left-value
  * <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable.
  * <li>New Instance: creating an instance using new(...)
@@ -51,7 +52,7 @@ public final class JexlFeatures {
     private static final String[] F_NAMES = {
         "register", "reserved variable", "local variable", "assign/modify",
         "global assign/modify", "array reference", "create instance", "loop", "function",
-        "method call", "set/map/array literal", "pragma", "annotation", "script"
+        "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical"
     };
     /** Registers feature ordinal. */
     private static final int REGISTER = 0;
@@ -81,6 +82,8 @@ public final class JexlFeatures {
     public static final int ANNOTATION = 12;
     /** Script feature ordinal. */
     public static final int SCRIPT = 13;
+    /** Script feature ordinal. */
+    public static final int LEXICAL = 14;
 
     /**
      * Creates an all-features-enabled instance.
@@ -471,4 +474,20 @@ public final class JexlFeatures {
         return !getFeature(SCRIPT);
     }
 
+    /**
+     * Sets whether syntactic lexical mode is enabled.
+     *
+     * @param flag true means syntactic lexical function scope is in effect, false implies non-lexical scoping 
+     * @return this features instance
+     */
+    public JexlFeatures lexical(boolean flag) {
+        setFeature(LEXICAL, flag);
+        return this;
+    }
+    
+    
+    /** @return whether lexical scope feature is enabled */
+    public boolean isLexical() {
+        return getFeature(LEXICAL);
+    }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
new file mode 100644
index 0000000..4d1ba6e
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl3;
+
+import java.math.MathContext;
+
+/**
+ * Flags and properties that can alter the evaluation behavior.
+ * The flags, briefly explained, are the following:
+ * <ul>
+ * <li>silent: whether errors throw exception</li>
+ * <li>safe: whether navigation through null is an error</li>
+ * <li>cancellable: whether thread interruption is an error</li>
+ * <li>lexical: whether redefining local variables is an error</li>
+ * <li>lexicalShade: whether local variables shade global ones even outside their scope</li>
+ * <li>strict: whether unknown or unsolvable identifiers are errors</li>
+ * <li>strictArithmetic: whether null as operand is an error</li>
+ * </ul>
+ * The sensible default is cancellable, strict and strictArithmetic.
+ * <p>This interface replaces the now deprecated JexlEngine.Options.
+ * @since 3.2
+ */
+public interface JexlOptions {
+    /**
+     * The MathContext instance used for +,-,/,*,% operations on big decimals.
+     * @return the math context
+     */
+    MathContext getMathContext();
+
+    /**
+     * The BigDecimal scale used for comparison and coercion operations.
+     * @return the scale
+     */
+    int getMathScale();
+
+    /**
+     * Checks whether evaluation will attempt resolving antish variable names.
+     * @return true if antish variables are solved, false otherwise
+     */
+    boolean isAntish();
+    
+    /**
+     * Checks whether evaluation will throw JexlException.Cancel (true) or
+     * return null (false) if interrupted.
+     * @return true when cancellable, false otherwise
+     */
+    boolean isCancellable();
+
+    /**
+     * Checks whether runtime variable scope is lexical.
+     * <p>If true, lexical scope applies to local variables and parameters.
+     * Redefining a variable in the same lexical unit will generate errors.
+     * @return true if scope is lexical, false otherwise
+     */
+    boolean isLexical();
+    
+    /**
+     * Checks whether local variables shade global ones.
+     * <p>After a symbol is defined as local, dereferencing it outside its
+     * scope will trigger an error instead of seeking a global variable of the
+     * same name. To further reduce potential naming ambiguity errors, 
+     * global variables (ie non local) must be declared to be assigned (@link JexlContext#has(String) )
+     * when this flag is on; attempting to set an undeclared global variables will
+     * raise an error.
+     * @return true if lexical shading is applied, false otherwise
+     */
+    boolean isLexicalShade();
+    
+    /**
+     * Checks whether the engine considers null in navigation expression as
+     * errors during evaluation..
+     * @return true if safe, false otherwise
+     */
+    boolean isSafe();
+
+    /**
+     * Checks whether the engine will throw a {@link JexlException} when an
+     * error is encountered during evaluation.
+     * @return true if silent, false otherwise
+     */
+    boolean isSilent();
+
+    /**
+     * Checks whether the engine considers unknown variables, methods and
+     * constructors as errors during evaluation.
+     * @return true if strict, false otherwise
+     */
+    boolean isStrict();
+
+    /**
+     * Checks whether the arithmetic triggers errors during evaluation when null
+     * is used as an operand.
+     * @return true if strict, false otherwise
+     */
+    boolean isStrictArithmetic();
+    
+    /**
+     * Sets whether the engine will attempt solving antish variable names from 
+     * context.
+     * @param flag true if antish variables are solved, false otherwise
+     */
+    void setAntish(boolean flag);
+
+    /**
+     * Sets whether the engine will throw JexlException.Cancel (true) or return
+     * null (false) when interrupted during evaluation.
+     * @param flag true when cancellable, false otherwise
+     */
+    void setCancellable(boolean flag);
+    
+    /**
+     * Sets whether the engine uses a strict block lexical scope during
+     * evaluation.
+     * @param flag true if lexical scope is used, false otherwise
+     */
+    void setLexical(boolean flag);
+    
+    /**
+     * Sets whether the engine strictly shades global variables.
+     * Local symbols shade globals after definition and creating global
+     * variables is prohibited evaluation.
+     * @param flag true if creation is allowed, false otherwise
+     */
+    void setLexicalShade(boolean flag);
+
+    /**
+     * Sets the arithmetic math context.
+     * @param mcontext the context
+     */
+    void setMathContext(MathContext mcontext);
+
+    /**
+     * Sets the arithmetic math scale.
+     * @param mscale the scale
+     */
+    void setMathScale(int mscale);
+
+    /**
+     * Set options from engine.
+     * @param jexl the engine
+     */
+    void setOptions(JexlEngine jexl);
+
+    /**
+     * Sets whether the engine considers null in navigation expression as errors
+     * during evaluation.
+     * @param flag true if safe, false otherwise
+     */
+    void setSafe(boolean flag);
+
+    /**
+     * Sets whether the engine will throw a {@link JexlException} when an error
+     * is encountered during evaluation.
+     * @param flag true if silent, false otherwise
+     */
+    void setSilent(boolean flag);
+
+    /**
+     * Sets whether the engine considers unknown variables, methods and
+     * constructors as errors during evaluation.
+     * @param flag true if strict, false otherwise
+     */
+    void setStrict(boolean flag);
+
+    /**
+     * Sets the strict arithmetic flag.
+     * @param stricta true or false
+     */
+    void setStrictArithmetic(boolean stricta);
+    
+    /**
+     * Creates a copy of this instance.
+     * @return a copy
+     */
+    JexlOptions copy();
+    
+}
\ No newline at end of file
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 a346fa7..839b1a0 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Closure.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Closure.java
@@ -18,15 +18,13 @@ package org.apache.commons.jexl3.internal;
 
 import org.apache.commons.jexl3.JexlContext;
 import org.apache.commons.jexl3.parser.ASTJexlLambda;
-import org.apache.commons.jexl3.parser.JexlNode;
-
 
 /**
  * A Script closure.
  */
 public class Closure extends Script {
     /** The frame. */
-    protected final Scope.Frame frame;
+    protected final Frame frame;
 
     /**
      * Creates a closure.
@@ -45,7 +43,7 @@ public class Closure extends Script {
      */
     protected Closure(Script base, Object[] args) {
         super(base.jexl, base.source, base.script);
-        Scope.Frame sf = (base instanceof Closure) ? ((Closure) base).frame :  null;
+        Frame sf = (base instanceof Closure) ? ((Closure) base).frame :  null;
         frame = sf == null
                 ? script.createFrame(args)
                 : sf.assign(args);
@@ -120,26 +118,18 @@ public class Closure extends Script {
 
     @Override
     public Object execute(JexlContext context, Object... args) {
-        Scope.Frame callFrame = null;
-        if (frame != null) {
-            callFrame = frame.assign(args);
-        }
-        Interpreter interpreter = createInterpreter(context, callFrame);
-        JexlNode block = script.jjtGetChild(script.jjtGetNumChildren() - 1);
-        return interpreter.interpret(block);
+        Frame local = frame != null? frame.assign(args) : null;
+        Interpreter interpreter = createInterpreter(context, local);
+        return interpreter.runClosure(this, null);
     }
 
     @Override
     public Callable callable(JexlContext context, Object... args) {
-        Scope.Frame local = null;
-        if (frame != null) {
-            local = frame.assign(args);
-        }
+        Frame local = frame != null? frame.assign(args) : null;
         return new Callable(createInterpreter(context, local)) {
             @Override
             public Object interpret() {
-                JexlNode block = script.jjtGetChild(script.jjtGetNumChildren() - 1);
-                return interpreter.interpret(block);
+                return interpreter.runClosure(Closure.this, null);
             }
         };
     }
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 b8c77a8..3205cef 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -23,6 +23,7 @@ import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
 import org.apache.commons.jexl3.JexlFeatures;
 import org.apache.commons.jexl3.JexlInfo;
+import org.apache.commons.jexl3.JexlOptions;
 import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.internal.introspection.SandboxUberspect;
 import org.apache.commons.jexl3.internal.introspection.Uberspect;
@@ -48,7 +49,6 @@ import java.util.Set;
 
 import java.nio.charset.Charset;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -117,14 +117,6 @@ public class Engine extends JexlEngine {
      */
     protected final boolean debug;
     /**
-     * The atomic parsing flag; true whilst parsing.
-     */
-    protected final AtomicBoolean parsing = new AtomicBoolean(false);
-    /**
-     * The default charset.
-     */
-    protected final Charset charset;
-    /**
      * The set of default script parsing features.
      */
     protected final JexlFeatures scriptFeatures;
@@ -133,6 +125,14 @@ public class Engine extends JexlEngine {
      */
     protected final JexlFeatures expressionFeatures;
     /**
+     * The default charset.
+     */
+    protected final Charset charset;
+    /**
+     * The atomic parsing flag; true whilst parsing.
+     */
+    protected final AtomicBoolean parsing = new AtomicBoolean(false);
+    /**
      * The {@link Parser}; when parsing expressions, this engine uses the parser if it
      * is not already in use otherwise it will create a new temporary one.
      */
@@ -153,6 +153,10 @@ public class Engine extends JexlEngine {
      * Collect all or only dot references.
      */
     protected final boolean collectAll;
+    /**
+     * A cached version of the options.
+     */
+    protected final JexlOptions options =  new org.apache.commons.jexl3.internal.Options();
 
     /**
      * Creates an engine with default arguments.
@@ -200,17 +204,9 @@ public class Engine extends JexlEngine {
         if (uberspect == null) {
             throw new IllegalArgumentException("uberspect can not be null");
         }
+        options.setOptions(this);
     }
 
-    /**
-     * Solves an optional option.
-     * @param conf the option as configured, may be null
-     * @param def the default value if null
-     * @return true or false
-     */
-    private boolean option(Boolean conf, boolean def) {
-        return conf == null? def : conf;
-    }
 
     /**
      * Gets the default instance of Uberspect.
@@ -252,10 +248,15 @@ public class Engine extends JexlEngine {
 
     @Override
     public boolean isStrict() {
-        return strict;
+        return this.strict;
     }
 
     @Override
+    public boolean isSafe() {
+        return this.safe;
+    }
+    
+    @Override
     public boolean isCancellable() {
         return this.cancellable;
     }
@@ -290,7 +291,47 @@ public class Engine extends JexlEngine {
     public Charset getCharset() {
         return charset;
     }
+ 
+    /**
+     * Extracts the engine evaluation options from context.
+     * @param context the context
+     * @return the options if any
+     */
+    JexlOptions options(JexlContext context) {
+        JexlOptions jexlo = null;
+        if (context instanceof JexlContext.OptionsHandle) {
+            jexlo = ((JexlContext.OptionsHandle) context).getEngineOptions();
+        }
+        if (jexlo == null) {
+            jexlo = options;
+            /** The following block for compatibility between 3.1 and 3.2*/
+            if (context instanceof JexlEngine.Options) {
+                jexlo = jexlo.copy();
+                JexlEngine jexl = this;
+                JexlEngine.Options opts = (JexlEngine.Options) context;
+                jexlo.setCancellable(option(opts.isCancellable(), jexl.isCancellable()));
+                jexlo.setSilent(option(opts.isSilent(), jexl.isSilent()));
+                jexlo.setStrict(option(opts.isStrict(), jexl.isStrict()));
+                JexlArithmetic jexla = jexl.getArithmetic();
+                jexlo.setStrictArithmetic(option(opts.isStrictArithmetic(), jexla.isStrict()));
+                jexlo.setMathContext(opts.getArithmeticMathContext());
+                jexlo.setMathScale(opts.getArithmeticMathScale());
+            }
+        }
+        return jexlo;
+    }
 
+    /**
+     * Solves an optional option.
+     * @param conf the option as configured, may be null
+     * @param def the default value if null, shall not be null
+     * @param <T> the option type
+     * @return conf or def
+     */
+    private static <T> T option(T conf, T def) {
+        return conf == null? def : conf;
+    }
+    
     @Override
     public TemplateEngine createJxltEngine(boolean noScript, int cacheSize, char immediate, char deferred) {
         return new TemplateEngine(this, noScript, cacheSize, immediate, deferred);
@@ -309,7 +350,7 @@ public class Engine extends JexlEngine {
      * @param frame   the interpreter frame
      * @return an Interpreter
      */
-    protected Interpreter createInterpreter(JexlContext context, Scope.Frame frame) {
+    protected Interpreter createInterpreter(JexlContext context, Frame frame) {
         return new Interpreter(this, context, frame);
     }
 
@@ -359,7 +400,7 @@ public class Engine extends JexlEngine {
             final Scope scope = new Scope(null, "#0");
             final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope);
             final JexlNode node = script.jjtGetChild(0);
-            final Scope.Frame frame = script.createFrame(bean);
+            final Frame frame = script.createFrame(bean);
             final Interpreter interpreter = createInterpreter(context, frame);
             return node.jjtAccept(interpreter, null);
         } catch (JexlException xjexl) {
@@ -388,7 +429,7 @@ public class Engine extends JexlEngine {
             final Scope scope = new Scope(null, "#0", "#1");
             final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope);
             final JexlNode node = script.jjtGetChild(0);
-            final Scope.Frame frame = script.createFrame(bean, value);
+            final Frame frame = script.createFrame(bean, value);
             final Interpreter interpreter = createInterpreter(context, frame);
             node.jjtAccept(interpreter, null);
         } catch (JexlException xjexl) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Frame.java b/src/main/java/org/apache/commons/jexl3/internal/Frame.java
new file mode 100644
index 0000000..8de92e6
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/Frame.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.internal;
+
+import java.util.Arrays;
+
+/**
+ * A call frame, created from a scope, stores the arguments and local variables in a "stack frame" (sic).
+ * @since 3.0
+ */
+public final class Frame {
+    /** The scope. */
+    private final Scope scope;
+    /** The actual stack frame. */
+    private final Object[] stack;
+    /** Number of curried parameters. */
+    private int curried = 0;
+
+    /**
+     * Creates a new frame.
+     * @param s the scope
+     * @param r the stack frame
+     * @param c the number of curried parameters
+     */
+    public Frame(Scope s, Object[] r, int c) {
+        scope = s;
+        stack = r;
+        curried = c;
+    }
+
+    /**
+     * Gets this script unbound parameters, i.e. parameters not bound through curry().
+     * @return the parameter names
+     */
+    public String[] getUnboundParameters() {
+        return scope.getParameters(curried);
+    }
+
+    /**
+     * Gets the scope.
+     * @return this frame scope
+     */
+    public Scope getScope() {
+        return scope;
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.deepHashCode(this.stack);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final Frame other = (Frame) obj;
+        return Arrays.deepEquals(this.stack, other.stack);
+    }
+
+    /**
+     * Gets a value.
+     * @param s the offset in this frame
+     * @return the stacked value
+     */
+    Object get(int s) {
+        return stack[s];
+    }
+
+    /**
+     * Whether this frame defines a symbol, ie declared it and assigned it a value.
+     * @param s the offset in this frame
+     * @return true if this symbol has been assigned a value, false otherwise
+     */
+    boolean has(int s) {
+        return s >= 0 && s < stack.length && stack[s] != Scope.UNDEFINED;
+    }
+
+    /**
+     * Sets a value.
+     * @param r the offset in this frame
+     * @param value the value to set in this frame
+     */
+    void set(int r, Object value) {
+        stack[r] = value;
+    }
+
+    /**
+     * Assign values to this frame.
+     * @param values the values
+     * @return this frame
+     */
+    Frame assign(Object... values) {
+        if (stack != null) {
+            int nparm = scope.getArgCount();
+            Object[] copy = stack.clone();
+            int ncopy = 0;
+            if (values != null && values.length > 0) {
+                ncopy = Math.min(nparm - curried, Math.min(nparm, values.length));
+                System.arraycopy(values, 0, copy, curried, ncopy);
+            }
+            // unbound parameters are defined as null
+            Arrays.fill(copy, curried + ncopy, nparm, null);
+            return new Frame(scope, copy, curried + ncopy);
+        }
+        return this;
+    }
+    
+}
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 9ef7b4a..86eeb75 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -117,8 +117,10 @@ public class Interpreter extends InterpreterBase {
     /** Frame height. */
     protected int fp = 0;
     /** Symbol values. */
-    protected final Scope.Frame frame;
-
+    protected final Frame frame;
+    /** Block micro-frames. */
+    protected LexicalScope block = null;
+    
     /**
      * The thread local interpreter.
      */
@@ -131,7 +133,7 @@ public class Interpreter extends InterpreterBase {
      * @param aContext the context to evaluate expression
      * @param eFrame   the interpreter evaluation frame
      */
-    protected Interpreter(Engine engine, JexlContext aContext, Scope.Frame eFrame) {
+    protected Interpreter(Engine engine, JexlContext aContext, Frame eFrame) {
         super(engine, aContext);
         this.frame = eFrame;
     }
@@ -156,7 +158,7 @@ public class Interpreter extends InterpreterBase {
         INTER.set(inter);
         return pinter;
     }
-
+  
     /**
      * Interpret the given script/expression.
      * <p>
@@ -601,7 +603,44 @@ public class Interpreter extends InterpreterBase {
     }
 
     @Override
+    protected Object visit(ASTVar node, Object data) {
+        int symbol = node.getSymbol();
+        // if we have a var, we have a scope thus a frame
+        Object value;
+        if (frame.has(symbol)) {
+            value = frame.get(symbol);
+        } else {
+            frame.set(symbol, null);
+            value = null;
+        }
+        if (options.isLexical() && !block.declareSymbol(symbol)) {
+            return redefinedVariable(node, node.getName());
+        }
+        return value;
+    }
+
+    @Override
     protected Object visit(ASTBlock node, Object data) {
+        int cnt = node.getSymbolCount();
+        if (!options.isLexical() || cnt <= 0) {
+            return visitBlock(node, data);
+        }
+        LexicalScope lexical = block;
+        try {
+            block = new LexicalScope(lexical);
+            return visitBlock(node, data);
+        } finally {
+            block = lexical;
+        }
+    }
+    
+    /**
+     * Base visitation for blocks.
+     * @param node the block
+     * @param data the usual data
+     * @return the result of the last expression evaluation
+     */
+    private Object visitBlock(ASTBlock node, Object data) {
         int numChildren = node.jjtGetNumChildren();
         Object result = null;
         for (int i = 0; i < numChildren; i++) {
@@ -634,27 +673,36 @@ public class Interpreter extends InterpreterBase {
         /* first objectNode is the loop variable */
         ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
         ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
-        int symbol = loopVariable.getSymbol();
-        /* second objectNode is the variable to iterate */
-        Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
-        // make sure there is a value to iterate upon
-        if (iterableValue != null) {
-            /* third objectNode is the statement to execute */
-            JexlNode statement = node.jjtGetNumChildren() >= 3? node.jjtGetChild(2) : null;
-            // get an iterator for the collection/array etc via the introspector.
-            Object forEach = null;
-            try {
+        final int symbol = loopVariable.getSymbol();
+        final LexicalScope lexical = block;
+        if (options.isLexical()) {
+            // the iteration variable can not be declared in parent block 
+            if (symbol >= 0 && block.hasSymbol(symbol)) {
+                return redefinedVariable(node, loopVariable.getName());
+            }
+            // create lexical frame
+            block = new LexicalScope(lexical);
+        }
+        Object forEach = null;
+        try {
+            /* second objectNode is the variable to iterate */
+            Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
+            // make sure there is a value to iterate upon
+            if (iterableValue != null) {
+                /* third objectNode is the statement to execute */
+                JexlNode statement = node.jjtGetNumChildren() >= 3 ? node.jjtGetChild(2) : null;
+                // get an iterator for the collection/array etc via the introspector.
                 forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, iterableValue);
                 Iterator<?> itemsIterator = forEach instanceof Iterator
-                                            ? (Iterator<?>) forEach
-                                            : uberspect.getIterator(iterableValue);
+                        ? (Iterator<?>) forEach
+                        : uberspect.getIterator(iterableValue);
                 if (itemsIterator != null) {
                     while (itemsIterator.hasNext()) {
                         cancelCheck(node);
                         // set loopVariable to value of iterator
                         Object value = itemsIterator.next();
                         if (symbol < 0) {
-                            context.set(loopVariable.getName(), value);
+                            setContextVariable(node, loopVariable.getName(), value);
                         } else {
                             frame.set(symbol, value);
                         }
@@ -670,9 +718,13 @@ public class Interpreter extends InterpreterBase {
                         }
                     }
                 }
-            } finally {
-                //  closeable iterator handling
-                closeIfSupported(forEach);
+            }
+        } finally {
+            //  closeable iterator handling
+            closeIfSupported(forEach);
+            // restore lexical frame
+            if (lexical != null && block != null) {
+                block = lexical;
             }
         }
         return result;
@@ -864,7 +916,15 @@ public class Interpreter extends InterpreterBase {
 
     @Override
     protected Object visit(ASTTernaryNode node, Object data) {
-        Object condition = node.jjtGetChild(0).jjtAccept(this, data);
+        Object condition;
+        try {
+            condition = node.jjtGetChild(0).jjtAccept(this, data);
+        } catch(JexlException xany) {
+            if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) {
+                throw xany;
+            }
+            condition = null;
+        }
         // ternary as in "x ? y : z"
         if (node.jjtGetNumChildren() == 3) {
             if (condition != null && arithmetic.toBoolean(condition)) {
@@ -883,7 +943,15 @@ public class Interpreter extends InterpreterBase {
 
     @Override
     protected Object visit(ASTNullpNode node, Object data) {
-        Object lhs = node.jjtGetChild(0).jjtAccept(this, data);
+        Object lhs;
+        try {
+            lhs = node.jjtGetChild(0).jjtAccept(this, data);
+        } catch(JexlException xany) {
+            if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) {
+                throw xany;
+            }
+            lhs = null;
+        }
         // null elision as in "x ?? z"
         return lhs != null? lhs : node.jjtGetChild(1).jjtAccept(this, data);
     }
@@ -908,19 +976,43 @@ public class Interpreter extends InterpreterBase {
         }
     }
 
+    /**
+     * Runs a closure.
+     * @param closure the closure
+     * @param data the usual data
+     * @return the closure return value
+     */
+    protected Object runClosure(Closure closure, Object data) {
+        ASTJexlScript script = closure.getScript();
+        final LexicalScope lexical = block;
+        block = new LexicalScope(frame, null);
+        try {
+            JexlNode body = script.jjtGetChild(script.jjtGetNumChildren() - 1);
+            return interpret(body);
+        } finally {
+            block = lexical;
+        }
+    }
+
     @Override
-    protected Object visit(ASTJexlScript node, Object data) {
-        if (node instanceof ASTJexlLambda && !((ASTJexlLambda) node).isTopLevel()) {
-            return new Closure(this, (ASTJexlLambda) node);
+    protected Object visit(ASTJexlScript script, Object data) {
+        if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
+            return new Closure(this, (ASTJexlLambda) script);
         } else {
-            final int numChildren = node.jjtGetNumChildren();
-            Object result = null;
-            for (int i = 0; i < numChildren; i++) {
-                JexlNode child = node.jjtGetChild(i);
-                result = child.jjtAccept(this, data);
-                cancelCheck(child);
+            final LexicalScope lexical = block;
+            block = new LexicalScope(frame, null);
+            try {
+                final int numChildren = script.jjtGetNumChildren();
+                Object result = null;
+                for (int i = 0; i < numChildren; i++) {
+                    JexlNode child = script.jjtGetChild(i);
+                    result = child.jjtAccept(this, data);
+                    cancelCheck(child);
+                }
+                return result;
+            } finally {
+                block = lexical;
             }
-            return result;
         }
     }
 
@@ -930,39 +1022,11 @@ public class Interpreter extends InterpreterBase {
     }
 
     @Override
-    protected Object visit(ASTVar node, Object data) {
-        int symbol = node.getSymbol();
-        // if we have a var, we have a scope thus a frame
-        if (frame.has(symbol)) {
-            return frame.get(symbol);
-        } else {
-            frame.set(symbol, null);
-            return null;
-        }
-    }
-
-    @Override
-    protected Object visit(ASTIdentifier node, Object data) {
-        cancelCheck(node);
-        String name = node.getName();
-        if (data == null) {
-            int symbol = node.getSymbol();
-            // if we have a symbol, we have a scope thus a frame
-            if (symbol >= 0 && frame.has(symbol)) {
-                return frame.get(symbol);
-            }
-            Object value = context.get(name);
-            if (value == null
-                && !(node.jjtGetParent() instanceof ASTReference)
-                && !(context.has(name))) {
-                return jexl.safe
-                        ? null
-                        : unsolvableVariable(node, name, !(node.getSymbol() >= 0 || context.has(name)));
-            }
-            return value;
-        } else {
-            return getAttribute(data, name, node);
-        }
+    protected Object visit(ASTIdentifier identifier, Object data) {
+        cancelCheck(identifier);
+        return data != null
+                ? getAttribute(data, identifier.getName(), identifier)
+                : getVariable(frame, block, identifier);
     }
 
     @Override
@@ -1036,7 +1100,7 @@ public class Interpreter extends InterpreterBase {
         JexlNode objectNode = null;
         JexlNode ptyNode = null;
         StringBuilder ant = null;
-        boolean antish = !(parent instanceof ASTReference);
+        boolean antish = !(parent instanceof ASTReference) && options.isAntish();
         int v = 1;
         main:
         for (int c = 0; c < numChildren; c++) {
@@ -1123,7 +1187,7 @@ public class Interpreter extends InterpreterBase {
         // am I the left-hand side of a safe op ?
         if (object == null) {
             if (ptyNode != null) {
-                if (ptyNode.isSafeLhs(jexl.safe)) {
+                if (ptyNode.isSafeLhs(isSafe())) {
                     return null;
                 }
                 if (ant != null) {
@@ -1134,7 +1198,7 @@ public class Interpreter extends InterpreterBase {
                 return unsolvableProperty(node, stringifyProperty(ptyNode), ptyNode == objectNode, null);
             }
             if (antish) {
-                if (node.isSafeLhs(jexl.safe)) {
+                if (node.isSafeLhs(isSafe())) {
                     return null;
                 }
                 String aname = ant != null ? ant.toString() : "?";
@@ -1203,21 +1267,36 @@ public class Interpreter extends InterpreterBase {
         cancelCheck(node);
         // left contains the reference to assign to
         final JexlNode left = node.jjtGetChild(0);
-        // right is the value expression to assign
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        ASTIdentifier var = null;
         Object object = null;
         int symbol = -1;
-        boolean antish = true;
-        // 0: determine initial object & property:
-        final int last = left.jjtGetNumChildren() - 1;
+        // check var decl with assign is ok
         if (left instanceof ASTIdentifier) {
-            ASTIdentifier var = (ASTIdentifier) left;
+            var = (ASTIdentifier) left;
             symbol = var.getSymbol();
+            if (symbol >= 0 && options.isLexical()) {
+                if (var instanceof ASTVar) {
+                    if (!block.declareSymbol(symbol)) {
+                        return redefinedVariable(var, var.getName());
+                    }
+                // if not in lexical block, undefined if (in its symbol) shade
+                } else if (!block.hasSymbol(symbol) && options.isLexicalShade()) {
+                    return undefinedVariable(var, var.getName());
+                }
+            }
+        }
+        boolean antish = options.isAntish();
+        // 0: determine initial object & property:
+        final int last = left.jjtGetNumChildren() - 1;
+        // right is the value expression to assign
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        // a (var?) v = ... expression
+        if (var != null) {
             if (symbol >= 0) {
                 // check we are not assigning a symbol itself
                 if (last < 0) {
                     if (assignop != null) {
-                        Object self = getVariable(frame, var);
+                        Object self = getVariable(frame, block, var);
                         right = operators.tryAssignOverload(node, assignop, self, right);
                         if (right == JexlOperator.ASSIGN) {
                             return self;
@@ -1230,7 +1309,7 @@ public class Interpreter extends InterpreterBase {
                     }
                     return right; // 1
                 }
-                object = getVariable(frame, var);
+                object = getVariable(frame, block, var);
                 // top level is a symbol, can not be an antish var
                 antish = false;
             } else {
@@ -1243,11 +1322,7 @@ public class Interpreter extends InterpreterBase {
                             return self;
                         }
                     }
-                    try {
-                        context.set(var.getName(), right);
-                    } catch (UnsupportedOperationException xsupport) {
-                        throw new JexlException(node, "context is readonly", xsupport);
-                    }
+                    setContextVariable(node, var.getName(), right);
                     return right; // 2
                 }
                 object = context.get(var.getName());
@@ -1326,11 +1401,7 @@ public class Interpreter extends InterpreterBase {
                         return self;
                     }
                 }
-                try {
-                    context.set(ant.toString(), right);
-                } catch (UnsupportedOperationException xsupport) {
-                    throw new JexlException(node, "context is readonly", xsupport);
-                }
+                setContextVariable(propertyNode, ant.toString(), right);
                 return right; // 3
             }
             // property of an object ?
@@ -1401,7 +1472,7 @@ public class Interpreter extends InterpreterBase {
                 object = data;
                 if (object == null) {
                     // no object, we fail
-                    return node.isSafeLhs(jexl.safe)
+                    return node.isSafeLhs(isSafe())
                         ? null
                         : unsolvableMethod(methodNode, "<null>.<?>(...)");
                 }
@@ -1416,7 +1487,7 @@ public class Interpreter extends InterpreterBase {
         for (int a = 1; a < node.jjtGetNumChildren(); ++a) {
             if (result == null) {
                 // no method, we fail// variable unknown in context and not a local
-                return node.isSafeLhs(jexl.safe)
+                return node.isSafeLhs(isSafe())
                         ? null
                         : unsolvableMethod(methodNode, "<?>.<null>(...)");
             }
@@ -1470,7 +1541,7 @@ public class Interpreter extends InterpreterBase {
             functor = null;
             // is it a global or local variable ?
             if (target == context) {
-                if (symbol >= 0 && frame.has(symbol)) {
+                if (frame != null && frame.has(symbol)) {
                     functor = frame.get(symbol);
                     isavar = functor != null;
                 } else if (context.has(methodName)) {
@@ -1491,7 +1562,7 @@ public class Interpreter extends InterpreterBase {
             symbol = -1 - 1; // -2;
             methodName = null;
             cacheable = false;
-        } else if (!node.isSafeLhs(jexl.safe)) {
+        } else if (!node.isSafeLhs(isSafe())) {
             return unsolvableMethod(node, "?(...)");
         } else {
             // safe lhs
@@ -1596,7 +1667,7 @@ public class Interpreter extends InterpreterBase {
                 }
             }
             // we have either evaluated and returned or no method was found
-            return node.isSafeLhs(jexl.safe)
+            return node.isSafeLhs(isSafe())
                     ? null
                     : unsolvableMethod(node, methodName, argv);
         } catch (JexlException.TryFailed xany) {
@@ -1719,7 +1790,7 @@ public class Interpreter extends InterpreterBase {
         // are we evaluating the block ?
         final int last = stmt.jjtGetNumChildren() - 1;
         if (index == last) {
-            JexlNode block = stmt.jjtGetChild(last);
+            JexlNode cblock = stmt.jjtGetChild(last);
             // if the context has changed, might need a new interpreter
             final JexlArithmetic jexla = arithmetic.options(context);
             if (jexla != arithmetic) {
@@ -1729,13 +1800,13 @@ public class Interpreter extends InterpreterBase {
                     );
                 }
                 Interpreter ii = new Interpreter(Interpreter.this, jexla);
-                Object r = block.jjtAccept(ii, data);
+                Object r = cblock.jjtAccept(ii, data);
                 if (ii.isCancelled()) {
                     Interpreter.this.cancel();
                 }
                 return r;
             } else {
-                return block.jjtAccept(Interpreter.this, data);
+                return cblock.jjtAccept(Interpreter.this, data);
             }
         }
         // tracking whether we processed the annotation
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 399ee3a..832bd10 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -24,7 +24,9 @@ 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.JexlException.VariableIssue;
 import org.apache.commons.jexl3.JexlOperator;
+import org.apache.commons.jexl3.JexlOptions;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
 import org.apache.commons.jexl3.introspection.JexlPropertySet;
@@ -56,6 +58,8 @@ public abstract class InterpreterBase extends ParserVisitor {
     protected final JexlArithmetic arithmetic;
     /** The context to store/retrieve variables. */
     protected final JexlContext context;
+    /** The options. */
+    protected final JexlOptions options;
     /** Cache executors. */
     protected final boolean cache;
     /** Cancellation support. */
@@ -70,7 +74,7 @@ public abstract class InterpreterBase extends ParserVisitor {
     protected Map<String, Object> functors;
     /** The operators evaluation delegate. */
     protected final Operators operators;
-
+    
     /**
      * Creates an interpreter base.
      * @param engine   the engine creating this interpreter
@@ -83,10 +87,11 @@ public abstract class InterpreterBase extends ParserVisitor {
         this.context = aContext != null ? aContext : Engine.EMPTY_CONTEXT;
         this.cache = engine.cache != null;
         JexlArithmetic jexla = jexl.arithmetic;
-        this.arithmetic = jexla.options(context);
+        this.options = jexl.options(context);
+        this.arithmetic = jexla.options(options);
         if (arithmetic != jexla && !arithmetic.getClass().equals(jexla.getClass())) {
             logger.warn("expected arithmetic to be " + jexla.getClass().getSimpleName()
-                          + ", got " + arithmetic.getClass().getSimpleName()
+                    + ", got " + arithmetic.getClass().getSimpleName()
             );
         }
         if (this.context instanceof JexlContext.NamespaceResolver) {
@@ -98,7 +103,7 @@ public abstract class InterpreterBase extends ParserVisitor {
         this.functors = null;
         this.operators = new Operators(this);
     }
-
+    
     /**
      * Copy constructor.
      * @param ii the base to copy
@@ -115,6 +120,7 @@ public abstract class InterpreterBase extends ParserVisitor {
         functions = ii.functions;
         functors = ii.functors;
         operators = ii.operators;
+        options = new org.apache.commons.jexl3.internal.Options(ii.options);
     }
 
 
@@ -188,9 +194,13 @@ public abstract class InterpreterBase extends ParserVisitor {
             } else if (namespace instanceof Class<?> || namespace instanceof String) {
                 // attempt to reuse last ctor cached in volatile JexlNode.value
                 if (cached instanceof JexlMethod) {
-                    Object eval = ((JexlMethod) cached).tryInvoke(null, context);
-                    if (JexlEngine.TRY_FAILED != eval) {
-                        functor = eval;
+                    try {
+                        Object eval = ((JexlMethod) cached).tryInvoke(null, context);
+                        if (JexlEngine.TRY_FAILED != eval) {
+                            functor = eval;
+                        }
+                    } catch (JexlException.TryFailed xtry) {
+                        throw new JexlException(node, "unable to instantiate namespace " + prefix, xtry.getCause());
                     }
                 }
                 if (functor == null) {
@@ -227,15 +237,53 @@ public abstract class InterpreterBase extends ParserVisitor {
     /**
      * Gets a value of a defined local variable or from the context.
      * @param frame the local frame
-     * @param node the variable node
+     * @param block the lexical block if any
+     * @param identifier the variable node
      * @return the value
      */
-    protected Object getVariable(Scope.Frame frame, ASTIdentifier node) {
-        int symbol = node.getSymbol();
-        if (frame.has(symbol)) {
-            return frame.get(symbol);
+    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 (symbol >= 0) {
+            if (frame.has(symbol)) {
+                if (options.isLexical()) {
+                    // if not in lexical block, undefined if (in its symbol) shade
+                    if (!block.hasSymbol(symbol) && options.isLexicalShade()) {
+                        return undefinedVariable(identifier, identifier.getName());
+                    }
+                }
+                return frame.get(symbol);
+            }
+        }
+        String name = identifier.getName();
+        Object value = context.get(name);
+        if (value == null
+            && !(identifier.jjtGetParent() instanceof ASTReference)
+            && !(context.has(name))) {
+                return isSafe()
+                    ? null
+                    : unsolvableVariable(identifier, name, true); // undefined
+        }
+        return value;
+    }
+
+    /**
+     * Sets a variable in the global context.
+     * <p>If interpretation applies lexical shade, the variable must exist (ie
+     * the context has(...) method returns true) otherwise an error occurs.
+     * @param node the node 
+     * @param name the variable name
+     * @param value the variable value
+     */
+    protected void setContextVariable(JexlNode node, String name, Object value) {
+        if (options.isLexicalShade() && !context.has(name)) {
+            throw new JexlException.Variable(node, name, true);
+        }
+        try {
+            context.set(name, value);
+        } catch (UnsupportedOperationException xsupport) {
+            throw new JexlException(node, "context is readonly", xsupport);
         }
-        return context.get(node.getName());
     }
     
     /**
@@ -243,14 +291,15 @@ public abstract class InterpreterBase extends ParserVisitor {
      * @return true if strict engine, false otherwise
      */
     protected boolean isStrictEngine() {
-        if (this.context instanceof JexlEngine.Options) {
-            JexlEngine.Options opts = (JexlEngine.Options) context;
-            Boolean strict = opts.isStrict();
-            if (strict != null) {
-                return strict.booleanValue();
-            }
-        }
-        return jexl.isStrict();
+        return options.isStrict();
+    }
+    
+    /**
+     * Whether this interpreter ignores null in navigation expression as errors.
+     * @return true if safe, false otherwise
+     */
+    protected boolean isSafe() {
+        return options.isSafe();
     }
 
     /**
@@ -258,26 +307,14 @@ public abstract class InterpreterBase extends ParserVisitor {
      * @return true if silent, false otherwise
      */
     protected boolean isSilent() {
-        if (this.context instanceof JexlEngine.Options) {
-            JexlEngine.Options opts = (JexlEngine.Options) context;
-            Boolean silent = opts.isSilent();
-            if (silent != null) {
-                return silent.booleanValue();
-            }
-        }
-        return jexl.isSilent();
+        return options.isSilent();
     }
-
-    /** @return true if interrupt throws a JexlException.Cancel. */
+    
+    /**
+     * @return true if interrupt throws a JexlException.Cancel.
+     */
     protected boolean isCancellable() {
-        if (this.context instanceof JexlEngine.Options) {
-            JexlEngine.Options opts = (JexlEngine.Options) context;
-            Boolean ocancellable = opts.isCancellable();
-            if (ocancellable != null) {
-                return ocancellable.booleanValue();
-            }
-        }
-        return jexl.isCancellable();
+        return options.isCancellable();
     }
 
     /**
@@ -308,14 +345,44 @@ public abstract class InterpreterBase extends ParserVisitor {
      * @return throws JexlException if strict and not silent, null otherwise
      */
     protected Object unsolvableVariable(JexlNode node, String var, boolean undef) {
+        return variableError(node, var, undef? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
+    }
+    
+    /**
+     * Triggered when a variable is lexically known as undefined.
+     * @param node  the node where the error originated from
+     * @param var   the variable name
+     * @return throws JexlException if strict and not silent, null otherwise
+     */
+    protected Object undefinedVariable(JexlNode node, String var) {
+        return variableError(node, var, VariableIssue.UNDEFINED);
+    }
+           
+    /**
+     * Triggered when a variable is lexically known as being redefined.
+     * @param node  the node where the error originated from
+     * @param var   the variable name
+     * @return throws JexlException if strict and not silent, null otherwise
+     */ 
+    protected Object redefinedVariable(JexlNode node, String var) {
+        return variableError(node, var, VariableIssue.REDEFINED);
+    }
+          
+    /**
+     * Triggered when a variable generates an issue.
+     * @param node  the node where the error originated from
+     * @param var   the variable name
+     * @param issue the issue type
+     * @return throws JexlException if strict and not silent, null otherwise
+     */ 
+    protected Object variableError(JexlNode node, String var, VariableIssue issue) {
         if (isStrictEngine() && !node.isTernaryProtected()) {
-            throw new JexlException.Variable(node, var, undef);
+            throw new JexlException.Variable(node, var, issue);
         } else if (logger.isDebugEnabled()) {
-            logger.debug(JexlException.variableError(node, var, undef));
+            logger.debug(JexlException.variableError(node, var, issue));
         }
         return null;
     }
-
     /**
      * Triggered when a method can not be resolved.
      * @param node   the node where the error originated from
diff --git a/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java b/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java
new file mode 100644
index 0000000..b9fc4bb
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.internal;
+
+import java.util.BitSet;
+
+/**
+ * The set of symbols declared in a lexical scope.
+ * <p>The symbol identifiers are determined by the functional scope.
+ */
+public final class LexicalScope {
+    /** Number of bits in a long. */
+    protected static final int LONGBITS = 64;
+    /** The mask of symbols in the frame. */
+    protected long symbols = 0L;
+    /** Symbols after 64. */
+    protected BitSet moreSymbols = null;
+    /** Previous block. */
+    protected final LexicalScope previous;
+
+    /**
+     * Default ctor.
+     * @param scope the previous scope
+     */
+    public LexicalScope(LexicalScope scope) {
+        this(null, scope);
+    }
+
+    /**
+     * Create a scope.
+     * @param frame the current execution frame
+     * @param scope the previous scope
+     */
+    public LexicalScope(Frame frame, LexicalScope scope) {
+        if (frame != null) {
+            int argc = frame.getScope().getArgCount();
+            for(int a  = 0; a < argc; ++a) {
+                declareSymbol(a);
+            }
+        }
+        previous = scope;
+    }
+
+    /**
+     * Ensure more symbpls can be stored.
+     * @return the set of more symbols
+     */
+    final BitSet moreSymbols() {
+        if (moreSymbols == null) {
+            moreSymbols = new BitSet();
+        }
+        return moreSymbols;
+    }
+    
+    /**
+     * Checks whether a symbol has already been declared.
+     * @param symbol the symbol
+     * @return true if declared, false otherwise
+     */
+    public boolean hasSymbol(int symbol) {
+        if (symbol < LONGBITS) {
+            return (symbols & (1L << symbol)) != 0L;
+        } else {
+            return moreSymbols == null ? false : moreSymbols.get(symbol - LONGBITS);
+        }
+    }
+
+    /**
+     * 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;
+        }
+        if (symbol < LONGBITS) {
+            if ((symbols & (1L << symbol)) != 0L) {
+                return false;
+            }
+            symbols |= (1L << symbol);
+        } else {
+            int s = symbol - LONGBITS;
+            BitSet ms = moreSymbols();
+            if (ms.get(s)) {
+                return false;
+            }
+            ms.set(s, true);
+        }
+        return true;
+    }
+
+    /**
+     * @return the number of symbols defined in this scope.
+     */
+    public int getSymbolCount() {
+        return Long.bitCount(symbols) + (moreSymbols == null? 0 : moreSymbols.cardinality());
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Options.java b/src/main/java/org/apache/commons/jexl3/internal/Options.java
new file mode 100644
index 0000000..f33ea5e
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/Options.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl3.internal;
+
+import org.apache.commons.jexl3.JexlOptions;
+import org.apache.commons.jexl3.JexlEngine;
+
+import java.math.MathContext;
+
+/**
+ * A basic implementation of JexlOptions.
+ * <p>Thread safety is only guaranteed if no modifications (call set* method)
+ * occurs in different threads; note however that using the 'callable()' method to
+ * allow a script to run as such will use a copy.
+ */
+public class Options implements JexlOptions {
+    /** The local shade bit. */
+    protected static final int SHADE = 6;
+    /** The antish var bit. */
+    protected static final int ANTISH_VAR = 5;
+    /** The lexical scope bit. */
+    protected static final int LEXICAL = 4;
+    /** The safe bit. */
+    protected static final int SAFE = 3;
+    /** The silent bit. */
+    protected static final int SILENT = 2;
+    /** The strict bit. */
+    protected static final int STRICT = 1;
+    /** The cancellable bit. */
+    protected static final int CANCELLABLE = 0;
+    /** Default mask .*/
+    protected static final int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH_VAR;
+    /** The arithmetic math context. */
+    private MathContext mathContext = null;
+    /** The arithmetic math scale. */
+    private int mathScale = Integer.MIN_VALUE;
+    /** The arithmetic strict math flag. */
+    private boolean strictArithmetic = true;
+    /** The default flags, all but safe. */
+    private int flags = DEFAULT;
+
+    /**
+     * 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
+     */
+    protected 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
+     */
+    protected static boolean isSet(int ordinal, int mask) {
+        return (mask & 1 << ordinal) != 0;
+    }
+        
+    /**
+     * Default ctor.
+     */
+    public Options() {}
+
+    /**
+     * Set options from engine.
+     * @param jexl the engine
+     */
+    @Override
+    public void setOptions(JexlEngine jexl) {
+        mathContext = jexl.getArithmetic().getMathContext();
+        mathScale = jexl.getArithmetic().getMathScale();
+        strictArithmetic = jexl.getArithmetic().isStrict();
+        int mask = DEFAULT;
+        mask = set(STRICT, mask, jexl.isStrict());
+        mask = set(SILENT, mask, jexl.isSilent());
+        mask = set(SAFE, mask, jexl.isSafe());
+        mask = set(CANCELLABLE, mask, jexl.isCancellable());
+        flags = mask;
+    }
+    
+    @Override
+    public JexlOptions copy() {
+        return new Options(this);
+    }
+
+    /**
+     * Create a copy from another set of options.
+     * @param opts the source options to copy
+     */
+    public Options(JexlOptions opts) {
+        if (opts instanceof Options) {
+            Options src = (Options) opts;
+            mathContext = src.mathContext;
+            mathScale = src.mathScale;
+            strictArithmetic = src.strictArithmetic;
+            flags = src.flags;
+        } else {
+            mathContext = opts.getMathContext();
+            mathScale = opts.getMathScale();
+            strictArithmetic = opts.isStrict();
+            int mask = DEFAULT;
+            mask = set(STRICT, mask, opts.isStrict());
+            mask = set(SILENT, mask, opts.isSilent());
+            mask = set(SAFE, mask, opts.isSafe());
+            mask = set(CANCELLABLE, mask, opts.isCancellable());
+            flags = mask;
+        }
+    }
+    
+    @Override
+    public void setAntish(boolean flag) {
+        flags = set(ANTISH_VAR, flags, flag);
+    }
+
+    @Override
+    public void setMathContext(MathContext mcontext) {
+        this.mathContext = mcontext;
+    }
+
+    @Override
+    public void setMathScale(int mscale) {
+        this.mathScale = mscale;
+    }
+
+    @Override
+    public void setStrictArithmetic(boolean stricta) {
+        this.strictArithmetic = stricta;
+    }
+
+    @Override
+    public void setStrict(boolean flag) {
+        flags = set(STRICT, flags, flag);
+    }
+            
+    @Override
+    public void setSafe(boolean flag) {
+        flags = set(SAFE, flags, flag);
+    }    
+
+    @Override
+    public void setSilent(boolean flag) {
+        flags = set(SILENT, flags, flag);
+    }
+            
+    @Override
+    public void setCancellable(boolean flag) {
+        flags = set(CANCELLABLE, flags, flag);
+    }
+            
+    @Override
+    public void setLexical(boolean flag) {
+        flags = set(LEXICAL, flags, flag);
+    }    
+    
+    @Override
+    public void setLexicalShade(boolean flag) {
+        flags = set(SHADE, flags, flag);
+    }
+    
+    @Override
+    public boolean isAntish() {
+        return isSet(ANTISH_VAR, flags);
+    }
+    
+    @Override
+    public boolean isSilent() {
+        return isSet(SILENT, flags);
+    }
+
+    @Override
+    public boolean isStrict() {
+        return isSet(STRICT, flags);
+    }
+    
+    @Override
+    public boolean isSafe() {
+        return isSet(SAFE, flags);
+    }
+
+    @Override
+    public boolean isCancellable() {
+        return isSet(CANCELLABLE, flags);
+    }
+     
+    @Override
+    public boolean isLexical() {
+        return isSet(LEXICAL, flags);
+    }
+       
+    @Override
+    public boolean isLexicalShade() {
+        return isSet(SHADE, flags);
+    }
+
+    @Override
+    public boolean isStrictArithmetic() {
+        return strictArithmetic;
+    }
+
+    @Override
+    public MathContext getMathContext() {
+        return mathContext;
+    }
+    
+    @Override
+    public int getMathScale() {
+        return mathScale;
+    }
+    
+}
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 15c0385..51cf842 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
@@ -22,13 +22,14 @@ import java.util.Map;
 
 /**
  * A script scope, stores the declaration of parameters and local variables as symbols.
+ * <p>This also acts as the functional scope and variable definition store.
  * @since 3.0
  */
 public final class Scope {    
     /**
      * The value of a declared but undefined variable, for instance: var x;.
      */
-    private static final Object UNDEFINED = new Object() {
+    static final Object UNDEFINED = new Object() {
         @Override public String toString() {
             return "?";
         }
@@ -152,8 +153,9 @@ public final class Scope {
      * This method creates an new entry in the symbol map.
      * </p>
      * @param name the parameter name
+     * @return the register index storing this variable
      */
-    public void declareParameter(String name) {
+    public int declareParameter(String name) {
         if (namedVariables == null) {
             namedVariables = new LinkedHashMap<String, Integer>();
         } else if (vars > 0) {
@@ -165,6 +167,7 @@ public final class Scope {
             namedVariables.put(name, register);
             parms += 1;
         }
+        return register;
     }
 
     /**
@@ -175,7 +178,7 @@ public final class Scope {
      * @param name the variable name
      * @return the register index storing this variable
      */
-    public Integer declareVariable(String name) {
+    public int declareVariable(String name) {
         if (namedVariables == null) {
             namedVariables = new LinkedHashMap<String, Integer>();
         }
@@ -202,9 +205,10 @@ public final class Scope {
      * Creates a frame by copying values up to the number of parameters.
      * <p>This captures the hoisted variables values.</p>
      * @param frame the caller frame
+     * @param args the arguments
      * @return the arguments array
      */
-    public Frame createFrame(Frame frame) {
+    public Frame createFrame(Frame frame, Object...args) {
         if (namedVariables != null) {
             Object[] arguments = new Object[namedVariables.size()];
             Arrays.fill(arguments, UNDEFINED);
@@ -216,7 +220,7 @@ public final class Scope {
                     arguments[target] = arg;
                 }
             }
-            return new Frame(this, arguments, 0);
+            return new Frame(this, arguments, 0).assign(args);
         } else {
             return null;
         }
@@ -305,109 +309,4 @@ public final class Scope {
         }
     }
 
-    /**
-     * A call frame, created from a scope, stores the arguments and local variables in a "stack frame" (sic).
-     * @since 3.0
-     */
-    public static final class Frame {
-        /** The scope. */
-        private final Scope scope;
-        /** The actual stack frame. */
-        private final Object[] stack;
-        /** Number of curried parameters. */
-        private int curried = 0;
-
-        /**
-         * Creates a new frame.
-         * @param s the scope
-         * @param r the stack frame
-         * @param c the number of curried parameters
-         */
-        public Frame(Scope s, Object[] r, int c) {
-            scope = s;
-            stack = r;
-            curried = c;
-        }
-
-        /**
-         * Gets this script unbound parameters, i.e. parameters not bound through curry().
-         * @return the parameter names
-         */
-        public String[] getUnboundParameters() {
-            return scope.getParameters(curried);
-        }
-
-        /**
-         * Gets the scope.
-         * @return this frame scope
-         */
-        public Scope getScope() {
-            return scope;
-        }
-
-        @Override
-        public int hashCode() {
-            return Arrays.deepHashCode(this.stack);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj == null) {
-                return false;
-            }
-            if (getClass() != obj.getClass()) {
-                return false;
-            }
-            final Frame other = (Frame) obj;
-            return Arrays.deepEquals(this.stack, other.stack);
-        }
-
-        /**
-         * Gets a value.
-         * @param s the offset in this frame
-         * @return the stacked value
-         */
-        public Object get(int s) {
-            return stack[s];
-        }
-        
-        /**
-         * Whether this frame defines a symbol, ie declared it and assigned it a value.
-         * @param s the offset in this frame
-         * @return true if this symbol has been assigned a value, false otherwise
-         */
-        public boolean has(int s) {
-            return s >= 0 && s < stack.length && stack[s] != UNDEFINED;
-        }
-            
-        /**
-         * Sets a value.
-         * @param r the offset in this frame
-         * @param value the value to set in this frame
-         */
-        public void set(int r, Object value) {
-            stack[r] = value;
-        }
-
-        /**
-         * Assign values to this frame.
-         * @param values the values
-         * @return this frame
-         */
-        public Frame assign(Object... values) {
-            if (stack != null) {
-                int nparm = scope.getArgCount();
-                Object[] copy = stack.clone();
-                int ncopy = 0;
-                if (values != null && values.length > 0) {
-                    ncopy = Math.min(nparm - curried, Math.min(nparm, values.length));
-                    System.arraycopy(values, 0, copy, curried, ncopy);
-                }
-                // unbound parameters are defined as null
-                Arrays.fill(copy, curried + ncopy, nparm, null);
-                return new Frame(scope, copy, curried + ncopy);
-            }
-            return this;
-        }
-    }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Script.java b/src/main/java/org/apache/commons/jexl3/internal/Script.java
index 2cd6642..62ecaca 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Script.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Script.java
@@ -93,7 +93,7 @@ public class Script implements JexlScript, JexlExpression {
      * @param args the arguments to bind to parameters
      * @return the frame (may be null)
      */
-    protected Scope.Frame createFrame(Object[] args) {
+    protected Frame createFrame(Object[] args) {
         return script.createFrame(args);
     }
 
@@ -103,7 +103,7 @@ public class Script implements JexlScript, JexlExpression {
      * @param frame the calling frame
      * @return  the interpreter
      */
-    protected Interpreter createInterpreter(JexlContext context, Scope.Frame frame) {
+    protected Interpreter createInterpreter(JexlContext context, Frame frame) {
         return jexl.createInterpreter(context, frame);
     }
 
@@ -179,7 +179,7 @@ public class Script implements JexlScript, JexlExpression {
     @Override
     public Object execute(JexlContext context) {
         checkCacheVersion();
-        Scope.Frame frame = createFrame(null);
+        Frame frame = createFrame(null);
         Interpreter interpreter = createInterpreter(context, frame);
         return interpreter.interpret(script);
     }
@@ -187,7 +187,7 @@ public class Script implements JexlScript, JexlExpression {
     @Override
     public Object execute(JexlContext context, Object... args) {
         checkCacheVersion();
-        Scope.Frame frame = createFrame(args != null && args.length > 0 ? args : null);
+        Frame frame = createFrame(args != null && args.length > 0 ? args : null);
         Interpreter interpreter = createInterpreter(context, frame);
         return interpreter.interpret(script);
     }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
index ffca1ef..f1ba7c0 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
@@ -47,7 +47,7 @@ public class TemplateInterpreter extends Interpreter {
      * @param out         the output writer
      */
     TemplateInterpreter(Engine jexl,
-            JexlContext jcontext, Scope.Frame jframe, TemplateExpression[] expressions, Writer out) {
+            JexlContext jcontext, Frame jframe, TemplateExpression[] expressions, Writer out) {
         super(jexl, jcontext, jframe);
         exprs = expressions;
         writer = out;
@@ -148,7 +148,7 @@ public class TemplateInterpreter extends Interpreter {
         if (node instanceof ASTJexlLambda && !((ASTJexlLambda) node).isTopLevel()) {
             return new Closure(this, (ASTJexlLambda) node) {
                 @Override
-                protected Interpreter createInterpreter(JexlContext context, Scope.Frame local) {
+                protected Interpreter createInterpreter(JexlContext context, Frame local) {
                     return new TemplateInterpreter(jexl, context, local, exprs, writer);
                 }
             };
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 63829f2..760f876 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
@@ -233,7 +233,7 @@ public final class TemplateScript implements JxltEngine.Template {
 
     @Override
     public TemplateScript prepare(JexlContext context) {
-        Scope.Frame frame = script.createFrame((Object[]) null);
+        Frame frame = script.createFrame((Object[]) null);
         TemplateExpression[] immediates = new TemplateExpression[exprs.length];
         for (int e = 0; e < exprs.length; ++e) {
             immediates[e] = exprs[e].prepare(frame, context);
@@ -248,7 +248,7 @@ public final class TemplateScript implements JxltEngine.Template {
 
     @Override
     public void evaluate(JexlContext context, Writer writer, Object... args) {
-        Scope.Frame frame = script.createFrame(args);
+        Frame frame = script.createFrame(args);
         Interpreter interpreter = new TemplateInterpreter(jxlt.getEngine(), context, frame, exprs, writer);
         interpreter.interpret(script);
     }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
index f3c9340..05e6726 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
@@ -284,7 +284,7 @@ final class ClassMap {
                 // add method to byKey cache; do not override
                 MethodKey key = new MethodKey(mi);
                 Method pmi = cache.byKey.putIfAbsent(key, permissions.allow(mi) ? mi : CACHE_MISS);
-                if (pmi != null && log.isDebugEnabled() && !key.equals(new MethodKey(pmi))) {
+                if (pmi != null && pmi != CACHE_MISS && log.isDebugEnabled() && !key.equals(new MethodKey(pmi))) {
                     // foo(int) and foo(Integer) have the same signature for JEXL
                     log.debug("Method " + pmi + " is already registered, key: " + key.debugString());
                 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java
index e787b4b..e82a111 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java
@@ -19,7 +19,6 @@ package org.apache.commons.jexl3.internal.introspection;
 import java.beans.IntrospectionException;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
-import org.apache.commons.jexl3.JexlException;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 
 /**
@@ -102,7 +101,7 @@ public final class ConstructorMethod implements JexlMethod {
         } catch (IllegalArgumentException xargument) {
             return Uberspect.TRY_FAILED;
         } catch (InvocationTargetException xinvoke) {
-            throw JexlException.tryFailed(xinvoke); // throw
+            return Uberspect.TRY_FAILED;
         }
         return Uberspect.TRY_FAILED;
     }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
index 4566638..b442aa3 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
@@ -61,7 +61,7 @@ public final class Introspector {
     /**
      * the logger.
      */
-    protected final Log rlog;
+    protected final Log logger;
     /**
      * The class loader used to solve constructors if needed.
      */
@@ -103,7 +103,7 @@ public final class Introspector {
      * @param perms the permissions
      */
     public Introspector(Log log, ClassLoader cloader, Permissions perms) {
-        this.rlog = log;
+        this.logger = log;
         this.loader = cloader;
         this.permissions = perms != null? perms : Permissions.DEFAULT;
     }
@@ -146,8 +146,8 @@ public final class Introspector {
             return getMap(c).getMethod(key);
         } catch (MethodKey.AmbiguousException xambiguous) {
             // whoops. Ambiguous and not benign. Make a nice log message and return null...
-            if (rlog != null && xambiguous.isSevere() && rlog.isInfoEnabled()) {
-                rlog.info("ambiguous method invocation: "
+            if (logger != null && xambiguous.isSevere() && logger.isInfoEnabled()) {
+                logger.info("ambiguous method invocation: "
                         + c.getName() + "."
                         + key.debugString(), xambiguous);
             }
@@ -273,15 +273,15 @@ public final class Introspector {
                     constructorsMap.put(key, CTOR_MISS);
                 }
             } catch (ClassNotFoundException xnotfound) {
-                if (rlog != null && rlog.isDebugEnabled()) {
-                    rlog.debug("unable to find class: "
+                if (logger != null && logger.isDebugEnabled()) {
+                    logger.debug("unable to find class: "
                             + cname + "."
                             + key.debugString(), xnotfound);
                 }
                 ctor = null;
             } catch (MethodKey.AmbiguousException xambiguous) {
-                if (rlog != null  && xambiguous.isSevere() &&  rlog.isInfoEnabled()) {
-                    rlog.info("ambiguous constructor invocation: "
+                if (logger != null  && xambiguous.isSevere() &&  logger.isInfoEnabled()) {
+                    logger.info("ambiguous constructor invocation: "
                             + cname + "."
                             + key.debugString(), xambiguous);
                 }
@@ -312,7 +312,7 @@ public final class Introspector {
                 // try again
                 classMap = classMethodMaps.get(c);
                 if (classMap == null) {
-                    classMap = new ClassMap(c, permissions, rlog);
+                    classMap = new ClassMap(c, permissions, logger);
                     classMethodMaps.put(c, classMap);
                 }
             } finally {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
index d596c9d..6e0a118 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
@@ -83,7 +83,7 @@ public final class MethodExecutor extends AbstractExecutor.Method {
 
     @Override
     public Object invoke(Object o, Object... args) throws IllegalAccessException, InvocationTargetException {
-        if (vaClass != null) {
+        if (vaClass != null && args != null) {
             args = handleVarArg(args);
         }
         if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
index 8301800..abc3e13 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
@@ -53,7 +53,7 @@ public class Uberspect implements JexlUberspect {
     /** Publicly exposed special failure object returned by tryInvoke. */
     public static final Object TRY_FAILED = JexlEngine.TRY_FAILED;
     /** The logger to use for all warnings and errors. */
-    protected final Log rlog;
+    protected final Log logger;
     /** The resolver strategy. */
     private final JexlUberspect.ResolverStrategy strategy;
     /** The permissions. */
@@ -88,7 +88,7 @@ public class Uberspect implements JexlUberspect {
      * @param perms the introspector permissions
      */
     public Uberspect(Log runtimeLogger, JexlUberspect.ResolverStrategy sty, Permissions perms) {
-        rlog = runtimeLogger;
+        logger = runtimeLogger;
         strategy = sty == null? JexlUberspect.JEXL_STRATEGY : sty;
         permissions  = perms;
         ref = new SoftReference<Introspector>(null);
@@ -111,7 +111,7 @@ public class Uberspect implements JexlUberspect {
             synchronized (this) {
                 intro = ref.get();
                 if (intro == null) {
-                    intro = new Introspector(rlog, loader.get(), permissions);
+                    intro = new Introspector(logger, loader.get(), permissions);
                     ref = new SoftReference<Introspector>(intro);
                     loader = new SoftReference<ClassLoader>(intro.getLoader());
                     version.incrementAndGet();
@@ -129,7 +129,7 @@ public class Uberspect implements JexlUberspect {
             if (intro != null) {
                 intro.setLoader(nloader);
             } else {
-                intro = new Introspector(rlog, nloader, permissions);
+                intro = new Introspector(logger, nloader, permissions);
                 ref = new SoftReference<Introspector>(intro);
             }
             loader = new SoftReference<ClassLoader>(intro.getLoader());
@@ -394,8 +394,8 @@ public class Uberspect implements JexlUberspect {
                 return (Iterator<Object>) it.invoke(obj, (Object[]) null);
             }
         } catch (Exception xany) {
-            if (rlog != null && rlog.isDebugEnabled()) {
-                rlog.info("unable to solve iterator()", xany);
+            if (logger != null && logger.isDebugEnabled()) {
+                logger.info("unable to solve iterator()", xany);
             }
         }
         return null;
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java b/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java
new file mode 100644
index 0000000..03a00d9
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.parser;
+
+import org.apache.commons.jexl3.internal.LexicalScope;
+
+/**
+ * Declares a local variable.
+ */
+public class ASTBlock extends JexlNode implements JexlParser.LexicalUnit {
+    private LexicalScope locals = null;
+    
+    public ASTBlock(int id) {
+        super(id);
+    }
+
+    public ASTBlock(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);
+        }
+        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
new file mode 100644
index 0000000..1ad61c0
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.parser;
+
+import org.apache.commons.jexl3.internal.LexicalScope;
+
+/**
+ * Declares a local variable.
+ */
+public class ASTForeachStatement extends JexlNode implements JexlParser.LexicalUnit {
+    private LexicalScope locals = null;
+    
+    public ASTForeachStatement(int id) {
+        super(id);
+    }
+
+    public ASTForeachStatement(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);
+        }
+        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/ASTJexlScript.java b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
index 74c7bbc..51c5d3b 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
@@ -19,17 +19,21 @@ package org.apache.commons.jexl3.parser;
 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 {
-    /** The script scope. */
-    private Scope scope = null;
+public class ASTJexlScript extends JexlNode implements JexlParser.LexicalUnit  {
     /** 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);
@@ -38,7 +42,30 @@ public class ASTJexlScript extends JexlNode {
     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
@@ -93,6 +120,11 @@ public class ASTJexlScript extends JexlNode {
      */
     public void setScope(Scope theScope) {
         this.scope = theScope;
+        if (theScope != null) {
+            for(int a = 0; a < theScope.getArgCount(); ++a) {
+                this.declareSymbol(a);
+            }
+        }
     }
 
     /**
@@ -108,14 +140,8 @@ public class ASTJexlScript extends JexlNode {
      * @param values the argument values
      * @return the arguments array
      */
-    public Scope.Frame createFrame(Scope.Frame caller, Object... values) {
-        if (scope != null) {
-            Scope.Frame frame = scope.createFrame(caller);
-            if (frame != null) {
-                return frame.assign(values);
-            }
-        }
-        return null;
+    public Frame createFrame(Frame caller, Object... values) {
+        return scope != null? scope.createFrame(caller, values) : null;
     }
     
     /**
@@ -123,7 +149,7 @@ public class ASTJexlScript extends JexlNode {
      * @param values the argument values
      * @return the arguments array
      */
-    public Scope.Frame createFrame(Object... values) {
+    public Frame createFrame(Object... values) {
         return createFrame(null, values);
     }
 
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 64efd98..93b2bc3 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
@@ -162,10 +162,8 @@ public abstract class JexlNode extends SimpleNode {
             int nc = walk.jjtGetNumChildren() - 1;
             if (nc >= 0) {
                 walk = walk.jjtGetChild(nc);
-            } else if (walk.jjtGetParent() instanceof ASTReference) {
-                return true;
             } else {
-                return false;
+                return walk.jjtGetParent() instanceof ASTReference;
             }
         } while (walk != null);
         return false;
@@ -212,8 +210,8 @@ public abstract class JexlNode extends SimpleNode {
         }
         if (this instanceof ASTMethodNode) {
             if (this.jjtGetNumChildren() > 1
-                && this.jjtGetChild(0) instanceof ASTIdentifierAccess
-                && (((ASTIdentifierAccess) this.jjtGetChild(0)).isSafe() || safe)) {
+                    && this.jjtGetChild(0) instanceof ASTIdentifierAccess
+                    && (((ASTIdentifierAccess) this.jjtGetChild(0)).isSafe() || safe)) {
                 return true;
             }
         }
@@ -262,7 +260,7 @@ public abstract class JexlNode extends SimpleNode {
         JexlNode node = this;
         for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) {
             // protect only the condition part of the ternary
-            if (walk instanceof ASTTernaryNode || walk instanceof ASTNullpNode) {
+            if (walk instanceof ASTTernaryNode || walk instanceof ASTNullpNode || walk instanceof ASTEQNode || walk instanceof ASTNENode) {
                 return node == walk.jjtGetChild(0);
             }
             if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
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 17e1937..f0cf9b0 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -74,8 +74,49 @@ public abstract class JexlParser extends StringParser {
      * Stack of parsing loop counts.
      */
     protected 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;
+    /**
+     * Stack of lexical blocks.
+     */
+    protected Deque<LexicalUnit> blocks = new ArrayDeque<LexicalUnit>();
 
-
+    /**
+     * A lexical unit is the container defining local symbols and their
+     * visibility boundaries.
+     */
+    public interface LexicalUnit {
+        /**
+         * Declares a local symbol.
+         * @param symbol the symbol index in the scope
+         * @return true if declaration was successful, false if symbol was already declared
+         */
+        boolean declareSymbol(int symbol);
+        
+        /**
+         * Checks whether a symbol is declared in this lexical unit.
+         * @param symbol the symbol
+         * @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();
+    }
+    
     /**
      * Cleanup.
      * @param features the feature set to restore if any
@@ -88,6 +129,7 @@ public abstract class JexlParser extends StringParser {
         pragmas = null;
         loopCounts.clear();
         loopCount = 0;
+        blocks.clear();
     }
     /**
      * Utility function to create '.' separated string from a list of string.
@@ -153,15 +195,6 @@ public abstract class JexlParser extends StringParser {
     }
 
     /**
-     * Sets the frame to use by this parser.
-     * <p> This is used to allow parameters to be declared before parsing. </p>
-     * @param theFrame the register map
-     */
-    protected void setFrame(Scope theFrame) {
-        frame = theFrame;
-    }
-
-    /**
      * Gets the frame used by this parser.
      * <p> Since local variables create new symbols, it is important to
      * regain access after parsing to known which / how-many registers are needed. </p>
@@ -198,21 +231,82 @@ public abstract class JexlParser extends StringParser {
     }
 
     /**
+     * Gets the lexical unit currently used by this parser.
+     * @return the named register map
+     */
+    protected LexicalUnit getUnit() {
+        return block;
+    }
+
+    /**
+     * Pushes a new lexical unit.
+     * <p>The merge flag allows the for(...) and lamba(...) constructs to
+     * merge in the next block since their loop-variable/parameter spill in the
+     * same lexical unit as their first block.
+     * @param unit the new lexical unit
+     * @param merge whether the next unit merges in this one
+     */
+    protected void pushUnit(LexicalUnit unit, boolean merge) {
+        if (merge) {
+            mergeBlock = true;
+        } else if (mergeBlock) {
+            mergeBlock = false;
+            return;
+        } 
+        if (block != null) {
+            blocks.push(block);
+        }
+        block = unit;
+    }
+    
+    /**
+     * Pushes a block as new lexical unit.
+     * @param unit the lexical unit
+     */
+    protected void pushUnit(LexicalUnit unit) {
+        pushUnit(unit, false);
+    }
+
+    /**
+     * Restores the previous lexical unit.
+     * @param unit restores the previous lexical scope
+     */
+    protected void popUnit(LexicalUnit unit) {
+        if (block == unit){ 
+            if (!blocks.isEmpty()) {
+                block = blocks.pop();
+            } else {
+                block = null;
+            }
+            //unit.clearUnit();
+        }
+    }
+    
+    /**
      * Checks whether an identifier is a local variable or argument, ie a symbol, stored in a register.
      * @param identifier the identifier
-     * @param image      the identifier image
+     * @param name      the identifier name
      * @return the image
      */
-    protected String checkVariable(ASTIdentifier identifier, String image) {
+    protected String checkVariable(ASTIdentifier identifier, String name) {
         if (frame != null) {
-            Integer register = frame.getSymbol(image);
-            if (register != null) {
-                identifier.setSymbol(register.intValue(), image);
+            Integer symbol = frame.getSymbol(name);
+            if (symbol != null) {
+                // can not reuse a local as a global
+                if (!block.hasSymbol(symbol) && getFeatures().isLexical()) {
+                    throw new JexlException(identifier,  name + ": variable is not defined");
+                }
+                identifier.setSymbol(symbol, name);
             }
         }
-        return image;
+        return name;
     }
 
+    /**
+     * Whether a given variable name is allowed.
+     * @param image the name
+     * @return true if allowed, false if reserved
+     */
     protected boolean allowVariable(String image) {
         JexlFeatures features = getFeatures();
         if (!features.supportsLocalVar()) {
@@ -223,6 +317,27 @@ public abstract class JexlParser extends StringParser {
         }
         return true;
     }
+    
+    /**
+     * Declares a symbol.
+     * @param symbol the symbol index
+     * @return true if symbol can be declared in lexical scope, false (error) 
+     * if it is already declared
+     */
+    private boolean declareSymbol(int symbol) {
+        if (blocks != null) {
+            for(LexicalUnit lu : blocks) {
+                if (lu.hasSymbol(symbol)) {
+                    return false;
+                }
+                // stop at first new scope reset, aka lambda
+                if (lu instanceof ASTJexlLambda) {
+                    break;
+                }
+            }
+        }
+        return block.declareSymbol(symbol);
+    }
 
     /**
      * Declares a local variable.
@@ -231,15 +346,19 @@ public abstract class JexlParser extends StringParser {
      * @param token      the variable name toekn
      */
     protected void declareVariable(ASTVar var, Token token) {
-        String identifier = token.image;
-        if (!allowVariable(identifier)) {
+        String name = token.image;
+        if (!allowVariable(name)) {
             throwFeatureException(JexlFeatures.LOCAL_VAR, token);
         }
         if (frame == null) {
             frame = new Scope(null, (String[]) null);
         }
-        Integer register = frame.declareVariable(identifier);
-        var.setSymbol(register.intValue(), identifier);
+        int symbol = frame.declareVariable(name);
+        var.setSymbol(symbol, name);
+        // lexical feature error
+        if (!declareSymbol(symbol) && getFeatures().isLexical()) {
+            throw new JexlException(var,  name + ": variable is already declared");
+        }
     }
 
     /**
@@ -270,7 +389,13 @@ public abstract class JexlParser extends StringParser {
         if (frame == null) {
             frame = new Scope(null, (String[]) null);
         }
-        frame.declareParameter(identifier);
+        int symbol = frame.declareParameter(identifier);
+        // not sure how declaring a parameter could fail...
+        // lexical feature error
+        if (!declareSymbol(symbol) && getFeatures().isLexical()) {
+            JexlInfo xinfo = info.at(token.beginLine, token.beginColumn);
+            throw new JexlException(xinfo,  identifier + ": variable is already declared", null);
+        }
     }
 
     /**
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 4cc2426..cdb19ca 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -295,8 +295,12 @@ ASTJexlScript JexlScript(Scope frame) : {
     jjtThis.setScope(frame);
 }
 {
-   ( ( Statement() )*) <EOF>
    {
+        pushUnit(jjtThis, frame != null && frame.getArgCount() > 0);
+   }
+        ( ( Statement() )*) <EOF>
+   {
+        popUnit(jjtThis);
         return jjtThis.script();
    }
 }
@@ -304,9 +308,13 @@ ASTJexlScript JexlScript(Scope frame) : {
 ASTJexlScript JexlExpression(Scope frame) #JexlScript : {
     jjtThis.setScope(frame);
 }
-{
+{   
+   {
+        pushUnit(jjtThis, true);
+   }
    ( Expression() )? <EOF>
    {
+        popUnit(jjtThis);
         return jjtThis.script();
    }
 }
@@ -343,7 +351,7 @@ void Statement() #void : {}
 
 void Block() #Block : {}
 {
-    <LCURLY> ( Statement() )* <RCURLY>
+    <LCURLY> { pushUnit(jjtThis); } ( Statement() )* { popUnit(jjtThis); } <RCURLY>
 }
 
 
@@ -391,7 +399,7 @@ void Break() #Break : {
 
 void ForeachStatement() : {}
 {
-    <FOR> <LPAREN> ForEachVar() <COLON>  Expression() <RPAREN> { loopCount += 1; } (LOOKAHEAD(1) Block() | Statement()) { loopCount -= 1; }
+    { pushUnit(jjtThis, true); } <FOR> <LPAREN> ForEachVar() <COLON>  Expression() <RPAREN> { loopCount += 1; } (LOOKAHEAD(1) Block() | Statement()) { loopCount -= 1; popUnit(jjtThis); }
 }
 
 void ForEachVar() #Reference : {}
@@ -610,7 +618,6 @@ void UnaryExpression() #void : {}
     ValueExpression()
 }
 
-
 /***************************************
  *      Identifier & Literals
  ***************************************/
@@ -809,7 +816,7 @@ void Parameter() #void :
 
 void Parameters() #void : {}
 {
-     <LPAREN> [Parameter() (<COMMA> Parameter())* ] <RPAREN>
+     <LPAREN> [(<VAR>)? Parameter() (<COMMA> (<VAR>)? Parameter())* ] <RPAREN>
 }
 
 
@@ -827,11 +834,11 @@ void Lambda() #JexlLambda() :
    pushFrame();
 }
 {
-  <FUNCTION> Parameters() Block()
+  { pushUnit(jjtThis, true); } <FUNCTION> Parameters() Block() { popUnit(jjtThis); }
   |
-  Parameters() <LAMBDA> Block()
+  { pushUnit(jjtThis, true); } Parameters() <LAMBDA> Block() { popUnit(jjtThis); }
   |
-  Parameter() <LAMBDA> Block()
+  { pushUnit(jjtThis, true); } Parameter() <LAMBDA> Block() { popUnit(jjtThis); }
 }
 
 
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index 30ec007..24e4b2a 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -26,6 +26,12 @@
     </properties>
     <body>
         <release version="3.2" date="unreleased">
+            <action dev="henrib" type="fix" issue="JEXL-315" due-to="Mike Bartlett">
+               JxltEngine literal string strings ending in \ $ or # throw JxltEngine$Exception
+            </action>
+            <action dev="henrib" type="fix" issue="JEXL-314" due-to="Osy">
+               Comparison NULL values of variables NAME1.NAME2
+            </action>
             <action dev="henrib" type="fix" issue="JEXL-312">
                 @NoJexl fails to disallow method call
             </action>
@@ -35,6 +41,9 @@
             <action dev="henrib" type="fix" issue="JEXL-309">
                 Line numbers are not correct when template report errors
             </action>
+            <action dev="henrib" type="fix" issue="JEXL-307" due-to="Dmitri Blinov">
+                Variable redeclaration option
+            </action>
             <action dev="henrib" type="fix" issue="JEXL-306" due-to="Dmitri Blinov">
                 Ternary operator ? protects also its branches from resolution errors
             </action>
diff --git a/src/site/xdoc/reference/syntax.xml b/src/site/xdoc/reference/syntax.xml
index 400d9b2..e447787 100644
--- a/src/site/xdoc/reference/syntax.xml
+++ b/src/site/xdoc/reference/syntax.xml
@@ -132,6 +132,9 @@
                         a value of <code>42</code>.
                         <p>Pragma keys can be identifiers or antish names, pragma values can be literals (boolean, integer,
                             real, string, null, NaN) and antish names</p>
+                        <p>Although pragmas are statements, they are <em>not</em> evaluated at runtime; they are constants
+                            associated to the script after parsing. It is expected that user code accesses the pragma map from 
+                            scripts to alter some functions behavior.</p>
                     </td>
                 </tr>
                 <tr>
diff --git a/src/test/java/org/apache/commons/jexl3/AnnotationTest.java b/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
index b36cd24..c466153 100644
--- a/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
+++ b/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
@@ -77,38 +77,40 @@ public class AnnotationTest extends JexlTestCase {
     public class OptAnnotationContext extends JexlEvalContext implements JexlContext.AnnotationProcessor {
         @Override
         public Object processAnnotation(String name, Object[] args, Callable<Object> statement) throws Exception {
+            JexlOptions options = this.getEngineOptions();
             // transient side effect for strict
             if ("strict".equals(name)) {
                 boolean s = (Boolean) args[0];
-                boolean b = this.isStrict();
-                setStrict(s);
+                boolean b = options.isStrict();
+                options.setStrict(s);
                 Object r = statement.call();
-                setStrict(b);
+                options.setStrict(b);
                 return r;
             }
             // transient side effect for silent
             if ("silent".equals(name)) {
                 if (args == null || args.length == 0) {
-                    boolean b = this.isSilent();
+                    boolean b = options.isSilent();
                     try {
                         return statement.call();
                     } catch(JexlException xjexl) {
                         return null;
                     } finally {
-                        setSilent(b);
+                        options.setSilent(b);
                     }
                 } else {
                     boolean s = (Boolean) args[0];
-                    boolean b = this.isSilent();
-                    setSilent(s);
+                    boolean b = options.isSilent();
+                    options.setSilent(s);
+                    Assert.assertEquals(s, options.isSilent());
                     Object r = statement.call();
-                    setSilent(b);
+                    options.setSilent(b);
                     return r;
                 }
             }
             // durable side effect for scale
             if ("scale".equals(name)) {
-                this.setMathScale((Integer) args[0]);
+                options.setMathScale((Integer) args[0]);
                 return statement.call();
             }
             return statement.call();
@@ -118,8 +120,9 @@ public class AnnotationTest extends JexlTestCase {
     @Test
     public void testVarStmt() throws Exception {
         OptAnnotationContext jc = new OptAnnotationContext();
+        JexlOptions options = jc.getEngineOptions();
         JexlEngine jexl = new JexlBuilder().strict(true).silent(false).create();
-        jc.setOptions(jexl);
+        jc.getEngineOptions().setOptions(jexl);
         JexlScript e;
         Object r;
         e = jexl.createScript("(s, v)->{ @strict(s) @silent(v) var x = y ; 42; }");
@@ -159,12 +162,12 @@ public class AnnotationTest extends JexlTestCase {
             Assert.fail("should not have thrown");
         }
         //Assert.assertEquals(42, r);
-        Assert.assertTrue(jc.isStrict());
+        Assert.assertTrue(options.isStrict());
         e = jexl.createScript("@scale(5) 42;");
         r = e.execute(jc);
         Assert.assertEquals(42, r);
-        Assert.assertTrue(jc.isStrict());
-        Assert.assertEquals(5, jc.getArithmeticMathScale());
+        Assert.assertTrue(options.isStrict());
+        Assert.assertEquals(5, options.getMathScale());
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index 9ec7f75..1635bb8 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -54,7 +54,7 @@ public class ArithmeticTest extends JexlTestCase {
 
     @Test
     public void testUndefinedVar() throws Exception {
-        asserter.failExpression("objects[1].status", ".* undefined variable objects.*");
+        asserter.failExpression("objects[1].status", ".*variable 'objects' is undefined.*");
     }
 
     @Test
@@ -268,7 +268,8 @@ public class ArithmeticTest extends JexlTestCase {
     @Test
     public void testLongLiterals() throws Exception {
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrictArithmetic(true);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrictArithmetic(true);
         String stmt = "{a = 10L; b = 10l; c = 42.0D; d = 42.0d; e=56.3F; f=56.3f; g=63.5; h=0x10; i=010; j=0x10L; k=010l}";
         JexlScript expr = JEXL.createScript(stmt);
         /* Object value = */ expr.execute(ctxt);
@@ -288,7 +289,8 @@ public class ArithmeticTest extends JexlTestCase {
     @Test
     public void testBigLiteralValue() throws Exception {
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrictArithmetic(true);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrictArithmetic(true);
         JexlExpression e = JEXL.createExpression("9223372036854775806.5B");
         String res = String.valueOf(e.evaluate(ctxt));
         Assert.assertEquals("9223372036854775806.5", res);
@@ -311,7 +313,8 @@ public class ArithmeticTest extends JexlTestCase {
     @Test
     public void testBigLiterals() throws Exception {
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrictArithmetic(true);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrictArithmetic(true);
         String stmt = "{a = 10H; b = 10h; c = 42.0B; d = 42.0b;}";
         JexlScript expr = JEXL.createScript(stmt);
         /* Object value = */ expr.execute(ctxt);
@@ -325,7 +328,8 @@ public class ArithmeticTest extends JexlTestCase {
     @Test
     public void testBigExponentLiterals() throws Exception {
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrictArithmetic(true);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrictArithmetic(true);
         String stmt = "{a = 42.0e1B; b = 42.0E+2B; c = 42.0e-1B; d = 42.0E-2b; e=4242.4242e1b}";
         JexlScript expr = JEXL.createScript(stmt);
         /* Object value = */ expr.execute(ctxt);
@@ -340,7 +344,8 @@ public class ArithmeticTest extends JexlTestCase {
     @Test
     public void test2DoubleLiterals() throws Exception {
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrictArithmetic(true);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrictArithmetic(true);
         String stmt = "{a = 42.0e1D; b = 42.0E+2D; c = 42.0e-1d; d = 42.0E-2d; e=10e10; f= +1.e1; g=1e1; }";
         JexlScript expr = JEXL.createScript(stmt);
         /* Object value = */ expr.execute(ctxt);
@@ -363,6 +368,8 @@ public class ArithmeticTest extends JexlTestCase {
     public void testDivideByZero() throws Exception {
         Map<String, Object> vars = new HashMap<String, Object>();
         JexlEvalContext context = new JexlEvalContext(vars);
+        JexlOptions options = context.getEngineOptions();
+        options.setStrictArithmetic(true);
         vars.put("aByte", new Byte((byte) 1));
         vars.put("aShort", new Short((short) 2));
         vars.put("aInteger", new Integer(3));
@@ -393,7 +400,8 @@ public class ArithmeticTest extends JexlTestCase {
         // for non-silent, silent...
         for (int s = 0; s < 2; ++s) {
             boolean strict = Boolean.valueOf(s != 0);
-            context.setStrict(true, strict);
+            options.setStrict(true);
+            options.setStrictArithmetic(strict);
             int zthrow = 0;
             int zeval = 0;
             // for vars of all types...
@@ -581,14 +589,16 @@ public class ArithmeticTest extends JexlTestCase {
     public void testOption() throws Exception {
         Map<String, Object> vars = new HashMap<String, Object>();
         JexlEvalContext context = new JexlEvalContext(vars);
+        JexlOptions options = context.getEngineOptions();
+        options.setStrictArithmetic(true);
         JexlScript script = JEXL.createScript("0 + '1.2' ");
         Object result;
 
-        context.setStrictArithmetic(true);
+        options.setStrictArithmetic(true);
         result = script.execute(context);
         Assert.assertEquals("01.2", result);
 
-        context.setStrictArithmetic(false);
+        options.setStrictArithmetic(false);
         result = script.execute(context);
         Assert.assertEquals(1.2d, (Double) result, EPSILON);
     }
@@ -1284,7 +1294,8 @@ public class ArithmeticTest extends JexlTestCase {
     public void testCoerceInteger() throws Exception {
         JexlArithmetic ja = JEXL.getArithmetic();
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrictArithmetic(true);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrictArithmetic(true);
         String stmt = "a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H;";
         JexlScript expr = JEXL.createScript(stmt);
         /* Object value = */ expr.execute(ctxt);
@@ -1302,7 +1313,8 @@ public class ArithmeticTest extends JexlTestCase {
     public void testCoerceLong() throws Exception {
         JexlArithmetic ja = JEXL.getArithmetic();
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrictArithmetic(true);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrictArithmetic(true);
         String stmt = "a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H;";
         JexlScript expr = JEXL.createScript(stmt);
         /* Object value = */ expr.execute(ctxt);
@@ -1320,7 +1332,8 @@ public class ArithmeticTest extends JexlTestCase {
     public void testCoerceDouble() throws Exception {
         JexlArithmetic ja = JEXL.getArithmetic();
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrictArithmetic(true);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrictArithmetic(true);
         String stmt = "{a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H; }";
         JexlScript expr = JEXL.createScript(stmt);
         /* Object value = */ expr.execute(ctxt);
@@ -1338,7 +1351,8 @@ public class ArithmeticTest extends JexlTestCase {
     public void testCoerceBigInteger() throws Exception {
         JexlArithmetic ja = JEXL.getArithmetic();
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrictArithmetic(true);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrictArithmetic(true);
         String stmt = "{a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H; }";
         JexlScript expr = JEXL.createScript(stmt);
         /* Object value = */ expr.execute(ctxt);
@@ -1356,7 +1370,8 @@ public class ArithmeticTest extends JexlTestCase {
     public void testCoerceBigDecimal() throws Exception {
         JexlArithmetic ja = JEXL.getArithmetic();
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrictArithmetic(true);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrictArithmetic(true);
         String stmt = "{a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H; }";
         JexlScript expr = JEXL.createScript(stmt);
         /* Object value = */ expr.execute(ctxt);
diff --git a/src/test/java/org/apache/commons/jexl3/BitwiseOperatorTest.java b/src/test/java/org/apache/commons/jexl3/BitwiseOperatorTest.java
index 94ba69a..ad80adb 100644
--- a/src/test/java/org/apache/commons/jexl3/BitwiseOperatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/BitwiseOperatorTest.java
@@ -32,7 +32,7 @@ public class BitwiseOperatorTest extends JexlTestCase {
     @Before
     public void setUp() {
         asserter = new Asserter(JEXL);
-        asserter.setStrict(false);
+        asserter.setStrict(false, false);
     }
 
     /**
diff --git a/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java b/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
index 7a5e768..c241547 100644
--- a/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
@@ -279,8 +279,9 @@ public class ClassCreatorTest extends JexlTestCase {
         JexlExpression expr = jexl.createExpression("foo.value");
         JexlExpression newx = jexl.createExpression("foo = new(clazz)");
         JexlEvalContext context = new JexlEvalContext();
-        context.setStrict(false);
-        context.setSilent(true);
+        JexlOptions options = context.getEngineOptions();
+        options.setStrict(false);
+        options.setSilent(true);
 
         ClassCreator cctor = new ClassCreator(jexl, base);
         for (int i = 0; i < LOOPS && gced < 0; ++i) {
diff --git a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
index 2f744a2..49cabae 100644
--- a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
@@ -125,32 +125,33 @@ public class ExceptionTest extends JexlTestCase {
         JexlEngine jexl = createEngine(false);
         JexlExpression e = jexl.createExpression("c.e * 6");
         JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
         // ensure errors will throw
-        ctxt.setSilent(false);
+        options.setSilent(false);
         // make unknown vars throw
-        ctxt.setStrict(true);
+        options.setStrict(true);
         // empty cotext
         try {
             /* Object o = */ e.evaluate(ctxt);
             Assert.fail("c not defined as variable should throw");
         } catch (JexlException.Variable xjexl) {
             String msg = xjexl.getMessage();
-            Assert.assertTrue(msg.indexOf("variable c") > 0);
+            Assert.assertTrue(msg.indexOf("variable 'c.e'") > 0);
         }
 
         // disallow null operands
-        ctxt.setStrictArithmetic(true);
+        options.setStrictArithmetic(true);
         ctxt.set("c.e", null);
         try {
             /* Object o = */ e.evaluate(ctxt);
             Assert.fail("c.e as null operand should throw");
         } catch (JexlException.Variable xjexl) {
             String msg = xjexl.getMessage();
-            Assert.assertTrue(msg.indexOf("variable c.e") > 0);
+            Assert.assertTrue(msg.indexOf("variable 'c.e'") > 0);
         }
 
         // allow null operands
-        ctxt.setStrictArithmetic(false);
+        options.setStrictArithmetic(false);
         try {
             /* Object o = */ e.evaluate(ctxt);
 
@@ -166,7 +167,7 @@ public class ExceptionTest extends JexlTestCase {
             Assert.fail("c.e not accessible as property should throw");
         } catch (JexlException.Property xjexl) {
             String msg = xjexl.getMessage();
-            Assert.assertTrue(msg.indexOf("property e") > 0);
+            Assert.assertTrue(msg.indexOf("property 'e") > 0);
         }
     }
 
@@ -176,11 +177,12 @@ public class ExceptionTest extends JexlTestCase {
         JexlEngine jexl = createEngine(false);
         JexlScript e = jexl.createScript("(x)->{ x * 6 }");
         JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
         // ensure errors will throw
-        ctxt.setSilent(false);
+        options.setSilent(false);
         // make unknown vars throw
-        ctxt.setStrict(true);
-        ctxt.setStrictArithmetic(true);
+        options.setStrict(true);
+        options.setStrictArithmetic(true);
         // empty cotext
         try {
             /* Object o = */ e.execute(ctxt);
@@ -191,7 +193,7 @@ public class ExceptionTest extends JexlTestCase {
         }
 
         // allow null operands
-        ctxt.setStrictArithmetic(false);
+        options.setStrictArithmetic(false);
         try {
             Object o = e.execute(ctxt, (Object) null);
         } catch (JexlException.Variable xjexl) {
@@ -205,28 +207,29 @@ public class ExceptionTest extends JexlTestCase {
         JexlEngine jexl = createEngine(false);
         JexlExpression e = jexl.createExpression("c.e.foo()");
         JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
         // ensure errors will throw
-        ctxt.setSilent(false);
+        options.setSilent(false);
         // make unknown vars throw
-        ctxt.setStrict(true);
+        options.setStrict(true);
         // empty cotext
         try {
             /* Object o = */ e.evaluate(ctxt);
             Assert.fail("c not declared as variable should throw");
         } catch (JexlException.Variable xjexl) {
             String msg = xjexl.getMessage();
-            Assert.assertTrue(msg.indexOf("variable c") > 0);
+            Assert.assertTrue(msg.indexOf("variable 'c.e'") > 0);
         }
 
         // disallow null operands
-        ctxt.setStrictArithmetic(true);
+        options.setStrictArithmetic(true);
         ctxt.set("c.e", null);
         try {
             /* Object o = */ e.evaluate(ctxt);
             Assert.fail("c.e as null operand should throw");
         } catch (JexlException xjexl) {
             String msg = xjexl.getMessage();
-            Assert.assertTrue(msg.indexOf("variable c.e") > 0);
+            Assert.assertTrue(msg.indexOf("variable 'c.e'") > 0);
         }
     }
 
diff --git a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
index 3a64362..02256ad 100644
--- a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
@@ -261,7 +261,7 @@ public class FeaturesTest extends JexlTestCase {
         JexlFeatures f = new JexlFeatures().pragma(false);
         String[] scripts = new String[]{
             "#pragma foo 42",
-            "@two var x = 3; #pragma foo 'bar'"
+            "#pragma foo 'bar'\n@two var x = 3;"
         };
         checkFeature(f, scripts);
     }
diff --git a/src/test/java/org/apache/commons/jexl3/IfTest.java b/src/test/java/org/apache/commons/jexl3/IfTest.java
index 377cf82..2309705 100644
--- a/src/test/java/org/apache/commons/jexl3/IfTest.java
+++ b/src/test/java/org/apache/commons/jexl3/IfTest.java
@@ -212,13 +212,14 @@ public class IfTest extends JexlTestCase {
         JexlEngine jexl = JEXL;
 
         JexlEvalContext jc = new JexlEvalContext();
+        JexlOptions options = jc.getEngineOptions();
         JexlExpression e = jexl.createExpression("x.y.z = foo ?'bar':'quux'");
         Object o;
 
         // undefined foo
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -228,8 +229,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", null);
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -239,8 +240,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", Boolean.FALSE);
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -250,8 +251,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", Boolean.TRUE);
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be bar", "bar", o);
             o = jc.get("x.y.z");
@@ -269,14 +270,15 @@ public class IfTest extends JexlTestCase {
     @Test
     public void testTernaryShorthand() throws Exception {
         JexlEvalContext jc = new JexlEvalContext();
+        JexlOptions options = jc.getEngineOptions();
         JexlExpression e = JEXL.createExpression("x.y.z = foo?:'quux'");
         JexlExpression f = JEXL.createExpression("foo??'quux'");
         Object o;
 
         // undefined foo
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -288,8 +290,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", null);
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -301,8 +303,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", Boolean.FALSE);
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -314,8 +316,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", Double.NaN);
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -327,8 +329,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", "");
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -340,8 +342,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", "false");
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -353,8 +355,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", 0d);
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -366,8 +368,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", 0);
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
@@ -379,8 +381,8 @@ public class IfTest extends JexlTestCase {
         jc.set("foo", "bar");
 
         for (int l = 0; l < 4; ++l) {
-            jc.setStrict((l & 1) == 0);
-            jc.setSilent((l & 2) != 0);
+            options.setStrict((l & 1) == 0);
+            options.setSilent((l & 2) != 0);
             o = e.evaluate(jc);
             Assert.assertEquals("Should be bar", "bar", o);
             o = jc.get("x.y.z");
@@ -432,10 +434,11 @@ public class IfTest extends JexlTestCase {
     @Test
     public void testTernaryFail() throws Exception {
         JexlEvalContext jc = new JexlEvalContext();
+        JexlOptions options = jc.getEngineOptions();
         JexlExpression e = JEXL.createExpression("false ? bar : quux");
         Object o;
-        jc.setStrict(true);
-        jc.setSilent(false);
+        options.setStrict(true);
+        options.setSilent(false);
         try {
            o = e.evaluate(jc);
            Assert.fail("Should have failed");
diff --git a/src/test/java/org/apache/commons/jexl3/Issues100Test.java b/src/test/java/org/apache/commons/jexl3/Issues100Test.java
index 1ec0d73..fa7c576 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues100Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues100Test.java
@@ -114,7 +114,9 @@ public class Issues100Test extends JexlTestCase {
     @Test
     public void test106() throws Exception {
         JexlEvalContext context = new JexlEvalContext();
-        context.setStrict(true, true);
+        JexlOptions options = context.getEngineOptions();
+        options.setStrict(true);
+        options.setStrictArithmetic(true);
         context.set("a", new BigDecimal(1));
         context.set("b", new BigDecimal(3));
         JexlEngine jexl = new Engine();
@@ -124,8 +126,8 @@ public class Issues100Test extends JexlTestCase {
         } catch (JexlException xjexl) {
             Assert.fail("should not occur");
         }
-        context.setMathContext(MathContext.UNLIMITED);
-        context.setMathScale(2);
+        options.setMathContext(MathContext.UNLIMITED);
+        options.setMathScale(2);
         try {
             jexl.createExpression("a / b").evaluate(context);
             Assert.fail("should fail");
@@ -289,6 +291,7 @@ public class Issues100Test extends JexlTestCase {
         String expStr1 = "result == salary/month * work.percent/100.00";
         JexlExpression exp1 = jexlX.createExpression(expStr1);
         JexlEvalContext ctx = new JexlEvalContext();
+        JexlOptions options = ctx.getEngineOptions();
         ctx.set("result", new BigDecimal("9958.33"));
         ctx.set("salary", new BigDecimal("119500.00"));
         ctx.set("month", new BigDecimal("12.00"));
@@ -298,7 +301,7 @@ public class Issues100Test extends JexlTestCase {
         Assert.assertFalse((Boolean) exp1.evaluate(ctx));
 
         // will succeed with scale = 2
-        ctx.setMathScale(2);
+        options.setMathScale(2);
         Assert.assertTrue((Boolean) exp1.evaluate(ctx));
     }
 
diff --git a/src/test/java/org/apache/commons/jexl3/Issues200Test.java b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
index cc28fc3..71c0324 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues200Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
@@ -133,6 +133,7 @@ public class Issues200Test extends JexlTestCase {
     @Test
     public void test217() throws Exception {
         JexlEvalContext jc = new JexlEvalContext();
+        JexlOptions options = jc.getEngineOptions();
         jc.set("foo", new int[]{0, 1, 2, 42});
         JexlEngine jexl;
         JexlScript e;
@@ -144,7 +145,8 @@ public class Issues200Test extends JexlTestCase {
 
         // cache and fail?
         jc.set("foo", new int[]{0, 1});
-        jc.setStrict(true);
+        options.setStrict(true);
+        Assert.assertTrue(options.isStrict());
         try {
             r = e.execute(jc);
             Assert.fail("should have thrown an exception");
@@ -153,7 +155,7 @@ public class Issues200Test extends JexlTestCase {
             Assert.assertTrue(ArrayIndexOutOfBoundsException.class.equals(th.getClass()));
         }
         //
-        jc.setStrict(false);
+        options.setStrict(false);
         r = e.execute(jc);
         Assert.assertNull("oob adverted", r);
     }
@@ -815,7 +817,7 @@ public class Issues200Test extends JexlTestCase {
         JexlEngine jexl = new JexlBuilder().strict(true).create();
         String src;
         JexlScript script;
-        Object result;
+        Object result = null;
         // declared, not defined
         src = "x = 1; if (false) var x = 2; x";
         script = jexl.createScript(src);
diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
index 527d124..d661a4e 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
@@ -16,7 +16,11 @@
  */
 package org.apache.commons.jexl3;
 
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 import org.junit.Assert;
 import static org.junit.Assert.assertEquals;
 import org.junit.Test;
@@ -234,4 +238,83 @@ public class Issues300Test {
             Assert.assertEquals(4, xerror.getInfo().getLine());
         }
     }
+    
+    public static class VaContext extends MapContext {
+        VaContext(Map<String, Object> vars) {
+            super(vars);
+        }
+        public int cell(String... ms) {
+            return ms.length == 0 ? 0 : ms.length;
+        }
+        
+        public int cell(List<?> l, String...ms) {
+            return 42 + cell(ms);
+        }
+    }
+
+    @Test
+    public void test314() throws Exception {
+        JexlEngine jexl = new JexlBuilder().strict(true).create();
+        Map<String,Object> vars = new HashMap<String, Object>();
+        JexlContext ctxt = new VaContext(vars);
+        JexlScript script;
+        Object result;
+        script = jexl.createScript("cell()");
+        result = script.execute(ctxt);
+        Assert.assertEquals(0, result);
+        script = jexl.createScript("x.cell()", "x");
+        result = script.execute(ctxt, Arrays.asList(10, 20));
+        Assert.assertEquals(42, result);
+        
+        vars.put("TVALOGAR", null);
+        String jexlExp = "TVALOGAR==null?'SIMON':'SIMONAZO'";
+        script = jexl.createScript(jexlExp);
+        result = script.execute(ctxt);
+        Assert.assertEquals("SIMON", result);
+        
+        jexlExp = "TVALOGAR.PEPITO==null?'SIMON':'SIMONAZO'";
+        script = jexl.createScript(jexlExp);
+        
+        Map<String, Object> tva = new LinkedHashMap<String, Object>();
+        tva.put("PEPITO", null);
+        vars.put("TVALOGAR", tva);
+        result = script.execute(ctxt);
+        Assert.assertEquals("SIMON", result);
+        
+        vars.remove("TVALOGAR");
+        ctxt.set("TVALOGAR.PEPITO", null);
+        result = script.execute(ctxt);
+        Assert.assertEquals("SIMON", result);
+    }
+    
+    @Test
+    public void test315() throws Exception {
+        JexlEngine jexl = new JexlBuilder().strict(true).create();
+        Map<String,Object> vars = new HashMap<String, Object>();
+        JexlContext ctxt = new VaContext(vars);
+        JexlScript script;
+        Object result;
+        script = jexl.createScript("a?? 42 + 10", "a");
+        result = script.execute(ctxt, 32);
+        Assert.assertEquals(32, result);
+        result = script.execute(ctxt, (Object) null);
+        Assert.assertEquals(52, result);
+        script = jexl.createScript("- a??42 + +10", "a");
+        result = script.execute(ctxt, 32);
+        Assert.assertEquals(-32, result);
+        result = script.execute(ctxt, (Object) null);
+        Assert.assertEquals(52, result);
+        // long version of ternary
+        script = jexl.createScript("a? a : +42 + 10", "a");
+        result = script.execute(ctxt, 32);
+        Assert.assertEquals(32, result);
+        result = script.execute(ctxt, (Object) null);
+        Assert.assertEquals(52, result);
+        // short one, elvis, equivalent
+        script = jexl.createScript("a ?: +42 + 10", "a");
+        result = script.execute(ctxt, 32);
+        Assert.assertEquals(32, result);
+        result = script.execute(ctxt, (Object) null);
+        Assert.assertEquals(52, result);
+    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/IssuesTest.java b/src/test/java/org/apache/commons/jexl3/IssuesTest.java
index 7b8ebf8..bda7b35 100644
--- a/src/test/java/org/apache/commons/jexl3/IssuesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/IssuesTest.java
@@ -84,9 +84,10 @@ public class IssuesTest extends JexlTestCase {
     public void test48() throws Exception {
         JexlEngine jexl = new Engine();
         JexlEvalContext jc = new JexlEvalContext();
+        JexlOptions options = jc.getEngineOptions();
         // ensure errors will throw
-        jc.setStrict(true);
-        jc.setSilent(false);
+        options.setStrict(true);
+        options.setSilent(false);
         try {
             String jexlExp = "(foo.getInner().foo() eq true) and (foo.getInner().goo() = (foo.getInner().goo()+1-1))";
             JexlExpression e = jexl.createExpression(jexlExp);
@@ -106,8 +107,9 @@ public class IssuesTest extends JexlTestCase {
     public void test47() throws Exception {
         JexlEngine jexl = new Engine();
         JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
         // ensure errors will throw
-        ctxt.setSilent(false);
+        options.setSilent(false);
 
         JexlExpression expr = jexl.createExpression("true//false\n");
         Object value = expr.evaluate(ctxt);
@@ -131,7 +133,10 @@ public class IssuesTest extends JexlTestCase {
         // ensure errors will throw
         //jexl.setSilent(false);
         JexlEvalContext ctxt = new JexlEvalContext();
-        ctxt.setStrict(false);
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setOptions(jexl);
+        options.setStrict(false);
+        options.setStrictArithmetic(false);
         ctxt.set("ax", "ok");
 
         JxltEngine.Expression expr = uel.createExpression("${ax+(bx)}");
@@ -156,8 +161,10 @@ public class IssuesTest extends JexlTestCase {
     public void test40() throws Exception {
         JexlEngine jexl = new Engine();
         JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setOptions(jexl);
         // ensure errors will throw
-        ctxt.setSilent(false);
+        options.setSilent(false);
 
         ctxt.set("derived", new Derived());
 
@@ -198,9 +205,10 @@ public class IssuesTest extends JexlTestCase {
     public void test11() throws Exception {
         JexlEngine jexl = createEngine(false);
         JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
         // ensure errors will throw
-        ctxt.setSilent(false);
-        ctxt.setStrict(true);
+        options.setSilent(false);
+        options.setStrict(true);
 
         ctxt.set("a", null);
 
@@ -228,8 +236,9 @@ public class IssuesTest extends JexlTestCase {
         JexlEngine jexl = createEngine(false);
         MapContext vars = new MapContext();
         JexlEvalContext ctxt = new JexlEvalContext(vars);
-        ctxt.setStrict(true);
-        ctxt.setSilent(true);// to avoid throwing JexlException on null method call
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setStrict(true);
+        options.setSilent(true);// to avoid throwing JexlException on null method call
 
         JexlScript jscript;
 
@@ -259,8 +268,9 @@ public class IssuesTest extends JexlTestCase {
     public void test87() throws Exception {
         JexlEngine jexl = createEngine(false);
         JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
         // ensure errors will throw
-        ctxt.setSilent(false);
+        options.setSilent(false);
         JexlExpression divide = jexl.createExpression("l / r");
         JexlExpression modulo = jexl.createExpression("l % r");
 
@@ -280,8 +290,9 @@ public class IssuesTest extends JexlTestCase {
     public void test90() throws Exception {
         JexlEngine jexl = createEngine(false);
         JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
         // ensure errors will throw
-        ctxt.setSilent(false);
+        options.setSilent(false);
         // ';' is necessary between expressions
         String[] fexprs = {
             "a=3 b=4",
@@ -320,8 +331,9 @@ public class IssuesTest extends JexlTestCase {
     public void test44() throws Exception {
         JexlEngine jexl = createEngine(false);
         JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
         // ensure errors will throw
-        ctxt.setSilent(false);
+        options.setSilent(false);
         JexlScript script;
         script = jexl.createScript("'hello world!'//commented");
         Assert.assertEquals("hello world!", script.execute(ctxt));
@@ -337,8 +349,9 @@ public class IssuesTest extends JexlTestCase {
     public void test97() throws Exception {
         JexlEngine jexl = createEngine(false);
         JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
         // ensure errors will throw
-        ctxt.setSilent(false);
+        options.setSilent(false);
         for (char v = 'a'; v <= 'z'; ++v) {
             ctxt.set(Character.toString(v), 10);
         }
diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
index cc2b8d5..ca085fc 100644
--- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
@@ -389,19 +389,20 @@ public class JXLTTest extends JexlTestCase {
     @Test
     public void testCharAtBug() throws Exception {
         context.set("foo", "abcdef");
+        JexlOptions options = context.getEngineOptions();
         JxltEngine.Expression expr = JXLT.createExpression("${foo.substring(2,4)/*comment*/}");
         Object o = expr.evaluate(context);
         Assert.assertEquals("cd", o);
 
         context.set("bar", "foo");
         try {
-            context.setSilent(true);
+            options.setSilent(true);
             expr = JXLT.createExpression("#{${bar}+'.charAt(-2)'}");
             expr = expr.prepare(context);
             o = expr.evaluate(context);
             Assert.assertEquals(null, o);
         } finally {
-            context.setSilent(false);
+            options.setSilent(false);
         }
 
     }
@@ -861,4 +862,33 @@ public class JXLTTest extends JexlTestCase {
         String output = strw.toString();
         Assert.assertEquals("<p>Universe 42</p>\n", output);
     }
+    
+    @Test
+    public void test315() throws Exception {
+        String s315;
+        StringWriter strw;
+        JxltEngine.Template t315;
+        String output;
+        
+        s315 = "<report/>$";
+        t315 = JXLT.createTemplate("$$", new StringReader(s315));
+        strw = new StringWriter();
+        t315.evaluate(context, strw);
+        output = strw.toString();
+        Assert.assertEquals(s315, output);
+        
+        s315 = "<foo/>#";
+        t315 = JXLT.createTemplate("$$", new StringReader(s315));
+         strw = new StringWriter();
+        t315.evaluate(context, strw);
+        output = strw.toString();
+        Assert.assertEquals(s315, output);
+        
+        s315 = "<bar/>\\";
+        t315 = JXLT.createTemplate("$$", new StringReader(s315));
+        strw = new StringWriter();
+        t315.evaluate(context, strw);
+        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 4a64f07..55fdcad 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
@@ -16,10 +16,10 @@
  */
 package org.apache.commons.jexl3;
 
-import java.math.MathContext;
-import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.Map;
+import org.apache.commons.jexl3.annotations.NoJexl;
+import org.apache.commons.jexl3.internal.Options;
 
 /**
  * A JEXL evaluation environment wrapping variables, namespace and options.
@@ -27,44 +27,33 @@ import java.util.Map;
 public class JexlEvalContext implements
        JexlContext,
        JexlContext.NamespaceResolver,
-       JexlEngine.Options {
+       JexlContext.OptionsHandle {
     /** The marker for the empty vars. */
     private static final Map<String,Object> EMPTY_MAP = Collections.<String,Object>emptyMap();
     /** The variables.*/
     private final JexlContext vars;
     /** The namespace. */
-    private final JexlContext.NamespaceResolver ns;
-    /** Whether the engine should be silent. */
-    private Boolean silent = null;
-    /** Whether the engine should be strict. */
-    private Boolean strict = null;
-    /** Whether the engine should be cancellable. */
-    private Boolean cancellable = null;
-    /** Whether the arithmetic should be strict. */
-    private Boolean mathStrict = null;
-    /** The math scale the arithmetic should use. */
-    private int mathScale = Integer.MIN_VALUE;
-    /** The math context the arithmetic should use. */
-    private MathContext mathContext = null;
+    private final JexlContext.NamespaceResolver ns; 
+    /** The options. */
+    private final JexlOptions options = new Options();
 
+    
+    
     /**
      * Default constructor.
      */
+    @NoJexl
     public JexlEvalContext() {
         this(EMPTY_MAP);
     }
-
-    @Override
-    public Charset getCharset() {
-        return Charset.defaultCharset();
-    }
-
+    
     /**
      * Creates an evaluation environment wrapping an existing user provided vars.
      * <p>The supplied vars should be null only in derived classes that override the get/set/has methods.
      * For a default vars context with a code supplied vars, use the default no-parameter contructor.</p>
      * @param map the variables map
      */
+    @NoJexl
     public JexlEvalContext(Map<String, Object> map) {
         this.vars = map == EMPTY_MAP ? new MapContext() : new MapContext(map);
         this.ns = null;
@@ -74,6 +63,7 @@ public class JexlEvalContext implements
      * Creates an evaluation environment from a context.
      * @param context the context (may be null, implies readonly)
      */
+    @NoJexl
     public JexlEvalContext(JexlContext context) {
         this(context, context instanceof JexlContext.NamespaceResolver? (JexlContext.NamespaceResolver) context : null);
     }
@@ -83,148 +73,40 @@ public class JexlEvalContext implements
      * @param context the context (may be null, implies readonly)
      * @param namespace the namespace (may be null, implies empty namespace)
      */
+    @NoJexl
     public JexlEvalContext(JexlContext context, JexlContext.NamespaceResolver namespace) {
         this.vars = context != null? context : JexlEngine.EMPTY_CONTEXT;
         this.ns = namespace != null? namespace : JexlEngine.EMPTY_NS;
     }
 
     @Override
+    @NoJexl
     public boolean has(String name) {
         return vars.has(name);
     }
 
     @Override
+    @NoJexl
     public Object get(String name) {
         return vars.get(name);
     }
 
     @Override
+    @NoJexl
     public void set(String name, Object value) {
         vars.set(name, value);
     }
 
     @Override
+    @NoJexl
     public Object resolveNamespace(String name) {
         return ns != null? ns.resolveNamespace(name) : null;
     }
 
-    /**
-     * Clear all options.
-     */
-    public void clearOptions() {
-        silent = null;
-        strict = null;
-        cancellable = null;
-        mathScale = -1;
-        mathContext = null;
-    }
-
-    /**
-     * Set options from engine.
-     * @param jexl the engine
-     */
-    public void setOptions(JexlEngine jexl) {
-        silent = jexl.isSilent();
-        strict = jexl.isStrict();
-        mathScale = jexl.getArithmetic().getMathScale();
-        mathContext = jexl.getArithmetic().getMathContext();
-    }
-
-    /**
-     * Sets whether the engine will throw JexlException during evaluation when an error is triggered.
-     * @param s true means no JexlException will occur, false allows them
-     */
-    public void setSilent(boolean s) {
-        this.silent = s ? Boolean.TRUE : Boolean.FALSE;
-    }
-
-    @Override
-    public Boolean isSilent() {
-        return this.silent;
-    }
-
-    /**
-     * Sets whether the engine will throw JexlException.Cancel during evaluation when interrupted.
-     * @param s true means JexlException.Cancel will be thrown, false implies null will be returned
-     */
-    public void setCancellable(boolean c) {
-        this.cancellable = c ? Boolean.TRUE : Boolean.FALSE;
-    }
-
     @Override
-    public Boolean isCancellable() {
-        return this.cancellable;
-    }
-
-    /**
-     * Sets the engine and arithmetic strict flags in one call.
-     * @param se the engine strict flag
-     * @param sa the arithmetic strict flag
-     */
-    public void setStrict(Boolean se, Boolean sa) {
-        this.strict = se == null? null : se ? Boolean.TRUE : Boolean.FALSE;
-        this.mathStrict = sa == null? null : sa ? Boolean.TRUE : Boolean.FALSE;
-    }
-
-    /**
-     * Sets whether the engine will consider unknown variables, methods and constructors as errors or evaluates them
-     * as null.
-     * @param se true means strict error reporting, false allows mentioned conditions to be evaluated as null
-     */
-    public void setStrict(boolean se) {
-        setStrict(se, se);
-    }
-
-    @Override
-    public Boolean isStrict() {
-        if (strict == null) {
-            return null;
-        } else {
-            return strict.booleanValue() ? Boolean.TRUE : Boolean.FALSE;
-        }
-    }
-
-    /**
-     * Sets whether the arithmetic will consider null arguments as errors during evaluation.
-     * @param s true means strict error reporting, false allows mentioned conditions to be evaluated as 0
-     */
-    public void setStrictArithmetic(boolean s) {
-        this.mathStrict = s ? Boolean.TRUE : Boolean.FALSE;
-    }
-
-    @Override
-    public Boolean isStrictArithmetic() {
-        if (mathStrict == null) {
-            return null;
-        } else {
-            return mathStrict.booleanValue() ? Boolean.TRUE : Boolean.FALSE;
-        }
-    }
-
-    @Override
-    public MathContext getArithmeticMathContext() {
-        return mathContext;
-    }
-
-    /**
-     * Sets the {@link MathContext} to use by the {@link JexlArithmetic} during evaluation.
-     * @param mc the math context
-     */
-    public void setMathContext(MathContext mc) {
-        mathContext = mc;
-    }
-
-    @Override
-    public int getArithmeticMathScale() {
-        return mathScale;
-    }
-
-    /**
-     * Sets the math scale to use to use by the {@link JexlArithmetic} during evaluation.
-     * @param scale the math scale
-     */
-    public void setMathScale(int scale) {
-        mathScale = scale;
+    @NoJexl
+    public JexlOptions getEngineOptions() {
+        return options;
     }
 
 }
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTest.java b/src/test/java/org/apache/commons/jexl3/JexlTest.java
index 8db318b..e8bd57a 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTest.java
@@ -155,7 +155,8 @@ public class JexlTest extends JexlTestCase {
     @Test
     public void testEmpty() throws Exception {
         JexlEvalContext jc = new JexlEvalContext();
-        jc.setStrict(false);
+        JexlOptions options = jc.getEngineOptions();
+        options.setStrict(false);
         jc.set("string", "");
         jc.set("array", new Object[0]);
         jc.set("map", new HashMap<Object, Object>());
@@ -179,7 +180,8 @@ public class JexlTest extends JexlTestCase {
     @Test
     public void testSize() throws Exception {
         JexlEvalContext jc = new JexlEvalContext();
-        jc.setStrict(false);
+        JexlOptions options = jc.getEngineOptions();
+        options.setStrict(false);
         jc.set("s", "five!");
         jc.set("array", new Object[5]);
 
@@ -275,7 +277,9 @@ public class JexlTest extends JexlTestCase {
     @Test
     public void testCalculations() throws Exception {
         JexlEvalContext jc = new JexlEvalContext();
-        jc.setStrict(false);
+        JexlOptions options = jc.getEngineOptions();
+        options.setStrict(false);
+        options.setStrictArithmetic(false);
 
         /*
          * test to ensure new string cat works
@@ -301,6 +305,7 @@ public class JexlTest extends JexlTestCase {
     @Test
     public void testConditions() throws Exception {
         JexlEvalContext jc = new JexlEvalContext();
+        JexlOptions options = jc.getEngineOptions();
         jc.set("foo", new Integer(2));
         jc.set("aFloat", new Float(1));
         jc.set("aDouble", new Double(2));
@@ -331,11 +336,11 @@ public class JexlTest extends JexlTestCase {
         assertExpression(jc, "aBool == 'false'", Boolean.FALSE);
         assertExpression(jc, "aBool != 'false'", Boolean.TRUE);
         // test null and boolean
-        jc.setStrict(false);
+        options.setStrict(false);
         assertExpression(jc, "aBool == notThere", Boolean.FALSE);
         assertExpression(jc, "aBool != notThere", Boolean.TRUE);
         // anything and string as a string comparison
-        jc.setStrict(true);
+        options.setStrict(true);
         assertExpression(jc, "aBuffer == 'abc'", Boolean.TRUE);
         assertExpression(jc, "aBuffer != 'abc'", Boolean.FALSE);
         // arbitrary equals
@@ -409,7 +414,8 @@ public class JexlTest extends JexlTestCase {
     @Test
     public void testNull() throws Exception {
         JexlEvalContext jc = new JexlEvalContext();
-        jc.setStrict(false);
+        JexlOptions options = jc.getEngineOptions();
+        options.setStrict(false);
         jc.set("bar", new Integer(2));
 
         assertExpression(jc, "empty foo", Boolean.TRUE);
@@ -541,7 +547,8 @@ public class JexlTest extends JexlTestCase {
     @Test
     public void testCharAtBug() throws Exception {
         JexlEvalContext jc = new JexlEvalContext();
-        jc.setSilent(true);
+        JexlOptions options = jc.getEngineOptions();
+        options.setSilent(true);
 
         jc.set("foo", "abcdef");
 
diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
new file mode 100644
index 0000000..3520c16
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test cases for lexical option and feature.
+ */
+public class LexicalTest {
+   
+    @Test
+    public void testLexical0a() throws Exception {
+        runLexical0(false);
+    }
+
+    @Test
+    public void testLexical0b() throws Exception {
+        runLexical0(true);
+    }
+
+    void runLexical0(boolean feature) throws Exception {
+        JexlFeatures f = new JexlFeatures();
+        f.lexical(feature);
+        JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
+        JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
+        // ensure errors will throw
+        options.setLexical(true);
+        JexlScript script;
+        try {
+            script = jexl.createScript("var x = 0; var x = 1;");
+            if (!feature) {
+                script.execute(ctxt);
+            }
+            Assert.fail();
+        } catch (JexlException xany) {
+            String ww = xany.toString();
+        }
+        try {
+            script = jexl.createScript("var x = 0; for(var y : null) { var y = 1;");
+            if (!feature) {
+                script.execute(ctxt);
+            }
+            Assert.fail();
+        } catch (JexlException xany) {
+            String ww = xany.toString();
+        }
+        try {
+            script = jexl.createScript("var x = 0; for(var x : null) {};");
+            if (!feature) {
+                script.execute(ctxt);
+            }
+            Assert.fail();
+        } catch (JexlException xany) {
+            String ww = xany.toString();
+        }
+        try {
+            script = jexl.createScript("(x)->{ var x = 0; x; }");
+            if (!feature) {
+                script.execute(ctxt);
+            }
+            Assert.fail();
+        } catch (JexlException xany) {
+            String ww = xany.toString();
+        }
+        try {
+            script = jexl.createScript("var x; if (true) { if (true) { var x = 0; x; } }");
+            if (!feature) {
+                script.execute(ctxt);
+            }
+            Assert.fail();
+        } catch (JexlException xany) {
+            String ww = xany.toString();
+        }
+        try {
+            script = jexl.createScript("if (a) { var y = (x)->{ var x = 0; x; }; y(2) }", "a");
+            if (!feature) {
+                script.execute(ctxt);
+            }
+            Assert.fail();
+        } catch (JexlException xany) {
+            String ww = xany.toString();
+        }
+        try {
+            script = jexl.createScript("(x)->{ for(var x : null) { x; } }");
+            if (!feature) {
+                script.execute(ctxt, 42);
+            }
+            Assert.fail();
+        } catch (JexlException xany) {
+            String ww = xany.toString();
+        }
+        // no fail
+        script = jexl.createScript("var x = 32; (()->{ for(var x : null) { x; }})();");
+        if (!feature) {
+            script.execute(ctxt, 42);
+        }
+    }
+        
+    @Test
+    public void testLexical1a() throws Exception {
+        runLexical1(false);
+    }
+
+    @Test
+    public void testLexical1b() throws Exception {
+        runLexical1(true);
+    }
+
+    void runLexical1(boolean shade) throws Exception {
+        JexlEngine jexl = new JexlBuilder().strict(true).create();
+        JexlEvalContext ctxt = new JexlEvalContext();
+        Object result;
+        ctxt.set("x", 4242);
+        JexlOptions options = ctxt.getEngineOptions();
+        // ensure errors will throw
+        options.setLexical(true);
+        options.setLexicalShade(shade);
+        JexlScript script;
+        try {
+            // if local shade, x is undefined
+            script = jexl.createScript("{ var x = 0; } x");
+            script.execute(ctxt);
+            if (shade) {
+                Assert.fail("local shade means 'x' should be undefined");
+            }
+        } catch (JexlException xany) {
+            if (!shade) {
+                throw xany;
+            }
+        }
+        try {
+            // if local shade, x = 42 is undefined
+            script = jexl.createScript("{ var x = 0; } x = 42");
+            script.execute(ctxt);
+            if (shade) {
+                Assert.fail("local shade means 'x = 42' should be undefined");
+            }
+        } catch (JexlException xany) {
+            if (!shade) {
+                throw xany;
+            }
+        }       
+        try {
+            // if local shade, x = 42 is undefined
+            script = jexl.createScript("{ var x = 0; } y = 42");
+            script.execute(ctxt);
+            if (shade) {
+                Assert.fail("local shade means 'y = 42' should be undefined (y is undefined)");
+            }
+        } catch (JexlException xany) {
+            if (!shade) {
+                throw xany;
+            }
+        }
+        // no fail
+        script = jexl.createScript("var x = 32; (()->{ for(var x : null) { x; }})();");
+        //if (!feature) {
+            script.execute(ctxt, 42);
+        //}
+        // y being defined as global
+        ctxt.set("y", 4242);
+        try {
+            // if no shade and global y being defined, 
+            script = jexl.createScript("{ var y = 0; } y = 42");
+            result = script.execute(ctxt);
+            if (!shade) {
+                Assert.assertEquals(42, result);
+            } else {
+                Assert.fail("local shade means 'y = 42' should be undefined");
+            }
+        } catch (JexlException xany) {
+            if (!shade) {
+                throw xany;
+            }
+        }  
+    }
+        
+    @Test
+    public void testLexical1() throws Exception {
+        JexlEngine jexl = new JexlBuilder().strict(true).create();
+        JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
+        // ensure errors will throw
+        options.setLexical(true);
+        JexlScript script;
+        Object result;
+        
+        script = jexl.createScript("var x = 0; for(var y : [1]) { var x = 42; return x; };");
+        try {
+        result = script.execute(ctxt);
+        //Assert.assertEquals(42, result);
+            Assert.fail();
+        } catch (JexlException xany) {
+            String ww = xany.toString();
+        }
+        
+        try {
+            script = jexl.createScript("(x)->{ if (x) { var x = 7 * (x + x); x; } }");
+            result = script.execute(ctxt, 3);
+            Assert.fail();
+        } catch (JexlException xany) {
+            String ww = xany.toString();
+        }
+        
+        script = jexl.createScript("{ var x = 0; } var x = 42; x");
+        result = script.execute(ctxt, 21);
+        Assert.assertEquals(42, result); 
+        
+    }
+    
+}
diff --git a/src/test/java/org/apache/commons/jexl3/PragmaTest.java b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
index 7dbecbb..2e549c8 100644
--- a/src/test/java/org/apache/commons/jexl3/PragmaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.jexl3;
 
+import java.math.MathContext;
+import java.nio.charset.Charset;
 import java.util.Map;
 import org.junit.Assert;
 import org.junit.Test;
@@ -58,5 +60,44 @@ public class PragmaTest extends JexlTestCase {
         Assert.assertEquals(1, pragmas.get("one"));
         Assert.assertEquals("truth", pragmas.get("the.very.hard"));
     }
+    
+    public static class SafeContext extends JexlEvalContext {
+        // @Override
+        public void processPragmas(Map<String, Object> pragmas) {
+            if (pragmas != null && !pragmas.isEmpty()) {
+                JexlOptions options = getEngineOptions();
+                for (Map.Entry<String, Object> pragma : pragmas.entrySet()) {
+                    String key = pragma.getKey();
+                    Object value = pragma.getValue();
+                    if ("jexl.safe".equals(key) && value instanceof Boolean) {
+                        options.setSafe(((Boolean) value).booleanValue());
+                    } else if ("jexl.strict".equals(key) && value instanceof Boolean) {
+                        options.setStrict(((Boolean) value).booleanValue());
+                    } else if ("jexl.silent".equals(key) && value instanceof Boolean) {
+                        options.setSilent(((Boolean) value).booleanValue());
+                    }
+                }
+            }
+        }
+    }
 
+    @Test
+    @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
+    public void testSafePragma() throws Exception {
+        SafeContext jc = new SafeContext();
+        jc.set("foo", null);
+        JexlScript script = JEXL.createScript("#pragma jexl.safe true\nfoo.bar;");
+        Assert.assertTrue(script != null);
+        jc.processPragmas(script.getPragmas());
+        Object result = script.execute(jc);
+        Assert.assertNull(result);
+        jc = new SafeContext();
+        jc.set("foo", null);
+        try {
+            result = script.execute(jc);
+            Assert.fail("should have thrown");
+        } catch (JexlException xvar) {
+            // ok, expected
+        }
+    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java b/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java
index 97fe8c1..7b094e4 100644
--- a/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java
+++ b/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java
@@ -16,30 +16,30 @@
  */
 package org.apache.commons.jexl3;
 
-import java.math.MathContext;
-import java.nio.charset.Charset;
+import org.apache.commons.jexl3.annotations.NoJexl;
 
 /**
  * A readonly context wrapper.
  * @since 3.0
  */
-public final class ReadonlyContext implements JexlContext, JexlEngine.Options {
+public final class ReadonlyContext implements JexlContext, JexlContext.OptionsHandle {
     /** The wrapped context. */
     private final JexlContext wrapped;
     /** The wrapped engine options. */
-    private final JexlEngine.Options options;
+    private final JexlOptions options;
 
     /**
      * Creates a new readonly context.
      * @param context the wrapped context
      * @param eopts the engine evaluation options
      */
-    public ReadonlyContext(JexlContext context, JexlEngine.Options eopts) {
+    public ReadonlyContext(JexlContext context, JexlOptions eopts) {
         wrapped = context;
         options = eopts;
     }
 
     @Override
+    @NoJexl
     public Object get(String name) {
         return wrapped.get(name);
     }
@@ -50,47 +50,20 @@ public final class ReadonlyContext implements JexlContext, JexlEngine.Options {
      * @param value the unused variable value
      */
     @Override
+    @NoJexl
     public void set(String name, Object value) {
         throw new UnsupportedOperationException("Not supported.");
     }
 
     @Override
+    @NoJexl
     public boolean has(String name) {
         return wrapped.has(name);
     }
 
     @Override
-    public Boolean isSilent() {
-        return options == null? null : options.isSilent();
-    }
-
-    @Override
-    public Boolean isStrict() {
-        return options == null? null : options.isStrict();
-    }
-
-    @Override
-    public Boolean isCancellable() {
-        return options == null? null : options.isCancellable();
-    }
-
-    @Override
-    public Boolean isStrictArithmetic() {
-        return options == null? null : options.isStrict();
-    }
-
-    @Override
-    public MathContext getArithmeticMathContext() {
-        return options == null? null : options.getArithmeticMathContext();
-    }
-
-    @Override
-    public int getArithmeticMathScale() {
-        return options == null? -1 : options.getArithmeticMathScale();
-    }
-
-    @Override
-    public Charset getCharset() {
-        return options == null? Charset.defaultCharset() : options.getCharset();
+    @NoJexl
+    public JexlOptions getEngineOptions() {
+        return options;
     }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/VarTest.java b/src/test/java/org/apache/commons/jexl3/VarTest.java
index f0f3126..23982b8 100644
--- a/src/test/java/org/apache/commons/jexl3/VarTest.java
+++ b/src/test/java/org/apache/commons/jexl3/VarTest.java
@@ -41,9 +41,10 @@ public class VarTest extends JexlTestCase {
     @Test
     public void testStrict() throws Exception {
         JexlEvalContext env = new JexlEvalContext();
-        JexlContext ctxt = new ReadonlyContext(env, env);
-        env.setStrict(true);
-        env.setSilent(false);
+        JexlOptions options = env.getEngineOptions();
+        JexlContext ctxt = new ReadonlyContext(env, options);
+        options.setStrict(true);
+        options.setSilent(false);
         JexlScript e;
 
         e = JEXL.createScript("x");
diff --git a/src/test/java/org/apache/commons/jexl3/junit/Asserter.java b/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
index 93f2c70..a68d64b 100644
--- a/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
+++ b/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
@@ -72,19 +72,16 @@ public class Asserter extends Assert {
     }
 
     public void setStrict(boolean s) {
-        context.setStrict(s, s);
+        context.getEngineOptions().setStrict(s);
     }
 
     public void setStrict(boolean es, boolean as) {
-        context.setStrict(es, as);
+        context.getEngineOptions().setStrict(es);
+        context.getEngineOptions().setStrictArithmetic(as);
     }
 
     public void setSilent(boolean silent) {
-        context.setSilent(silent);
-    }
-
-    public void clearOptions() {
-        context.clearOptions();
+        context.getEngineOptions().setSilent(silent);
     }
 
     /**