Merge branch 'pr/8'
diff --git a/README.md b/README.md
index f30cd0d..14cb747 100644
--- a/README.md
+++ b/README.md
@@ -14,90 +14,130 @@
  See the License for the specific language governing permissions and

  limitations under the License.

 -->

-<!---

- +======================================================================+

- |****                                                              ****|

- |****      THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN      ****|

- |****                    DO NOT EDIT DIRECTLY                      ****|

- |****                                                              ****|

- +======================================================================+

- | TEMPLATE FILE: readme-md-template.md                                 |

- | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |

- +======================================================================+

- |                                                                      |

- | 1) Re-generate using: mvn commons:readme-md                          |

- |                                                                      |

- | 2) Set the following properties in the component's pom:              |

- |    - commons.componentid (required, alphabetic, lower case)          |

- |    - commons.release.version (required)                              |

- |                                                                      |

- | 3) Example Properties                                                |

- |                                                                      |

- |  <properties>                                                        |

- |    <commons.componentid>math</commons.componentid>                   |

- |    <commons.release.version>1.2</commons.release.version>            |

- |  </properties>                                                       |

- |                                                                      |

- +======================================================================+

---->

-Apache Commons JEXL

-===================

+Apache Commons JEXL Pro

+=======================

 

-[![Build Status](https://travis-ci.org/apache/commons-jexl.svg?branch=trunk)](https://travis-ci.org/apache/commons-jexl)

-[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.commons/commons-jexl3/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.apache.commons/commons-jexl3/)

+The Apache Commons JEXL Pro library is an experimental fork of the The Apache Commons JEXL library.

 

-The Apache Commons JEXL library is an implementation of the JSTL Expression Language with extensions.

+Idea of the fork

+----------------

+The fork is intended to be source compatible with the latest JEXL version (3.2-SNAPSHOT), but provides some 

+enhancements and changes to the capabilities of the scripting language. 

 

-Documentation

--------------

+I have no intention of promoting this fork as an alternative implementation, and I would be happy to have all 

+the changes to be backported to the base JEXL library one day, but the decision whether these changes are the ones 

+the JEXL community would benefit from remains at the descretion of the Apache JEXL team.

 

-More information can be found on the [Apache Commons JEXL homepage](https://commons.apache.org/proper/commons-jexl).

-The [JavaDoc](https://commons.apache.org/proper/commons-jexl/javadocs/api-release) can be browsed.

-Questions related to the usage of Apache Commons JEXL should be posted to the [user mailing list][ml].

+Language Compatibility 

+----------------------

+The library tends to maintain as much syntax compatibility with the original syntax as possible, but there are

+some changes that may break your existing scripts. The main reason for this comes from the introduction of the new 

+reserved words to support new syntax constructs, so your variables may no longer be named by one of those that were introduced. 

+There are also some minor tweaks to the original syntax in order to clarify language structure and align language 

+constructs with other popular scripting languages, to minimize the learning curve. 

+These changes are all reflected in the documentation, but the breef summary is given below.

 

-Where can I get the latest release?

------------------------------------

-You can download source and binaries from our [download page](https://commons.apache.org/proper/commons-jexl/download_jexl.cgi).

+Incompatible changes

+--------------------

++ Java 8 is the new minimum supported version 

 

-Alternatively you can pull it from the central Maven repositories:

++ New reserved words are introduced. Those are:

+  `switch` `case` `default` `try` `catch` `finally` `throw` `synchronized` `this` `instanceof` `in` `remove`.

+  You may not longer use them as the names of the variables and methods. The exception is made to the `remove` identifer,

+  as it is still may be used in method invocations.

 

-```xml

-<dependency>

-  <groupId>org.apache.commons</groupId>

-  <artifactId>commons-jexl3</artifactId>

-  <version>3.1</version>

-</dependency>

-```

++ Pragmas can only be defined at the beginning of the script. The reason is the pragmas are not executable constructs, 

+  so it is pointless and misleading to have them incorporated in flow-control structures somewhere in the middle.

 

-Contributing

++ Literal `null` can no longer have any properies, so it is forbidden to use it in expressions like `null.prop`.

+  If, for some reason, you still want to do this, use parentheses like `(null).prop`.

+

++ Precedence of the `range` operator (`..`) is changed to be higher than that of relational operators, 

+  but lower than that of arithmetic operators.

+

++ Precedence of the match(`=~`) and not-match(`!~`) operators is changed to be that of equality operators.

+

++ Passing a lambda more arguments than is specified in the lambda declaration now results in error in a strict mode

+

+New features

 ------------

++ Java-like `switch` statement is introduced

 

-We accept Pull Requests via GitHub. The [developer mailing list][ml] is the main channel of communication for contributors.

-There are some guidelines which will make applying PRs easier for us:

-+ No tabs! Please use spaces for indentation.

-+ Respect the code style.

-+ Create minimal diffs - disable on save actions like reformat source code or organize imports. If you feel the source code should be reformatted create a separate PR for this change.

-+ Provide JUnit tests for your changes and make sure your changes don't break any existing tests by running ```mvn clean test```.

++ Java-like `synchronized` statement is introduced

 

-If you plan to contribute on a regular basis, please consider filing a [contributor license agreement](https://www.apache.org/licenses/#clas).

-You can learn more about contributing via GitHub in our [contribution guidelines](CONTRIBUTING.md).

++ Java-like `try-with-resources` statement is introduced

+

++ Java-like `try-catch-finally` statement is introduced

+

++ Java-like `throw` statement is introduced

+

++ Java-like `for` classical loop statement is introduced

+

++ Java-like `assert` statement is introduced

+

++ New `remove` flow-control statement is introduced

+

++ New `this` literal is introduced to allow easier access to the current evaluation context

+

++ Java-like `<<`,`>>`,`>>>` bitwise shift operators are introduced. 

+

++ Java-like `<<=`,`>>=`,`>>>=` self-assignment operators are introduced. 

+

++ Java-like `++` and `--` increment/decrement operators are introduced. Prefix and postfix forms are supported.

+

++ Java-like `instanceof` operator is introduced

+

++ Java-like `+` unary promotion operator is introduced.

+

++ Javascript-like `===` and `!==` identity operators are introduced

+

++ C-like `&` pointer and `*` pointer dereference operators are introduced

+

++ New iterator `...` operator is introduced

+

++ New iterator processing (selection/projection/reduction) operators are introduced

+

++ New multiple assignment statement is introduced

+

++ New inline property assignment `a{b:3,c:4}` construct is introduced

+

+Enhancements

+------------

++ Labeled blocks and statements like `switch`, `for`, `while`, `do` can be used. 

+  The defined labels can be further specified for inner `break`, `continue` and `remove` flow-control statements

+

++ Multidimensional arrays can be accessed by using new syntax `arr[x,y]` as well as by using older syntax `arr[x][y]`

+

++ Single expression lambdas can be defined by using `=>` fat arrow operator

+

++ Variable argument lambdas can be defined by using `...` syntax after the last lambda argument

+

++ Last part of the ternary expression (along with the separating `:`) can be omitted, implying `null` as a result

+

++ Pattern matching operators `=~` and `!~` can use new `in` and `!in` aliases 

+

++ Operator `new` can use Java-like syntax `new String()`

+

++ Foreach statement may also define additional `counter` variable along with current variable

+

++ Immutable arraylist `#[1,2,3]` literal constructs can be used 

+

++ Immutable set `#{1,2,3}` literal constructs can be used

+

++ Immutable map `#{1:2,3:4}` literal constructs can be used

+

++ Array comprehensions `[...a]` can be used in array literals

+

++ Set comprehensions `{...a}` can be used in set literals

+

++ Map comprehensions `{*:...a}` can be used in map literals

+

++ Function argument comprehensions `func(...a)` can be used 

+

++ Corresponding unicode characters may be used for the operators like `!=`, `>=` etc

 

 License

 -------

 This code is under the [Apache Licence v2](https://www.apache.org/licenses/LICENSE-2.0).

 

 See the `NOTICE.txt` file for required notices and attributions.

-

-Donations

----------

-You like Apache Commons JEXL? Then [donate back to the ASF](https://www.apache.org/foundation/contributing.html) to support the development.

-

-Additional Resources

---------------------

-

-+ [Apache Commons Homepage](https://commons.apache.org/)

-+ [Apache Issue Tracker (JIRA)](https://issues.apache.org/jira/browse/JEXL)

-+ [Apache Commons Twitter Account](https://twitter.com/ApacheCommons)

-+ `#apache-commons` IRC channel on `irc.freenode.org`

-

-[ml]:https://commons.apache.org/mail-lists.html

diff --git a/pom.xml b/pom.xml
index b5bb9f0..5814363 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.apache.commons</groupId>
     <artifactId>commons-jexl3</artifactId>
-    <version>3.2-SNAPSHOT</version>
+    <version>4.0-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>
@@ -113,11 +113,11 @@
     </dependencies>
 
     <properties>
-        <maven.compiler.source>1.6</maven.compiler.source>
-        <maven.compiler.target>1.6</maven.compiler.target>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</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.release.version>3.2</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>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index 2e6114e..f5e8c05 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -19,15 +19,23 @@
 
 import org.apache.commons.jexl3.introspection.JexlMethod;
 
+import java.lang.ref.Reference;
 import java.lang.reflect.Array;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.MathContext;
 import java.util.Collection;
 import java.util.Map;
+import java.util.Iterator;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.Optional;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * Perform arithmetic, implements JexlOperator methods.
@@ -605,6 +613,34 @@
     }
 
     /**
+     * Checks if object is eligible for fast integer arithmetic.
+     *
+     * @param object  argument
+     */
+    protected boolean isIntegerPrecisionNumber(Object value) {
+        return value instanceof Integer || value instanceof Short || value instanceof Byte;
+    }
+
+    /**
+     * Checks if object is eligible for fast long arithmetic.
+     *
+     * @param object  argument
+     */
+    protected boolean isLongPrecisionNumber(Object value) {
+        return value instanceof Long || isIntegerPrecisionNumber(value);
+    }
+
+    protected BigInteger extendedLong(long x, byte msb) {
+        byte[] bi = new byte[9];
+        bi[0] = msb;
+        for (int i = 8; i > 0; i--) {
+            bi[i] = (byte)(x & 0xFF);
+            x >>= 8;
+        }
+        return new BigInteger(bi);
+    }
+
+    /**
      * Add two values together.
      * <p>
      * If any numeric add fails on coercion to the appropriate type,
@@ -624,6 +660,25 @@
                             : left instanceof String && right instanceof String;
         if (!strconcat) {
             try {
+                // if either are no longer than integers use that type
+                if (isLongPrecisionNumber(left) && isLongPrecisionNumber(right)) {
+                    long l = ((Number) left).longValue();
+                    long r = ((Number) right).longValue();
+                    long result = l + r;
+                    if (left instanceof Long || right instanceof Long) {
+                        if ((l & r & ~result) < 0) {
+                            return extendedLong(result, (byte) -1);
+                        } else if ((~l & ~r & result) < 0) {
+                            return extendedLong(result, (byte) -0);
+                        } else {
+                            return result;
+                        }
+                    } else if (result >= Integer.MIN_VALUE && result <= Integer.MAX_VALUE) {
+                        return (int) result;
+                    } else {
+                        return result;
+                    }
+                }
                 // if either are bigdecimal use that type
                 if (left instanceof BigDecimal || right instanceof BigDecimal) {
                     BigDecimal l = toBigDecimal(left);
@@ -652,6 +707,27 @@
     }
 
     /**
+     * Default self assign implementation for Add.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left + right.
+     */
+    public Object selfAdd(Object left, Object right) {
+        return add(left, right);
+    }
+
+    /**
+     * Default self assign implementation for Increment.
+     *
+     * @param left  left argument
+     * @return ++left.
+     */
+    public Object increment(Object left) {
+        return selfAdd(left, 1);
+    }
+
+    /**
      * Divide the left value by the right.
      *
      * @param left  left argument
@@ -663,6 +739,21 @@
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
+        // if either are no longer than long use that type
+        if (isLongPrecisionNumber(left) && isLongPrecisionNumber(right)) {
+            long l = ((Number) left).longValue();
+            long r = ((Number) right).longValue();
+            if (r == 0L) {
+                throw new ArithmeticException("/");
+            }
+            long result = l / r;
+            if (!(left instanceof Long || right instanceof Long) 
+                    && result >= Integer.MIN_VALUE && result <= Integer.MAX_VALUE) {
+                return (int) result;
+            } else {
+                return result;
+            }
+        }
         // if either are bigdecimal use that type
         if (left instanceof BigDecimal || right instanceof BigDecimal) {
             BigDecimal l = toBigDecimal(left);
@@ -693,6 +784,17 @@
     }
 
     /**
+     * Default self assign implementation for Divide.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left / right.
+     */
+    public Object selfDivide(Object left, Object right) {
+        return divide(left, right);
+    }
+
+    /**
      * left value modulo right.
      *
      * @param left  left argument
@@ -704,6 +806,21 @@
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
+        // if either are no longer than long use that type
+        if (isLongPrecisionNumber(left) && isLongPrecisionNumber(right)) {
+            long l = ((Number) left).longValue();
+            long r = ((Number) right).longValue();
+            if (r == 0L) {
+                throw new ArithmeticException("%");
+            }
+            long result = l % r;
+            if (!(left instanceof Long || right instanceof Long) 
+                    && result >= Integer.MIN_VALUE && result <= Integer.MAX_VALUE) {
+                return (int) result;
+            } else {
+                return result;
+            }
+        }
         // if either are bigdecimal use that type
         if (left instanceof BigDecimal || right instanceof BigDecimal) {
             BigDecimal l = toBigDecimal(left);
@@ -734,6 +851,17 @@
     }
 
     /**
+     * Default self assign implementation for Mod.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left % right.
+     */
+    public Object selfMod(Object left, Object right) {
+        return mod(left, right);
+    }
+
+    /**
      * Multiply the left value by the right.
      *
      * @param left  left argument
@@ -744,6 +872,17 @@
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
+        // if either are no longer than integers use that type
+        if (isIntegerPrecisionNumber(left) && isIntegerPrecisionNumber(right)) {
+            long l = ((Number) left).longValue();
+            long r = ((Number) right).longValue();
+            long result = l * r;
+            if (result >= Integer.MIN_VALUE && result <= Integer.MAX_VALUE) {
+                return (int) result;
+            } else {
+                return result;
+            }
+        }
         // if either are bigdecimal use that type
         if (left instanceof BigDecimal || right instanceof BigDecimal) {
             BigDecimal l = toBigDecimal(left);
@@ -765,6 +904,17 @@
     }
 
     /**
+     * Default self assign implementation for Multiply.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left * right.
+     */
+    public Object selfMultiply(Object left, Object right) {
+        return multiply(left, right);
+    }
+
+    /**
      * Subtract the right value from the left.
      *
      * @param left  left argument
@@ -775,6 +925,25 @@
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
+        // if either are no longer than integers use that type
+        if (isLongPrecisionNumber(left) && isLongPrecisionNumber(right)) {
+            long l = ((Number) left).longValue();
+            long r = ((Number) right).longValue();
+            long result = l - r;
+            if (left instanceof Long || right instanceof Long) {
+                if ((l & r & ~result) > 0) {
+                    return extendedLong(result, (byte) 0);
+                } else if ((~l & ~r & result) > 0) {
+                    return extendedLong(result, (byte) -1);
+                } else {
+                    return result;
+                }
+            } else if (result >= Integer.MIN_VALUE && result <= Integer.MAX_VALUE) {
+                return (int) result;
+            } else {
+                return result;
+            }
+        }
         // if either are bigdecimal use that type
         if (left instanceof BigDecimal || right instanceof BigDecimal) {
             BigDecimal l = toBigDecimal(left);
@@ -796,6 +965,27 @@
     }
 
     /**
+     * Default self assign implementation for Subtract.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left - right.
+     */
+    public Object selfSubtract(Object left, Object right) {
+        return subtract(left, right);
+    }
+
+    /**
+     * Default self assign implementation for Decrement.
+     *
+     * @param left  left argument
+     * @return --left.
+     */
+    public Object decrement(Object left) {
+        return selfSubtract(left, 1);
+    }
+
+    /**
      * Negates a value (unary minus for numbers).
      *
      * @param val the value to negate
@@ -827,6 +1017,151 @@
     }
 
     /**
+     * Confirms a value (unary plus for numbers).
+     *
+     * @param val the value to confirm
+     * @return the confirmed value
+     */
+    public Object confirm(Object val) {
+        if (val instanceof Byte) {
+            return +(Byte) val;
+        } else if (val instanceof Short) {
+            return +(Short) val;
+        } else if (val instanceof Character) {
+            return +(Character) val;
+        } else if (val instanceof Number) {
+            return val;
+        } else if (val instanceof Boolean) {
+            return val;
+        } else if (val instanceof AtomicBoolean) {
+            return ((AtomicBoolean) val).get();
+        }
+        throw new ArithmeticException("Object confirmation:(" + val + ")");
+    }
+
+    /**
+     * Shifts a bit pattern to the left.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left << right.
+     */
+    public Object leftShift(Object left, Object right) {
+        if (left == null && right == null) {
+            return controlNullNullOperands();
+        }
+        // if either are no longer than integers use that type
+        if (isIntegerPrecisionNumber(left)) {
+            int l = ((Number) left).intValue();
+            int r = toInteger(right);
+            int result = l << r;
+            return result;
+        }
+        // if either are no longer than long integers use that type
+        if (isLongPrecisionNumber(left)) {
+            long l = ((Number) left).longValue();
+            int r = toInteger(right);
+            long result = l << r;
+            return result;
+        }
+        // otherwise treat as big integers
+        BigInteger l = toBigInteger(left);
+        int r = toInteger(right);
+        BigInteger result = l.shiftLeft(r);
+        return result;
+    }
+
+    /**
+     * Default self assign implementation for Left Shift.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left << right.
+     */
+    public Object selfLeftShift(Object left, Object right) {
+        return leftShift(left, right);
+    }
+
+    /**
+     * Shifts a bit pattern to the right.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left >> right.
+     */
+    public Object rightShift(Object left, Object right) {
+        if (left == null && right == null) {
+            return controlNullNullOperands();
+        }
+        // if either are no longer than integers use that type
+        if (isIntegerPrecisionNumber(left)) {
+            int l = ((Number) left).intValue();
+            int r = toInteger(right);
+            int result = l >> r;
+            return result;
+        }
+        // if either are no longer than long integers use that type
+        if (isLongPrecisionNumber(left)) {
+            long l = ((Number) left).longValue();
+            int r = toInteger(right);
+            long result = l >> r;
+            return result;
+        }
+        // otherwise treat as big integers
+        BigInteger l = toBigInteger(left);
+        int r = toInteger(right);
+        BigInteger result = l.shiftRight(r);
+        return result;
+    }
+
+    /**
+     * Default self assign implementation for Right Shift.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left >> right.
+     */
+    public Object selfRightShift(Object left, Object right) {
+        return rightShift(left, right);
+    }
+
+    /**
+     * Shifts a bit pattern to the right unsigned.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left >>> right.
+     */
+    public Object rightShiftUnsigned(Object left, Object right) {
+        if (left == null && right == null) {
+            return controlNullNullOperands();
+        }
+        // if either are no longer than integers use that type
+        if (isIntegerPrecisionNumber(left)) {
+            int l = ((Number) left).intValue();
+            int r = toInteger(right);
+            int result = l >>> r;
+            return result;
+        }
+        // otherwise treat as long integers
+        long l = toLong(left);
+        int r = toInteger(right);
+        long result = l >>> r;
+        return result;
+    }
+
+    /**
+     * Default self assign implementation for Right Unsigned Shift.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left >>> right.
+     */
+    public Object selfRightShiftUnsigned(Object left, Object right) {
+        return rightShiftUnsigned(left, right);
+    }
+
+    /**
      * Test if left contains right (right matches/in left).
      * <p>Beware that this method arguments are the opposite of the operator arguments.
      * 'x in y' means 'y contains x'.</p>
@@ -937,6 +1272,9 @@
         if (object instanceof Map<?, ?>) {
             return ((Map<?, ?>) object).isEmpty() ? Boolean.TRUE : Boolean.FALSE;
         }
+        if (object instanceof Iterator<?>) {
+            return ((Iterator<?>) object).hasNext() ? Boolean.FALSE : Boolean.TRUE;
+        }
         return null;
     }
 
@@ -963,6 +1301,68 @@
     }
 
     /**
+     * Dereferences various types: SoftReference, AtomicReference, Optional, ThreadLocal etc
+     *
+     * @param object the object to be derefenced
+     * @return the object or TRY_FAILED if there is no dereference path
+     */
+    public Object indirect(Object object) {
+        if (object instanceof Reference) {
+            return ((Reference) object).get();
+        }
+        if (object instanceof ThreadLocal) {
+            return ((ThreadLocal) object).get();
+        }
+        if (object instanceof Optional) {
+            return ((Optional) object).get();
+        }
+        if (object instanceof AtomicReference) {
+            return ((AtomicReference) object).get();
+        }
+        if (object instanceof AtomicBoolean) {
+            return ((AtomicBoolean) object).get();
+        }
+        if (object instanceof AtomicInteger) {
+            return ((AtomicInteger) object).get();
+        }
+        if (object instanceof AtomicLong) {
+            return ((AtomicLong) object).get();
+        }
+        return JexlEngine.TRY_FAILED;
+    }
+
+    /**
+     * Assigns value to various types: ThreadLocal, AtomicReference etc
+     *
+     * @param object the object to be derefenced
+     * @param value the value to assign
+     * @return JexlOperator.ASSIGN or null if there is no dereferenced assignment path
+     */
+    public Object indirectAssign(Object object, Object value) {
+        if (object instanceof ThreadLocal) {
+            ((ThreadLocal) object).set(value);
+            return value;
+        }
+        if (object instanceof AtomicReference) {
+            ((AtomicReference) object).set(value);
+            return value;
+        }
+        if (object instanceof AtomicBoolean) {
+            ((AtomicBoolean) object).set(toBoolean(value));
+            return value;
+        }
+        if (object instanceof AtomicInteger) {
+            ((AtomicInteger) object).set(toInteger(value));
+            return value;
+        }
+        if (object instanceof AtomicLong) {
+            ((AtomicLong) object).set(toLong(value));
+            return value;
+        }
+        return JexlEngine.TRY_FAILED;
+    }
+
+    /**
      * Performs a bitwise and.
      *
      * @param left  the left operand
@@ -976,6 +1376,17 @@
     }
 
     /**
+     * Default self assign implementation for bitwise and.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left &amp; right.
+     */
+    public Object selfAnd(Object left, Object right) {
+        return and(left, right);
+    }
+
+    /**
      * Performs a bitwise or.
      *
      * @param left  the left operand
@@ -989,6 +1400,17 @@
     }
 
     /**
+     * Default self assign implementation for bitwise or.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left | right.
+     */
+    public Object selfOr(Object left, Object right) {
+        return or(left, right);
+    }
+
+    /**
      * Performs a bitwise xor.
      *
      * @param left  the left operand
@@ -1002,6 +1424,17 @@
     }
 
     /**
+     * Default self assign implementation for bitwise xor.
+     *
+     * @param left  left argument
+     * @param right  right argument
+     * @return left ^ right.
+     */
+    public Object selfXor(Object left, Object right) {
+        return xor(left, right);
+    }
+
+    /**
      * Performs a bitwise complement.
      *
      * @param val the operand
diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index 38786a8..7a0d2d7 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -88,6 +88,9 @@
     /** Whether error messages will carry debugging information. */
     private Boolean debug = null;
 
+    /** Whether this engine evaluates assertions */
+    private Boolean assertions = null;
+
     /** Whether interrupt throws JexlException.Cancel. */
     private Boolean cancellable = null;
 
@@ -292,6 +295,22 @@
     }
 
     /**
+     * Sets whether the engine evaluates assertions
+     *
+     * @param flag true means assertions are evaluated, false otherwise
+     * @return this builder
+     */
+    public JexlBuilder assertions(boolean flag) {
+        this.assertions = flag;
+        return this;
+    }
+
+    /** @return true if assertions are enabled, false otherwise */
+    public Boolean assertions() {
+        return this.assertions;
+    }
+
+    /**
      * Sets whether the engine considers dereferencing null in navigation expressions
      * as errors or evaluates them as null.
      * <p><code>x.y()</code> if x is null throws an exception when not safe,
diff --git a/src/main/java/org/apache/commons/jexl3/JexlEngine.java b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
index b796bb8..a15837c 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
@@ -130,6 +130,13 @@
          * @return true if strict, false otherwise
          */
         Boolean isStrict();
+
+        /**
+         * Checks whether the engine evaluates assertions
+         *
+         * @return true if assertions are enabled, false otherwise
+         */
+        default Boolean isAssertions() { return false; }
         
         /**
          * Checks whether the arithmetic triggers errors during evaluation when null is used as an operand.
@@ -259,6 +266,13 @@
     public abstract boolean isStrict();
 
     /**
+     * Checks whether this engine evaluates assertions.
+     *
+     * @return true if assertions are enabled, false otherwise
+     */
+    public abstract boolean isAssertions();
+
+    /**
      * Checks whether this engine will throw JexlException.Cancel (true) or return null (false) when interrupted
      * during an execution.
      *
diff --git a/src/main/java/org/apache/commons/jexl3/JexlException.java b/src/main/java/org/apache/commons/jexl3/JexlException.java
index f9862d8..b4c4303 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlException.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlException.java
@@ -235,7 +235,7 @@
                     + expr.substring(begin, end > length ? length : end) + " ...'";
         }
     }
-    
+
     /**
      * Pleasing checkstyle.
      * @return the info
@@ -327,7 +327,7 @@
         public Ambiguous(JexlInfo info, String expr) {
            this(info, null, expr);
         }
-                
+
         /**
          * Creates a new Ambiguous statement exception instance.
          * @param begin  the start location information
@@ -338,7 +338,7 @@
             super(begin, expr);
             recover = end;
         }
-        
+
         @Override
         protected String detailedMessage() {
             return parserError("ambiguous statement", getDetail());
@@ -347,7 +347,7 @@
         /**
          * Tries to remove this ambiguity in the source.
          * @param src the source that triggered this exception
-         * @return the source with the ambiguous statement removed 
+         * @return the source with the ambiguous statement removed
          *         or null if no recovery was possible
          */
         public String tryCleanSource(String src) {
@@ -421,7 +421,7 @@
             return "stack overflow " + getDetail();
         }
     }
-    
+
     /**
      * Thrown when parsing fails due to an invalid assigment.
      *
@@ -543,7 +543,7 @@
          * Undefined variable flag.
          */
         private final boolean undefined;
-               
+
         /**
          * Creates a new Property exception instance.
          *
@@ -556,7 +556,7 @@
         public Property(JexlNode node, String pty, Throwable cause) {
             this(node, pty, true, cause);
         }
-        
+
         /**
          * Creates a new Property exception instance.
          *
@@ -805,18 +805,58 @@
     }
 
     /**
-     * Thrown to break a loop.
+     * Thrown to branch a script execution.
+     *
+     * @since 3.2
+     */
+    public static class LabelledException extends JexlException {
+
+        /** The target label */
+        private final String label;
+
+        /**
+         * Creates a new instance of LabeledException.
+         *
+         * @param node the node where the interruption was detected
+         * @param message the exception message
+         * @param label the target label
+         */
+        public LabelledException(JexlNode node, String message, String label) {
+            super(node, message, null);
+            this.label = label;
+        }
+
+        /**
+         * @return the statement target label
+         */
+        public String getLabel() {
+            return label;
+        }
+    }
+
+    /**
+     * Thrown to break a loop or a block.
      *
      * @since 3.0
      */
-    public static class Break extends JexlException {
+    public static class Break extends LabelledException {
+        /**
+         * Creates a new instance of Break.
+         *
+         * @param node the break
+         * @param label the target label
+         */
+        public Break(JexlNode node, String label) {
+            super(node, "break loop", label);
+        }
+
         /**
          * Creates a new instance of Break.
          *
          * @param node the break
          */
         public Break(JexlNode node) {
-            super(node, "break loop", null);
+            this(node, null);
         }
     }
 
@@ -825,14 +865,50 @@
      *
      * @since 3.0
      */
-    public static class Continue extends JexlException {
+    public static class Continue extends LabelledException {
+        /**
+         * Creates a new instance of Continue.
+         *
+         * @param node the continue
+         * @param label the target label
+         */
+        public Continue(JexlNode node, String label) {
+            super(node, "continue loop", label);
+        }
+
         /**
          * Creates a new instance of Continue.
          *
          * @param node the continue
          */
         public Continue(JexlNode node) {
-            super(node, "continue loop", null);
+            this(node, null);
+        }
+    }
+
+    /**
+     * Thrown to remove an element from underlying iterator collection within a loop.
+     *
+     * @since 3.2
+     */
+    public static class Remove extends LabelledException {
+        /**
+         * Creates a new instance of Remove.
+         *
+         * @param node the remove
+         * @param label the target label
+         */
+        public Remove(JexlNode node, String label) {
+            super(node, "remove", label);
+        }
+
+        /**
+         * Creates a new instance of Remove.
+         *
+         * @param node the remove
+         */
+        public Remove(JexlNode node) {
+            this(node, null);
         }
     }
 
@@ -863,4 +939,4 @@
         }
         return msg.toString();
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOperator.java b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
index 5bc448e..aa9e2d6 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOperator.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
@@ -105,6 +105,30 @@
     XOR("^", "xor", 2),
 
     /**
+     * Left-shift operator.
+     * <br><strong>Syntax:</strong> <code>x << y</code>
+     * <br><strong>Method:</strong> <code>T leftShift(L x, R y);</code>.
+     * @see JexlArithmetic#leftShift
+     */
+    SHL("<<", "leftShift", 2),
+
+    /**
+     * Right-shift operator.
+     * <br><strong>Syntax:</strong> <code>x >> y</code>
+     * <br><strong>Method:</strong> <code>T rightShift(L x, R y);</code>.
+     * @see JexlArithmetic#rightShift
+     */
+    SAR(">>", "rightShift", 2),
+
+    /**
+     * Right-shift unsigned operator.
+     * <br><strong>Syntax:</strong> <code>x >>> y</code>
+     * <br><strong>Method:</strong> <code>T rightShiftUnsigned(L x, R y);</code>.
+     * @see JexlArithmetic#rightShiftUnsigned
+     */
+    SHR(">>>", "rightShiftUnsigned", 2),
+
+    /**
      * Equals operator.
      * <br><strong>Syntax:</strong> <code>x == y</code>
      * <br><strong>Method:</strong> <code>boolean equals(L x, R y);</code>.
@@ -193,6 +217,14 @@
     NEGATE("-", "negate", 1),
 
     /**
+     * Confirm operator.
+     * <br><strong>Syntax:</strong> <code>+x</code>
+     * <br><strong>Method:</strong> <code>T confirm(L x);</code>.
+     * @see JexlArithmetic#confirm
+     */
+    CONFIRM("+", "confirm", 1),
+
+    /**
      * Empty operator.
      * <br><strong>Syntax:</strong> <code>empty x</code> or <code>empty(x)</code>
      * <br><strong>Method:</strong> <code>boolean isEmpty(L x);</code>.
@@ -265,6 +297,55 @@
     SELF_XOR("^=", "selfXor", XOR),
 
     /**
+     * Self-shl operator.
+     * <br><strong>Syntax:</strong> <code>x <<= y</code>
+     * <br><strong>Method:</strong> <code>T selfLeftShift(L x, R y);</code>.
+     */
+    SELF_SHL("<<=", "selfLeftShift", SHL),
+
+    /**
+     * Self-sar operator.
+     * <br><strong>Syntax:</strong> <code>x >>= y</code>
+     * <br><strong>Method:</strong> <code>T selfRightShift(L x, R y);</code>.
+     */
+    SELF_SAR(">>=", "selfRightShift", SAR),
+
+    /**
+     * Self-shr operator.
+     * <br><strong>Syntax:</strong> <code>x >>>= y</code>
+     * <br><strong>Method:</strong> <code>T selfRightShiftUnsigned(L x, R y);</code>.
+     */
+    SELF_SHR(">>>=", "selfRightShiftUnsigned", SHR),
+
+    /**
+     * Increment operator.
+     * <br><strong>Syntax:</strong> <code>++x</code>
+     * <br><strong>Method:</strong> <code>T increment(L x);</code>.
+     */
+    INCREMENT("++", "increment", 1),
+
+    /**
+     * Increment operator.
+     * <br><strong>Syntax:</strong> <code>--x</code>
+     * <br><strong>Method:</strong> <code>T decrement(L x);</code>.
+     */
+    DECREMENT("--", "decrement", 1),
+
+    /**
+     * Indirect operator.
+     * <br><strong>Syntax:</strong> <code>*x</code>
+     * <br><strong>Method:</strong> <code>T indirect(L x);</code>.
+     */
+    INDIRECT("*", "indirect", 1),
+
+    /**
+     * Indirect assign operator.
+     * <br><strong>Syntax:</strong> <code>*x = y</code>
+     * <br><strong>Method:</strong> <code>T indirectAssign(L x, R y);</code>.
+     */
+    INDIRECT_ASSIGN("*=", "indirectAssign", 2),
+
+    /**
      * Marker for side effect.
      * <br>Returns this from 'self*' overload method to let the engine know the side effect has been performed and
      * there is no need to assign the result.
@@ -306,7 +387,25 @@
      * <br><strong>Method:</strong> <code>Iterator&lt;Object&gt; forEach(R y);</code>.
      * @since 3.1
      */
-    FOR_EACH("for(...)", "forEach", 1);
+    FOR_EACH("for(x:y)", "forEach", 1),
+
+    /**
+     * Iterator generator as in for(var i,j : y).
+     * If the returned Iterator is AutoCloseable, close will be called after the last execution of the loop block.
+     * <br><strong>Syntax:</strong> <code>for(var i,j : y){...} </code>
+     * <br><strong>Method:</strong> <code>Iterator&lt;Object&gt; forEachIndexed(R y);</code>.
+     * @since 3.2
+     */
+    FOR_EACH_INDEXED("for(i,j:y)", "forEachIndexed", 1),
+
+    /**
+     * Resource generator as in try(var x : y).
+     * If the returned Resource is AutoCloseable, close will be called after the execution of the statement.
+     * <br><strong>Syntax:</strong> <code>try(var x : y){...} </code>
+     * <br><strong>Method:</strong> <code>Object tryWith(R y);</code>.
+     * @since 3.2
+     */
+    TRY_WITH("try(x:y)", "tryWith", 1);
 
     /**
      * The operator symbol.
diff --git a/src/main/java/org/apache/commons/jexl3/JexlScript.java b/src/main/java/org/apache/commons/jexl3/JexlScript.java
index ada303c..e7011c3 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlScript.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlScript.java
@@ -93,6 +93,14 @@
     String[] getParameters();
 
     /**
+     * Returns true if script supports taking variable number of arguments.
+     *
+     * @return boolean
+     * @since 3.2
+     */
+    boolean isVarArgs();
+
+    /**
      * Gets this script unbound parameters.
      * <p>Parameters that haven't been bound by a previous call to curry().</p>
      * @return the parameters or null
diff --git a/src/main/java/org/apache/commons/jexl3/internal/ArrayBuilder.java b/src/main/java/org/apache/commons/jexl3/internal/ArrayBuilder.java
index 6075c9d..8796e6f 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/ArrayBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/ArrayBuilder.java
@@ -54,6 +54,8 @@
         return prim == null ? parm : prim;
     }
 
+    protected static Object[] EMPTY_ARRAY = new Object[0];
+
     /** The intended class array. */
     protected Class<?> commonClass = null;
     /** Whether the array stores numbers. */
@@ -61,16 +63,14 @@
     /** Whether we can try unboxing. */
     protected boolean unboxing = true;
     /** The untyped list of items being added. */
-    protected final Object[] untyped;
-    /** Number of added items. */
-    protected int added = 0;
+    protected final ArrayList<Object> untyped;
 
     /**
      * Creates a new builder.
-     * @param size the exact array size
+     * @param size the initial array size
      */
     public ArrayBuilder(int size) {
-        untyped = new Object[size];
+        untyped = new ArrayList<Object> (size);
     }
 
     @Override
@@ -103,40 +103,34 @@
                 }
             }
         }
-        if (added >= untyped.length) {
-            throw new IllegalArgumentException("add() over size");
-        }
-        untyped[added++] = value;
+        untyped.add(value);
     }
 
     @Override
     public Object create(boolean extended) {
-        if (untyped != null) {
-            if (extended) {
-                List<Object> list = new ArrayList<Object>(added);
-                for(int i = 0; i < added; ++i) {
-                    list.add(untyped[i]);
-                }
-                return list;
-            }
-            // convert untyped array to the common class if not Object.class
-            if (commonClass != null && !Object.class.equals(commonClass)) {
-                final int size = added;
-                // if the commonClass is a number, it has an equivalent primitive type, get it
-                if (unboxing) {
-                    commonClass = unboxingClass(commonClass);
-                }
-                // allocate and fill up the typed array
-                Object typed = Array.newInstance(commonClass, size);
-                for (int i = 0; i < size; ++i) {
-                    Array.set(typed, i, untyped[i]);
-                }
-                return typed;
-            } else {
-                return untyped;
-            }
-        } else {
-            return new Object[0];
+        if (extended) {
+            return untyped;
         }
+
+        if (commonClass == null)
+            commonClass = Object.class;
+
+        // convert untyped array to the common class
+        final int size = untyped.size();
+        // if the commonClass is a number, it has an equivalent primitive type, get it
+        if (unboxing) {
+            commonClass = unboxingClass(commonClass);
+        }
+
+        if (commonClass == Object.class) {
+            return untyped.size() > 0 ? untyped.toArray() : EMPTY_ARRAY;
+        }
+
+        // allocate and fill up the typed array
+        Object typed = Array.newInstance(commonClass, size);
+        for (int i = 0; i < size; ++i) {
+            Array.set(typed, i, untyped.get(i));
+        }
+        return typed;
     }
 }
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 009c962..252af80 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Closure.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Closure.java
@@ -28,6 +28,9 @@
     /** The frame. */
     protected final Scope.Frame frame;
 
+    /** The number of arguments being curried. */
+    protected final int argCount;
+
     /**
      * Creates a closure.
      * @param theCaller the calling interpreter
@@ -36,6 +39,7 @@
     protected Closure(Interpreter theCaller, ASTJexlLambda lambda) {
         super(theCaller.jexl, null, lambda);
         frame = lambda.createFrame(theCaller.frame);
+        argCount = 0;
     }
 
     /**
@@ -45,10 +49,65 @@
      */
     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 == null
-                ? script.createFrame(args)
-                : sf.assign(args);
+
+        if (base instanceof Closure) {
+            Scope.Frame sf = ((Closure) base).frame;
+
+            boolean varArgs = script.isVarArgs();
+            int baseArgCount = ((Closure) base).argCount;
+
+            if (varArgs) {
+                if (baseArgCount >= script.getArgCount()) {
+                   frame = createNewVarArgFrame(sf, args);
+                } else {
+                   frame = sf.assign(scriptArgs(baseArgCount, args));
+                }
+            } else {
+                frame = sf.assign(args);
+            }
+            argCount = baseArgCount + args.length;
+        } else {
+            frame = script.createFrame(scriptArgs(args));
+            argCount = args.length;
+        }
+    }
+
+    protected Scope.Frame createNewVarArgFrame(Scope.Frame sf, Object[] args) {
+
+        if (args != null && args.length > 0) {
+           int varArgPos = script.getArgCount() - 1;
+           Scope.Frame frame = sf.clone();
+
+           Object[] carg = (Object[]) frame.get(varArgPos);
+           Object[] varg = new Object[carg.length + args.length];
+
+           System.arraycopy(carg, 0, varg, 0, carg.length);
+           System.arraycopy(args, 0, varg, carg.length, args.length);
+           frame.set(varArgPos, varg);
+
+           return frame;
+        } else {
+           return sf;
+        }
+    }
+
+    @Override
+    public String[] getUnboundParameters() {
+
+        String[] scriptParams = super.getParameters();
+
+        if (scriptParams == null || scriptParams.length == 0)
+            return scriptParams;
+
+        String[] unboundParams = frame.getUnboundParameters();
+
+        boolean varArgs = script.isVarArgs();
+
+        if (unboundParams.length == 0 && varArgs) {
+            return new String[] {scriptParams[scriptParams.length - 1]};
+        } else {
+            return unboundParams;
+        }
     }
 
     @Override
@@ -83,11 +142,6 @@
         return true;
     }
 
-    @Override
-    public String[] getUnboundParameters() {
-        return frame.getUnboundParameters();
-    }
-
     /**
      * Sets the hoisted index of a given symbol, ie the target index of a parent hoisted symbol in this closure's frame.
      * <p>This is meant to allow a locally defined function to "see" and call itself as a local (hoisted) variable;
@@ -121,9 +175,26 @@
     @Override
     public Object execute(JexlContext context, Object... args) {
         Scope.Frame callFrame = null;
+
         if (frame != null) {
-            callFrame = frame.assign(args);
+
+            boolean varArgs = script.isVarArgs();
+
+            if (varArgs) {
+
+                if (argCount >= script.getArgCount()) {
+                   callFrame = createNewVarArgFrame(frame, args);
+                } else {
+                   callFrame = frame.assign(scriptArgs(argCount, args));
+                }
+
+            } else {
+                callFrame = frame.assign(args);
+            }
+        } else {
+            callFrame = script.createFrame(scriptArgs(args));
         }
+
         Interpreter interpreter = jexl.createInterpreter(context, callFrame);
         JexlNode block = script.jjtGetChild(script.jjtGetNumChildren() - 1);
         return interpreter.interpret(block);
@@ -133,7 +204,7 @@
     public Callable callable(JexlContext context, Object... args) {
         Scope.Frame local = null;
         if (frame != null) {
-            local = frame.assign(args);
+            local = frame.assign(scriptArgs(args));
         }
         return new Callable(jexl.createInterpreter(context, local)) {
             @Override
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
index cfc4dbc..fa2df9c 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
@@ -24,7 +24,9 @@
 import org.apache.commons.jexl3.parser.ASTAndNode;
 import org.apache.commons.jexl3.parser.ASTArguments;
 import org.apache.commons.jexl3.parser.ASTArrayAccess;
+import org.apache.commons.jexl3.parser.ASTArrayConstructorNode;
 import org.apache.commons.jexl3.parser.ASTArrayLiteral;
+import org.apache.commons.jexl3.parser.ASTAssertStatement;
 import org.apache.commons.jexl3.parser.ASTAssignment;
 import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
 import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
@@ -32,23 +34,45 @@
 import org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
 import org.apache.commons.jexl3.parser.ASTBlock;
 import org.apache.commons.jexl3.parser.ASTBreak;
+import org.apache.commons.jexl3.parser.ASTClassLiteral;
 import org.apache.commons.jexl3.parser.ASTConstructorNode;
 import org.apache.commons.jexl3.parser.ASTContinue;
+import org.apache.commons.jexl3.parser.ASTDecrementNode;
+import org.apache.commons.jexl3.parser.ASTDecrementPostfixNode;
 import org.apache.commons.jexl3.parser.ASTDivNode;
 import org.apache.commons.jexl3.parser.ASTDoWhileStatement;
 import org.apache.commons.jexl3.parser.ASTEQNode;
 import org.apache.commons.jexl3.parser.ASTERNode;
 import org.apache.commons.jexl3.parser.ASTEWNode;
+import org.apache.commons.jexl3.parser.ASTElvisNode;
 import org.apache.commons.jexl3.parser.ASTEmptyFunction;
 import org.apache.commons.jexl3.parser.ASTEmptyMethod;
-import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
+import org.apache.commons.jexl3.parser.ASTEnumerationNode;
+import org.apache.commons.jexl3.parser.ASTEnumerationReference;
+import org.apache.commons.jexl3.parser.ASTExpressionStatement;
+import org.apache.commons.jexl3.parser.ASTExtVar;
 import org.apache.commons.jexl3.parser.ASTFalseNode;
+import org.apache.commons.jexl3.parser.ASTForStatement;
+import org.apache.commons.jexl3.parser.ASTForInitializationNode;
+import org.apache.commons.jexl3.parser.ASTForTerminationNode;
+import org.apache.commons.jexl3.parser.ASTForIncrementNode;
 import org.apache.commons.jexl3.parser.ASTForeachStatement;
+import org.apache.commons.jexl3.parser.ASTForeachVar;
 import org.apache.commons.jexl3.parser.ASTFunctionNode;
 import org.apache.commons.jexl3.parser.ASTGENode;
 import org.apache.commons.jexl3.parser.ASTGTNode;
 import org.apache.commons.jexl3.parser.ASTIdentifier;
 import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
+import org.apache.commons.jexl3.parser.ASTIncrementNode;
+import org.apache.commons.jexl3.parser.ASTIncrementPostfixNode;
+import org.apache.commons.jexl3.parser.ASTIndirectNode;
+import org.apache.commons.jexl3.parser.ASTInitializedArrayConstructorNode;
+import org.apache.commons.jexl3.parser.ASTInlinePropertyAssignment;
+import org.apache.commons.jexl3.parser.ASTInlinePropertyArrayEntry;
+import org.apache.commons.jexl3.parser.ASTInlinePropertyEntry;
+import org.apache.commons.jexl3.parser.ASTIOFNode;
+import org.apache.commons.jexl3.parser.ASTISNode;
+import org.apache.commons.jexl3.parser.ASTMapLiteral;
 import org.apache.commons.jexl3.parser.ASTIfStatement;
 import org.apache.commons.jexl3.parser.ASTJexlLambda;
 import org.apache.commons.jexl3.parser.ASTJexlScript;
@@ -56,24 +80,34 @@
 import org.apache.commons.jexl3.parser.ASTLENode;
 import org.apache.commons.jexl3.parser.ASTLTNode;
 import org.apache.commons.jexl3.parser.ASTMapEntry;
+import org.apache.commons.jexl3.parser.ASTMapEnumerationNode;
 import org.apache.commons.jexl3.parser.ASTMapLiteral;
+import org.apache.commons.jexl3.parser.ASTMapProjectionNode;
 import org.apache.commons.jexl3.parser.ASTMethodNode;
 import org.apache.commons.jexl3.parser.ASTModNode;
 import org.apache.commons.jexl3.parser.ASTMulNode;
+import org.apache.commons.jexl3.parser.ASTMultipleAssignment;
 import org.apache.commons.jexl3.parser.ASTNENode;
 import org.apache.commons.jexl3.parser.ASTNEWNode;
+import org.apache.commons.jexl3.parser.ASTNINode;
 import org.apache.commons.jexl3.parser.ASTNRNode;
 import org.apache.commons.jexl3.parser.ASTNSWNode;
 import org.apache.commons.jexl3.parser.ASTNotNode;
 import org.apache.commons.jexl3.parser.ASTNullLiteral;
 import org.apache.commons.jexl3.parser.ASTNumberLiteral;
 import org.apache.commons.jexl3.parser.ASTOrNode;
+import org.apache.commons.jexl3.parser.ASTPointerNode;
+import org.apache.commons.jexl3.parser.ASTProjectionNode;
+import org.apache.commons.jexl3.parser.ASTQualifiedConstructorNode;
 import org.apache.commons.jexl3.parser.ASTRangeNode;
+import org.apache.commons.jexl3.parser.ASTReductionNode;
 import org.apache.commons.jexl3.parser.ASTReference;
 import org.apache.commons.jexl3.parser.ASTReferenceExpression;
 import org.apache.commons.jexl3.parser.ASTRegexLiteral;
+import org.apache.commons.jexl3.parser.ASTRemove;
 import org.apache.commons.jexl3.parser.ASTReturnStatement;
 import org.apache.commons.jexl3.parser.ASTSWNode;
+import org.apache.commons.jexl3.parser.ASTSelectionNode;
 import org.apache.commons.jexl3.parser.ASTSetAddNode;
 import org.apache.commons.jexl3.parser.ASTSetAndNode;
 import org.apache.commons.jexl3.parser.ASTSetDivNode;
@@ -82,14 +116,33 @@
 import org.apache.commons.jexl3.parser.ASTSetMultNode;
 import org.apache.commons.jexl3.parser.ASTSetOrNode;
 import org.apache.commons.jexl3.parser.ASTSetSubNode;
+import org.apache.commons.jexl3.parser.ASTSetShlNode;
+import org.apache.commons.jexl3.parser.ASTSetSarNode;
+import org.apache.commons.jexl3.parser.ASTSetShrNode;
 import org.apache.commons.jexl3.parser.ASTSetXorNode;
+import org.apache.commons.jexl3.parser.ASTShiftLeftNode;
+import org.apache.commons.jexl3.parser.ASTShiftRightNode;
+import org.apache.commons.jexl3.parser.ASTShiftRightUnsignedNode;
 import org.apache.commons.jexl3.parser.ASTSizeFunction;
 import org.apache.commons.jexl3.parser.ASTSizeMethod;
+import org.apache.commons.jexl3.parser.ASTStartCountNode;
+import org.apache.commons.jexl3.parser.ASTStopCountNode;
 import org.apache.commons.jexl3.parser.ASTStringLiteral;
 import org.apache.commons.jexl3.parser.ASTSubNode;
+import org.apache.commons.jexl3.parser.ASTSwitchStatement;
+import org.apache.commons.jexl3.parser.ASTSwitchStatementCase;
+import org.apache.commons.jexl3.parser.ASTSwitchStatementDefault;
+import org.apache.commons.jexl3.parser.ASTSynchronizedStatement;
 import org.apache.commons.jexl3.parser.ASTTernaryNode;
+import org.apache.commons.jexl3.parser.ASTThisNode;
+import org.apache.commons.jexl3.parser.ASTThrowStatement;
 import org.apache.commons.jexl3.parser.ASTTrueNode;
+import org.apache.commons.jexl3.parser.ASTTryStatement;
+import org.apache.commons.jexl3.parser.ASTTryVar;
+import org.apache.commons.jexl3.parser.ASTTryWithResourceStatement;
+import org.apache.commons.jexl3.parser.ASTTryResource;
 import org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
+import org.apache.commons.jexl3.parser.ASTUnaryPlusNode;
 import org.apache.commons.jexl3.parser.ASTVar;
 import org.apache.commons.jexl3.parser.ASTWhileStatement;
 import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
@@ -129,7 +182,7 @@
      */
     public Debugger() {
     }
-    
+
     /**
      * Resets this debugger state.
      */
@@ -141,7 +194,7 @@
         indentLevel = 0;
         indent = 2;
     }
-    
+
     /**
      * Position the debugger on the root of an expression.
      * @param jscript the expression
@@ -294,9 +347,14 @@
         if (!(child instanceof ASTJexlScript
             || child instanceof ASTBlock
             || child instanceof ASTIfStatement
+            || child instanceof ASTForStatement
             || child instanceof ASTForeachStatement
             || child instanceof ASTWhileStatement
             || child instanceof ASTDoWhileStatement
+            || child instanceof ASTTryStatement
+            || child instanceof ASTTryWithResourceStatement
+            || child instanceof ASTSwitchStatement
+            || child instanceof ASTSynchronizedStatement
             || child instanceof ASTAnnotation)) {
             builder.append(';');
             if (indent > 0) {
@@ -377,6 +435,27 @@
         return data;
     }
 
+    /**
+     * Checks if the child of a node using postfix notation is the cause to debug, adds their representation to the
+     * rebuilt expression.
+     * @param node   the node
+     * @param suffix the node token
+     * @param data   visitor pattern argument
+     * @return visitor pattern value
+     */
+    protected Object postfixChild(JexlNode node, String suffix, Object data) {
+        boolean paren = node.jjtGetChild(0).jjtGetNumChildren() > 1;
+        if (paren) {
+            builder.append('(');
+        }
+        accept(node.jjtGetChild(0), data);
+        if (paren) {
+            builder.append(')');
+        }
+        builder.append(suffix);
+        return data;
+    }
+
     @Override
     protected Object visit(ASTAddNode node, Object data) {
         return additiveNode(node, " + ", data);
@@ -414,6 +493,32 @@
         return data;
     }
 
+    /**
+     * Rebuilds a shift expression.
+     * @param node the node
+     * @param op   the operator
+     * @param data visitor pattern argument
+     * @return visitor pattern value
+     */
+    protected Object shiftNode(JexlNode node, String op, Object data) {
+        // need parenthesis if not in operator precedence order
+        boolean paren = node.jjtGetParent() instanceof ASTAddNode
+                || node.jjtGetParent() instanceof ASTSubNode;
+        int num = node.jjtGetNumChildren();
+        if (paren) {
+            builder.append('(');
+        }
+        accept(node.jjtGetChild(0), data);
+        for (int i = 1; i < num; ++i) {
+            builder.append(op);
+            accept(node.jjtGetChild(i), data);
+        }
+        if (paren) {
+            builder.append(')');
+        }
+        return data;
+    }
+
     @Override
     protected Object visit(ASTAndNode node, Object data) {
         return infixChildren(node, " && ", false, data);
@@ -431,14 +536,12 @@
     }
 
     @Override
-    protected Object visit(ASTExtendedLiteral node, Object data) {
-        builder.append("...");
-        return data;
-    }
-
-    @Override
     protected Object visit(ASTArrayLiteral node, Object data) {
         int num = node.jjtGetNumChildren();
+
+        if (node.isImmutable())
+            builder.append("#");
+
         builder.append("[ ");
         if (num > 0) {
             accept(node.jjtGetChild(0), data);
@@ -447,6 +550,12 @@
                 accept(node.jjtGetChild(i), data);
             }
         }
+        if (node.isExtended()) {
+            if (num > 0)
+                builder.append(",");
+            builder.append("...");
+        }
+
         builder.append(" ]");
         return data;
     }
@@ -462,6 +571,25 @@
     }
 
     @Override
+    protected Object visit(ASTMultipleAssignment node, Object data) {
+        int num = node.jjtGetNumChildren();
+        boolean isVarDeclare = node.jjtGetChild(0) instanceof ASTExtVar;
+
+        if (isVarDeclare)
+            builder.append("var");
+
+        builder.append('(');
+        for (int i = 0; i < num - 1; ++i) {
+            if (i > 0)
+                builder.append(',');
+            accept(node.jjtGetChild(i), data);
+        }
+        builder.append(") = ");
+        accept(node.jjtGetChild(num - 1), data);
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTBitwiseAndNode node, Object data) {
         return infixChildren(node, " & ", false, data);
     }
@@ -485,6 +613,11 @@
 
     @Override
     protected Object visit(ASTBlock node, Object data) {
+        String label = node.getLabel();
+        if (label != null) {
+            builder.append(label);
+            builder.append(" : ");
+        }
         builder.append('{');
         if (indent > 0) {
             indentLevel += 1;
@@ -529,6 +662,16 @@
     }
 
     @Override
+    protected Object visit(ASTISNode node, Object data) {
+        return infixChildren(node, " === ", false, data);
+    }
+
+    @Override
+    protected Object visit(ASTNINode node, Object data) {
+        return infixChildren(node, " !== ", false, data);
+    }
+
+    @Override
     protected Object visit(ASTEQNode node, Object data) {
         return infixChildren(node, " == ", false, data);
     }
@@ -565,16 +708,87 @@
 
     @Override
     protected Object visit(ASTContinue node, Object data) {
-        return check(node, "continue", data);
+        builder.append("continue");
+        String label = node.getLabel();
+        if (label != null) {
+            builder.append(' ');
+            builder.append(label);
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTRemove node, Object data) {
+        builder.append("remove");
+        String label = node.getLabel();
+        if (label != null) {
+            builder.append(' ');
+            builder.append(label);
+        }
+        return data;
     }
 
     @Override
     protected Object visit(ASTBreak node, Object data) {
-        return check(node, "break", data);
+        builder.append("break");
+        String label = node.getLabel();
+        if (label != null) {
+            builder.append(' ');
+            builder.append(label);
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTForStatement node, Object data) {
+        String label = node.getLabel();
+        if (label != null) {
+            builder.append(label);
+            builder.append(" : ");
+        }
+        builder.append("for(");
+        accept(node.jjtGetChild(0), data);
+        builder.append(" ; ");
+        accept(node.jjtGetChild(1), data);
+        builder.append(" ; ");
+        accept(node.jjtGetChild(2), data);
+        builder.append(") ");
+        if (node.jjtGetNumChildren() > 3) {
+            acceptStatement(node.jjtGetChild(3), data);
+        } else {
+            builder.append(';');
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTForInitializationNode node, Object data) {
+        if (node.jjtGetNumChildren() > 0) 
+            accept(node.jjtGetChild(0), data);
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTForTerminationNode node, Object data) {
+        if (node.jjtGetNumChildren() > 0) 
+            accept(node.jjtGetChild(0), data);
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTForIncrementNode node, Object data) {
+        if (node.jjtGetNumChildren() > 0) 
+            accept(node.jjtGetChild(0), data);
+        return data;
     }
 
     @Override
     protected Object visit(ASTForeachStatement node, Object data) {
+        String label = node.getLabel();
+        if (label != null) {
+            builder.append(label);
+            builder.append(" : ");
+        }
         builder.append("for(");
         accept(node.jjtGetChild(0), data);
         builder.append(" : ");
@@ -589,6 +803,77 @@
     }
 
     @Override
+    protected Object visit(ASTForeachVar node, Object data) {
+        accept(node.jjtGetChild(0), data);
+        if (node.jjtGetNumChildren() > 1) {
+            builder.append(", ");
+            accept(node.jjtGetChild(1), data);
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTTryStatement node, Object data) {
+        int num = node.jjtGetNumChildren();
+        builder.append("try ");
+        accept(node.jjtGetChild(0), data);
+        if (num == 2) {
+            builder.append(" finally ");
+            accept(node.jjtGetChild(1), data);
+        } else if (num >= 3) {
+            builder.append(" catch (");
+            accept(node.jjtGetChild(1), data);
+            builder.append(") ");
+            accept(node.jjtGetChild(2), data);
+            if (num == 4) {
+                builder.append(" finally ");
+                accept(node.jjtGetChild(3), data);
+            }
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTTryVar node, Object data) {
+        accept(node.jjtGetChild(0), data);
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTTryWithResourceStatement node, Object data) {
+        int num = node.jjtGetNumChildren();
+        builder.append("try ");
+        builder.append("(");
+        accept(node.jjtGetChild(0), data);
+        builder.append(")");
+        accept(node.jjtGetChild(1), data);
+        if (num == 3) {
+            builder.append(" finally ");
+            accept(node.jjtGetChild(2), data);
+        } else if (num >= 4) {
+            builder.append(" catch (");
+            accept(node.jjtGetChild(2), data);
+            builder.append(") ");
+            accept(node.jjtGetChild(3), data);
+            if (num == 5) {
+                builder.append(" finally ");
+                accept(node.jjtGetChild(4), data);
+            }
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTTryResource node, Object data) {
+        accept(node.jjtGetChild(0), data);
+        if (node.jjtGetNumChildren() > 1) {
+            builder.append("=");
+            accept(node.jjtGetChild(1), data);
+        }
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTGENode node, Object data) {
         return infixChildren(node, " >= ", false, data);
     }
@@ -647,6 +932,12 @@
     }
 
     @Override
+    protected Object visit(ASTExpressionStatement node, Object data) {
+        accept(node.jjtGetChild(0), data);
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTIfStatement node, Object data) {
         final int numChildren = node.jjtGetNumChildren();
         // if (...) ...
@@ -690,26 +981,49 @@
     protected Object visit(ASTJexlScript node, Object data) {
         // if lambda, produce parameters
         if (node instanceof ASTJexlLambda) {
+            boolean expr = false;
+
+            if (node.jjtGetNumChildren() == 1) {
+               JexlNode child = node.jjtGetChild(0);
+
+               if (!(child instanceof ASTBlock))
+                   expr = true;
+            }
+
             JexlNode parent = node.jjtGetParent();
             // use lambda syntax if not assigned
-            boolean named = parent instanceof ASTAssignment;
+            boolean named = parent instanceof ASTAssignment && !expr;
             if (named) {
                 builder.append("function");
             }
-            builder.append('(');
+
             String[] params = node.getParameters();
+
+            if (named || params == null || params.length != 1 || node.isVarArgs())
+                builder.append('(');
+
             if (params != null && params.length > 0) {
                 builder.append(visitParameter(params[0], data));
                 for (int p = 1; p < params.length; ++p) {
                     builder.append(", ");
                     builder.append(visitParameter(params[p], data));
                 }
+
+                if (node.isVarArgs())
+                    builder.append("...");
             }
-            builder.append(')');
+
+            if (named || params == null || params.length != 1 || node.isVarArgs())
+                builder.append(')');
+
             if (named) {
                 builder.append(' ');
             } else {
-                builder.append("->");
+                if (expr) {
+                    builder.append("=>");
+                } else {
+                    builder.append("->");
+                }
             }
             // we will need a block...
         }
@@ -737,6 +1051,11 @@
     }
 
     @Override
+    protected Object visit(ASTIOFNode node, Object data) {
+        return infixChildren(node, " instanceof ", false, data);
+    }
+
+    @Override
     protected Object visit(ASTMapEntry node, Object data) {
         accept(node.jjtGetChild(0), data);
         builder.append(" : ");
@@ -745,9 +1064,16 @@
     }
 
     @Override
+    protected Object visit(ASTMapEnumerationNode node, Object data) {
+        return prefixChild(node, "*:...", data);
+    }
+
+    @Override
     protected Object visit(ASTSetLiteral node, Object data) {
-        int num = node.jjtGetNumChildren();
+        if (node.isImmutable())
+            builder.append("#");
         builder.append("{ ");
+        int num = node.jjtGetNumChildren();
         if (num > 0) {
             accept(node.jjtGetChild(0), data);
             for (int i = 1; i < num; ++i) {
@@ -761,8 +1087,10 @@
 
     @Override
     protected Object visit(ASTMapLiteral node, Object data) {
-        int num = node.jjtGetNumChildren();
+        if (node.isImmutable())
+            builder.append("#");
         builder.append("{ ");
+        int num = node.jjtGetNumChildren();
         if (num > 0) {
             accept(node.jjtGetChild(0), data);
             for (int i = 1; i < num; ++i) {
@@ -777,6 +1105,36 @@
     }
 
     @Override
+    protected Object visit(ASTInlinePropertyArrayEntry node, Object data) {
+        builder.append("[");
+        accept(node.jjtGetChild(0), data);
+        builder.append("] : ");
+        accept(node.jjtGetChild(1), data);
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTInlinePropertyEntry node, Object data) {
+        accept(node.jjtGetChild(0), data);
+        builder.append(" : ");
+        accept(node.jjtGetChild(1), data);
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTInlinePropertyAssignment node, Object data) {
+        int num = node.jjtGetNumChildren();
+        builder.append("{ ");
+        accept(node.jjtGetChild(0), data);
+        for (int i = 1; i < num; ++i) {
+            builder.append(",");
+            accept(node.jjtGetChild(i), data);
+        }
+        builder.append(" }");
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTConstructorNode node, Object data) {
         int num = node.jjtGetNumChildren();
         builder.append("new(");
@@ -790,6 +1148,50 @@
     }
 
     @Override
+    protected Object visit(ASTQualifiedConstructorNode node, Object data) {
+        int num = node.jjtGetNumChildren();
+        builder.append("new ");
+        accept(node.jjtGetChild(0), data);
+        builder.append("(");
+        for (int i = 1; i < num; ++i) {
+            if (i > 1)
+                builder.append(", ");
+            accept(node.jjtGetChild(i), data);
+        }
+        builder.append(")");
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTArrayConstructorNode node, Object data) {
+        int num = node.jjtGetNumChildren();
+        builder.append("new ");
+        accept(node.jjtGetChild(0), data);
+        for (int i = 1; i < num; ++i) {
+            builder.append("[");
+            accept(node.jjtGetChild(i), data);
+            builder.append("]");
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTInitializedArrayConstructorNode node, Object data) {
+        int num = node.jjtGetNumChildren();
+        builder.append("new ");
+        accept(node.jjtGetChild(0), data);
+        builder.append("[]");
+        builder.append("{");
+        for (int i = 1; i < num; ++i) {
+            if (i > 1)
+                builder.append(", ");
+            accept(node.jjtGetChild(i), data);
+        }
+        builder.append("}");
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTFunctionNode node, Object data) {
         int num = node.jjtGetNumChildren();
         if (num == 3) {
@@ -840,6 +1242,21 @@
     }
 
     @Override
+    protected Object visit(ASTShiftLeftNode node, Object data) {
+        return shiftNode(node, " << ", data);
+    }
+
+    @Override
+    protected Object visit(ASTShiftRightNode node, Object data) {
+        return shiftNode(node, " >> ", data);
+    }
+
+    @Override
+    protected Object visit(ASTShiftRightUnsignedNode node, Object data) {
+        return shiftNode(node, " >>> ", data);
+    }
+
+    @Override
     protected Object visit(ASTNENode node, Object data) {
         return infixChildren(node, " != ", false, data);
     }
@@ -901,6 +1318,25 @@
     }
 
     @Override
+    protected Object visit(ASTThrowStatement node, Object data) {
+        builder.append("throw ");
+        accept(node.jjtGetChild(0), data);
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTAssertStatement node, Object data) {
+        builder.append("assert ");
+        accept(node.jjtGetChild(0), data);
+        int num = node.jjtGetNumChildren();
+        if (num > 1) {
+            builder.append(" : ");
+            accept(node.jjtGetChild(1), data);
+        }
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTSizeFunction node, Object data) {
         builder.append("size ");
         accept(node.jjtGetChild(0), data);
@@ -927,22 +1363,32 @@
     }
 
     @Override
+    protected Object visit(ASTClassLiteral node, Object data) {
+        builder.append(node.toString());
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTTernaryNode node, Object data) {
         accept(node.jjtGetChild(0), data);
+        builder.append("? ");
+        accept(node.jjtGetChild(1), data);
         if (node.jjtGetNumChildren() > 2) {
-            builder.append("? ");
-            accept(node.jjtGetChild(1), data);
             builder.append(" : ");
             accept(node.jjtGetChild(2), data);
-        } else {
-            builder.append("?:");
-            accept(node.jjtGetChild(1), data);
-
         }
         return data;
     }
 
     @Override
+    protected Object visit(ASTElvisNode node, Object data) {
+        accept(node.jjtGetChild(0), data);
+        builder.append("?:");
+        accept(node.jjtGetChild(1), data);
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTNullpNode node, Object data) {
         accept(node.jjtGetChild(0), data);
         builder.append("??");
@@ -957,11 +1403,80 @@
     }
 
     @Override
+    protected Object visit(ASTThisNode node, Object data) {
+        check(node, "this", data);
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTUnaryMinusNode node, Object data) {
         return prefixChild(node, "-", data);
     }
 
     @Override
+    protected Object visit(ASTUnaryPlusNode node, Object data) {
+        return prefixChild(node, "+", data);
+    }
+
+    @Override
+    protected Object visit(ASTIncrementNode node, Object data) {
+        return prefixChild(node, "++", data);
+    }
+
+    @Override
+    protected Object visit(ASTDecrementNode node, Object data) {
+        return prefixChild(node, "--", data);
+    }
+
+    @Override
+    protected Object visit(ASTIncrementPostfixNode node, Object data) {
+        return postfixChild(node, "++", data);
+    }
+
+    @Override
+    protected Object visit(ASTDecrementPostfixNode node, Object data) {
+        return postfixChild(node, "--", data);
+    }
+
+    @Override
+    protected Object visit(ASTIndirectNode node, Object data) {
+        return prefixChild(node, "*", data);
+    }
+
+    @Override
+    protected Object visit(ASTPointerNode node, Object data) {
+        builder.append("&");
+        accept(node.jjtGetChild(0), data);
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTEnumerationNode node, Object data) {
+        int num = node.jjtGetNumChildren();
+        if (num == 1) {
+            builder.append("...");
+            accept(node.jjtGetChild(0), data);
+            return data;
+        } else {
+            builder.append("...(");
+            accept(node.jjtGetChild(0), data);
+            builder.append(':');
+            accept(node.jjtGetChild(1), data);
+            builder.append(")");
+            return data;
+        }
+    }
+
+    @Override
+    protected Object visit(ASTEnumerationReference node, Object data) {
+        int num = node.jjtGetNumChildren();
+        for (int i = 0; i < num; ++i) {
+            accept(node.jjtGetChild(i), data);
+        }
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTVar node, Object data) {
         builder.append("var ");
         check(node, node.getName(), data);
@@ -969,7 +1484,18 @@
     }
 
     @Override
+    protected Object visit(ASTExtVar node, Object data) {
+        check(node, node.getName(), data);
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTWhileStatement node, Object data) {
+        String label = node.getLabel();
+        if (label != null) {
+            builder.append(label);
+            builder.append(" : ");
+        }
         builder.append("while (");
         accept(node.jjtGetChild(0), data);
         builder.append(") ");
@@ -983,19 +1509,75 @@
 
     @Override
     protected Object visit(ASTDoWhileStatement node, Object data) {
+        String label = node.getLabel();
+        if (label != null) {
+            builder.append(label);
+            builder.append(" : ");
+        }
         builder.append("do ");
-
         acceptStatement(node.jjtGetChild(0), data);
-
         builder.append(" while (");
-
         accept(node.jjtGetChild(1), data);
-
         builder.append(")");
         return data;
     }
 
     @Override
+    protected Object visit(ASTSynchronizedStatement node, Object data) {
+        builder.append("synchronized (");
+        accept(node.jjtGetChild(0), data);
+        builder.append(") ");
+        if (node.jjtGetNumChildren() > 1) {
+            acceptStatement(node.jjtGetChild(1), data);
+        } else {
+            builder.append(';');
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTSwitchStatement node, Object data) {
+        String label = node.getLabel();
+        if (label != null) {
+            builder.append(label);
+            builder.append(" : ");
+        }
+        builder.append("switch (");
+        accept(node.jjtGetChild(0), data);
+        builder.append(") {");
+        for (int i = 1; i < node.jjtGetNumChildren(); i++)
+            accept(node.jjtGetChild(i), data);
+        builder.append("}");
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTSwitchStatementCase node, Object data) {
+        builder.append("case ");
+        accept(node.jjtGetChild(0), data);
+        builder.append(" : ");
+        if (node.jjtGetNumChildren() > 1) {
+            for (int i = 1; i < node.jjtGetNumChildren(); i++)
+                acceptStatement(node.jjtGetChild(i), data);
+        } else {
+            builder.append(';');
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTSwitchStatementDefault node, Object data) {
+        builder.append("default : ");
+        if (node.jjtGetNumChildren() > 0) {
+            for (int i = 0; i < node.jjtGetNumChildren(); i++)
+                acceptStatement(node.jjtGetChild(i), data);
+        } else {
+            builder.append(';');
+        }
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTSetAddNode node, Object data) {
         return infixChildren(node, " += ", false, data);
     }
@@ -1036,6 +1618,21 @@
     }
 
     @Override
+    protected Object visit(ASTSetShlNode node, Object data) {
+        return infixChildren(node, " <<= ", false, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetSarNode node, Object data) {
+        return infixChildren(node, " >>= ", false, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetShrNode node, Object data) {
+        return infixChildren(node, " >>>= ", false, data);
+    }
+
+    @Override
     protected Object visit(ASTJxltLiteral node, Object data) {
         String img = node.getLiteral().replace("`", "\\`");
         return check(node, "`" + img + "`", data);
@@ -1064,4 +1661,59 @@
         }
         return data;
     }
-}
\ No newline at end of file
+
+    @Override
+    protected Object visit(ASTProjectionNode node, Object data) {
+        int num = node.jjtGetNumChildren();
+        builder.append(".[");
+        for (int i = 0; i < num; ++i) {
+            if (i > 0)
+                builder.append(',');
+            accept(node.jjtGetChild(i), data);
+        }
+        builder.append(']');
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTMapProjectionNode node, Object data) {
+        builder.append(".{");
+        accept(node.jjtGetChild(0), data);
+        builder.append(':');
+        accept(node.jjtGetChild(1), data);
+        builder.append('}');
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTSelectionNode node, Object data) {
+        builder.append(".(");
+        accept(node.jjtGetChild(0), data);
+        builder.append(')');
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTStartCountNode node, Object data) {
+        return prefixChild(node, ">", data);
+    }
+
+    @Override
+    protected Object visit(ASTStopCountNode node, Object data) {
+        return prefixChild(node, "<", data);
+    }
+
+    @Override
+    protected Object visit(ASTReductionNode node, Object data) {
+        int num = node.jjtGetNumChildren();
+        builder.append(".$(");
+        accept(node.jjtGetChild(0), data);
+        if (num > 1) {
+            builder.append(':');
+            accept(node.jjtGetChild(1), data);
+        }
+        builder.append(')');
+        return data;
+    }
+
+}
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 4604c7b..f05dac7 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -107,6 +107,10 @@
      */
     protected final boolean silent;
     /**
+     * Whether this engine evaluates assertions.
+     */
+    protected final boolean assertions;
+    /**
      * Whether expressions evaluated by this engine will throw JexlException.Cancel (true) or return null (false) when
      * interrupted.
      * Default is true when not silent and strict.
@@ -166,6 +170,7 @@
         this.strict = conf.strict() == null ? true : conf.strict();
         this.safe = conf.safe() == null ? false : conf.safe();
         this.silent = conf.silent() == null ? false : conf.silent();
+        this.assertions = conf.assertions() == null ? false : conf.assertions();
         this.cancellable = conf.cancellable() == null ? !silent && strict : conf.cancellable();
         this.debug = conf.debug() == null ? true : conf.debug();
         this.stackOverflow = conf.stackOverflow() > 0? conf.stackOverflow() : Integer.MAX_VALUE;
@@ -241,6 +246,11 @@
     }
 
     @Override
+    public boolean isAssertions() {
+        return assertions;
+    }
+
+    @Override
     public boolean isCancellable() {
         return this.cancellable;
     }
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 76d3b68..9812819 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -35,7 +35,9 @@
 import org.apache.commons.jexl3.parser.ASTAnnotation;
 import org.apache.commons.jexl3.parser.ASTArguments;
 import org.apache.commons.jexl3.parser.ASTArrayAccess;
+import org.apache.commons.jexl3.parser.ASTArrayConstructorNode;
 import org.apache.commons.jexl3.parser.ASTArrayLiteral;
+import org.apache.commons.jexl3.parser.ASTAssertStatement;
 import org.apache.commons.jexl3.parser.ASTAssignment;
 import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
 import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
@@ -43,37 +45,62 @@
 import org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
 import org.apache.commons.jexl3.parser.ASTBlock;
 import org.apache.commons.jexl3.parser.ASTBreak;
+import org.apache.commons.jexl3.parser.ASTClassLiteral;
 import org.apache.commons.jexl3.parser.ASTConstructorNode;
 import org.apache.commons.jexl3.parser.ASTContinue;
+import org.apache.commons.jexl3.parser.ASTDecrementNode;
+import org.apache.commons.jexl3.parser.ASTDecrementPostfixNode;
 import org.apache.commons.jexl3.parser.ASTDivNode;
 import org.apache.commons.jexl3.parser.ASTDoWhileStatement;
 import org.apache.commons.jexl3.parser.ASTEQNode;
 import org.apache.commons.jexl3.parser.ASTERNode;
 import org.apache.commons.jexl3.parser.ASTEWNode;
+import org.apache.commons.jexl3.parser.ASTElvisNode;
 import org.apache.commons.jexl3.parser.ASTEmptyFunction;
 import org.apache.commons.jexl3.parser.ASTEmptyMethod;
-import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
+import org.apache.commons.jexl3.parser.ASTEnumerationNode;
+import org.apache.commons.jexl3.parser.ASTEnumerationReference;
+import org.apache.commons.jexl3.parser.ASTExpressionStatement;
+import org.apache.commons.jexl3.parser.ASTExtVar;
 import org.apache.commons.jexl3.parser.ASTFalseNode;
+import org.apache.commons.jexl3.parser.ASTForStatement;
+import org.apache.commons.jexl3.parser.ASTForInitializationNode;
+import org.apache.commons.jexl3.parser.ASTForTerminationNode;
+import org.apache.commons.jexl3.parser.ASTForIncrementNode;
 import org.apache.commons.jexl3.parser.ASTForeachStatement;
+import org.apache.commons.jexl3.parser.ASTForeachVar;
 import org.apache.commons.jexl3.parser.ASTFunctionNode;
 import org.apache.commons.jexl3.parser.ASTGENode;
 import org.apache.commons.jexl3.parser.ASTGTNode;
 import org.apache.commons.jexl3.parser.ASTIdentifier;
 import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
 import org.apache.commons.jexl3.parser.ASTIdentifierAccessJxlt;
+import org.apache.commons.jexl3.parser.ASTIncrementNode;
+import org.apache.commons.jexl3.parser.ASTIncrementPostfixNode;
+import org.apache.commons.jexl3.parser.ASTIndirectNode;
+import org.apache.commons.jexl3.parser.ASTInitializedArrayConstructorNode;
+import org.apache.commons.jexl3.parser.ASTInlinePropertyAssignment;
+import org.apache.commons.jexl3.parser.ASTInlinePropertyArrayEntry;
+import org.apache.commons.jexl3.parser.ASTInlinePropertyEntry;
 import org.apache.commons.jexl3.parser.ASTIfStatement;
+import org.apache.commons.jexl3.parser.ASTIOFNode;
+import org.apache.commons.jexl3.parser.ASTISNode;
 import org.apache.commons.jexl3.parser.ASTJexlLambda;
 import org.apache.commons.jexl3.parser.ASTJexlScript;
 import org.apache.commons.jexl3.parser.ASTJxltLiteral;
 import org.apache.commons.jexl3.parser.ASTLENode;
 import org.apache.commons.jexl3.parser.ASTLTNode;
 import org.apache.commons.jexl3.parser.ASTMapEntry;
+import org.apache.commons.jexl3.parser.ASTMapEnumerationNode;
 import org.apache.commons.jexl3.parser.ASTMapLiteral;
+import org.apache.commons.jexl3.parser.ASTMapProjectionNode;
 import org.apache.commons.jexl3.parser.ASTMethodNode;
 import org.apache.commons.jexl3.parser.ASTModNode;
 import org.apache.commons.jexl3.parser.ASTMulNode;
+import org.apache.commons.jexl3.parser.ASTMultipleAssignment;
 import org.apache.commons.jexl3.parser.ASTNENode;
 import org.apache.commons.jexl3.parser.ASTNEWNode;
+import org.apache.commons.jexl3.parser.ASTNINode;
 import org.apache.commons.jexl3.parser.ASTNRNode;
 import org.apache.commons.jexl3.parser.ASTNSWNode;
 import org.apache.commons.jexl3.parser.ASTNotNode;
@@ -81,12 +108,18 @@
 import org.apache.commons.jexl3.parser.ASTNullpNode;
 import org.apache.commons.jexl3.parser.ASTNumberLiteral;
 import org.apache.commons.jexl3.parser.ASTOrNode;
+import org.apache.commons.jexl3.parser.ASTPointerNode;
+import org.apache.commons.jexl3.parser.ASTProjectionNode;
+import org.apache.commons.jexl3.parser.ASTQualifiedConstructorNode;
 import org.apache.commons.jexl3.parser.ASTRangeNode;
+import org.apache.commons.jexl3.parser.ASTReductionNode;
 import org.apache.commons.jexl3.parser.ASTReference;
 import org.apache.commons.jexl3.parser.ASTReferenceExpression;
 import org.apache.commons.jexl3.parser.ASTRegexLiteral;
+import org.apache.commons.jexl3.parser.ASTRemove;
 import org.apache.commons.jexl3.parser.ASTReturnStatement;
 import org.apache.commons.jexl3.parser.ASTSWNode;
+import org.apache.commons.jexl3.parser.ASTSelectionNode;
 import org.apache.commons.jexl3.parser.ASTSetAddNode;
 import org.apache.commons.jexl3.parser.ASTSetAndNode;
 import org.apache.commons.jexl3.parser.ASTSetDivNode;
@@ -95,25 +128,53 @@
 import org.apache.commons.jexl3.parser.ASTSetMultNode;
 import org.apache.commons.jexl3.parser.ASTSetOrNode;
 import org.apache.commons.jexl3.parser.ASTSetSubNode;
+import org.apache.commons.jexl3.parser.ASTSetShlNode;
+import org.apache.commons.jexl3.parser.ASTSetSarNode;
+import org.apache.commons.jexl3.parser.ASTSetShrNode;
 import org.apache.commons.jexl3.parser.ASTSetXorNode;
+import org.apache.commons.jexl3.parser.ASTShiftLeftNode;
+import org.apache.commons.jexl3.parser.ASTShiftRightNode;
+import org.apache.commons.jexl3.parser.ASTShiftRightUnsignedNode;
 import org.apache.commons.jexl3.parser.ASTSizeFunction;
 import org.apache.commons.jexl3.parser.ASTSizeMethod;
+import org.apache.commons.jexl3.parser.ASTStartCountNode;
+import org.apache.commons.jexl3.parser.ASTStopCountNode;
 import org.apache.commons.jexl3.parser.ASTStringLiteral;
 import org.apache.commons.jexl3.parser.ASTSubNode;
+import org.apache.commons.jexl3.parser.ASTSwitchStatement;
+import org.apache.commons.jexl3.parser.ASTSwitchStatementCase;
+import org.apache.commons.jexl3.parser.ASTSwitchStatementDefault;
+import org.apache.commons.jexl3.parser.ASTSynchronizedStatement;
 import org.apache.commons.jexl3.parser.ASTTernaryNode;
+import org.apache.commons.jexl3.parser.ASTThisNode;
+import org.apache.commons.jexl3.parser.ASTThrowStatement;
 import org.apache.commons.jexl3.parser.ASTTrueNode;
+import org.apache.commons.jexl3.parser.ASTTryStatement;
+import org.apache.commons.jexl3.parser.ASTTryVar;
+import org.apache.commons.jexl3.parser.ASTTryWithResourceStatement;
+import org.apache.commons.jexl3.parser.ASTTryResource;
 import org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
+import org.apache.commons.jexl3.parser.ASTUnaryPlusNode;
 import org.apache.commons.jexl3.parser.ASTVar;
 import org.apache.commons.jexl3.parser.ASTWhileStatement;
 import org.apache.commons.jexl3.parser.JexlNode;
 import org.apache.commons.jexl3.parser.Node;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
 import java.util.Map;
+import java.util.AbstractMap;
+import java.util.NoSuchElementException;
 import java.util.concurrent.Callable;
 
+import java.lang.reflect.Array;
+
+import org.apache.commons.jexl3.JxltEngine;
+
 
 /**
  * An interpreter of JEXL syntax.
@@ -176,7 +237,7 @@
         functions = ii.functions;
         functors = ii.functors;
     }
-        
+
     /**
      * Swaps the current thread local interpreter.
      * @param inter the interpreter or null
@@ -240,10 +301,8 @@
         } finally {
             synchronized(this) {
                 if (functors != null) {
-                    if (AUTOCLOSEABLE != null) {
-                        for (Object functor : functors.values()) {
-                            closeIfSupported(functor);
-                        }
+                    for (Object functor : functors.values()) {
+                        closeIfSupported(functor);
                     }
                     functors.clear();
                     functors = null;
@@ -297,26 +356,27 @@
             if (namespace instanceof JexlContext.NamespaceFunctor) {
                 functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
             } 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 {
+                    // 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;
+                        }
                     }
-                }
-                if (functor == null) {
-                    JexlMethod ctor = uberspect.getConstructor(namespace, context);
-                    if (ctor != null) {
-                        try {
+                    if (functor == null) {
+                        JexlMethod ctor = uberspect.getConstructor(namespace, context);
+                        if (ctor != null) {
                             functor = ctor.invoke(namespace, context);
                             if (cacheable && ctor.isCacheable()) {
                                 node.jjtSetValue(ctor);
                             }
-                        } catch (Exception xinst) {
-                            throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
                         }
                     }
+                } catch (Exception xinst) {
+                    throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
                 }
+
             }
             // got a functor, store it and return it
             if (functor != null) {
@@ -405,6 +465,45 @@
     }
 
     @Override
+    protected Object visit(ASTShiftLeftNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            Object result = operators.tryOverload(node, JexlOperator.SHL, left, right);
+            return result != JexlEngine.TRY_FAILED ? result : arithmetic.leftShift(left, right);
+        } catch (ArithmeticException xrt) {
+            JexlNode xnode = findNullOperand(xrt, node, left, right);
+            throw new JexlException(xnode, "<< error", xrt);
+        }
+    }
+
+    @Override
+    protected Object visit(ASTShiftRightNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            Object result = operators.tryOverload(node, JexlOperator.SAR, left, right);
+            return result != JexlEngine.TRY_FAILED ? result : arithmetic.rightShift(left, right);
+        } catch (ArithmeticException xrt) {
+            JexlNode xnode = findNullOperand(xrt, node, left, right);
+            throw new JexlException(xnode, ">> error", xrt);
+        }
+    }
+
+    @Override
+    protected Object visit(ASTShiftRightUnsignedNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            Object result = operators.tryOverload(node, JexlOperator.SHR, left, right);
+            return result != JexlEngine.TRY_FAILED ? result : arithmetic.rightShiftUnsigned(left, right);
+        } catch (ArithmeticException xrt) {
+            JexlNode xnode = findNullOperand(xrt, node, left, right);
+            throw new JexlException(xnode, ">>> error", xrt);
+        }
+    }
+
+    @Override
     protected Object visit(ASTBitwiseAndNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
@@ -441,6 +540,20 @@
     }
 
     @Override
+    protected Object visit(ASTISNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return left == right;
+    }
+
+    @Override
+    protected Object visit(ASTNINode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return left != right;
+    }
+
+    @Override
     protected Object visit(ASTEQNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
@@ -568,6 +681,26 @@
     }
 
     @Override
+    protected Object visit(ASTIOFNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        if (left != null) {
+            ASTClassLiteral right = (ASTClassLiteral) node.jjtGetChild(1);
+            Class k = left.getClass();
+            Class type = right.getLiteral();
+            int i = right.getArray();
+            while (i-- > 0) {
+                if (k.isArray()) {
+                    k = k.getComponentType();
+                } else {
+                    return false;
+                }
+            }
+            return type != null ? type.isAssignableFrom(k) : true;
+        }
+        return false;
+    }
+
+    @Override
     protected Object visit(ASTRangeNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
@@ -600,6 +733,238 @@
     }
 
     @Override
+    protected Object visit(ASTUnaryPlusNode node, Object data) {
+        JexlNode valNode = node.jjtGetChild(0);
+        Object val = valNode.jjtAccept(this, data);
+        try {
+            Object result = operators.tryOverload(node, JexlOperator.CONFIRM, val);
+            if (result != JexlEngine.TRY_FAILED) {
+                return result;
+            }
+            Object number = arithmetic.confirm(val);
+            // attempt to recoerce to literal class
+            if (valNode instanceof ASTNumberLiteral && number instanceof Number) {
+                number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass());
+            }
+            return number;
+        } catch (ArithmeticException xrt) {
+            throw new JexlException(valNode, "+ error", xrt);
+        }
+    }
+
+    @Override
+    protected Object visit(ASTIndirectNode node, Object data) {
+        Object val = node.jjtGetChild(0).jjtAccept(this, data);
+        if (val == null) {
+            if (isStrictEngine()) {
+                throw new JexlException(node, "Null dereference", null);
+            } else {
+                return null;
+            }
+        }
+        if (val instanceof Pointer)
+            return ((Pointer) val).get();
+
+        return operators.indirect(node, val);
+    }
+
+
+    /**
+     * Declares pointer dereference operators
+     */
+    public interface Pointer {
+        public Object get();
+        public void set(Object right);
+    }
+
+    /**
+     * Pointer to a local variable.
+     *
+     */
+    public class VarPointer implements Pointer {
+
+        protected int symbol;
+
+        protected VarPointer(int symbol) {
+            this.symbol = symbol;
+        }
+
+        @Override
+        public Object get() {
+            return frame.get(symbol);
+        }
+
+        @Override
+        public void set(Object value) {
+            frame.set(symbol, value);
+        }
+    }
+
+    /**
+     * Pointer to a context variable.
+     *
+     */
+    public class ContextVarPointer implements Pointer {
+
+        protected String name;
+
+        protected ContextVarPointer(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public Object get() {
+            return context.get(name);
+        }
+
+        @Override
+        public void set(Object value) {
+            context.set(name, value);
+        }
+    }
+
+    /**
+     * Pointer to a bean property.
+     *
+     */
+    public class PropertyPointer implements Pointer {
+
+        protected JexlNode propertyNode;
+        protected Object object;
+        protected String property;
+
+        protected PropertyPointer(JexlNode node, Object object, String property) {
+            this.propertyNode = node;
+            this.object = object;
+            this.property = property;
+        }
+
+        @Override
+        public Object get() {
+            return getAttribute(object, property, propertyNode);
+        }
+
+        @Override
+        public void set(Object value) {
+            setAttribute(object, property, value, propertyNode, JexlOperator.PROPERTY_SET);
+        }
+    }
+
+    /**
+     * Pointer to an indexed element.
+     *
+     */
+    public class ArrayPointer implements Pointer {
+
+        protected JexlNode propertyNode;
+        protected Object object;
+        protected Object index;
+
+        protected ArrayPointer(JexlNode node, Object object, Object index) {
+            this.propertyNode = node;
+            this.object = object;
+            this.index = index;
+        }
+
+        @Override
+        public Object get() {
+            return getAttribute(object, index, propertyNode);
+        }
+
+        @Override
+        public void set(Object value) {
+            setAttribute(object, index, value, propertyNode, JexlOperator.ARRAY_SET);
+        }
+    }
+
+    @Override
+    protected Object visit(ASTPointerNode node, Object data) {
+        JexlNode left = node.jjtGetChild(0);
+        if (left instanceof ASTIdentifier) {
+            ASTIdentifier var = (ASTIdentifier) left;
+            if (data != null) {
+                return new PropertyPointer(var, data, var.getName());
+            } else {
+                int symbol = var.getSymbol();
+                if (symbol >= 0) {
+                    return new VarPointer(symbol);
+                } else {
+                    return new ContextVarPointer(var.getName());
+                }
+            }
+        } else {
+            Object object = data;
+            int last = left.jjtGetNumChildren() - 1;
+            boolean antish = true;
+            // 1: follow children till penultimate, resolve dot/array
+            JexlNode objectNode = null;
+            StringBuilder ant = null;
+            int v = 1;
+            // start at 1 if symbol
+            for (int c = 0; c < last; ++c) {
+                objectNode = left.jjtGetChild(c);
+                object = objectNode.jjtAccept(this, object);
+                if (object != null) {
+                    // disallow mixing antish variable & bean with same root; avoid ambiguity
+                    antish = false;
+                } else if (antish) {
+                    if (ant == null) {
+                        JexlNode first = left.jjtGetChild(0);
+                        if (first instanceof ASTIdentifier && ((ASTIdentifier) first).getSymbol() < 0) {
+                            ant = new StringBuilder(((ASTIdentifier) first).getName());
+                        } else {
+                            break;
+                        }
+                    }
+                    for (; v <= c; ++v) {
+                        JexlNode child = left.jjtGetChild(v);
+                        if (child instanceof ASTIdentifierAccess) {
+                            ant.append('.');
+                            ant.append(((ASTIdentifierAccess) objectNode).getName());
+                        } else {
+                            break;
+                        }
+                    }
+                    object = context.get(ant.toString());
+                } else {
+                    throw new JexlException(objectNode, "illegal address");
+                }
+            }
+            // 2: last objectNode will perform assignement in all cases
+            JexlNode propertyNode = left.jjtGetChild(last);
+            if (propertyNode instanceof ASTIdentifierAccess) {
+                String property = String.valueOf(evalIdentifier((ASTIdentifierAccess) propertyNode));
+                if (object == null) {
+                    // deal with antish variable
+                    if (ant != null) {
+                        if (last > 0) {
+                            ant.append('.');
+                        }
+                        ant.append(property);
+                        return new ContextVarPointer(ant.toString());
+                    } else {
+                        return new ContextVarPointer(property);
+                    }
+                }
+                return new PropertyPointer(propertyNode, object, property);
+            } else if (propertyNode instanceof ASTArrayAccess) {
+                // can have multiple nodes - either an expression, integer literal or reference
+                int numChildren = propertyNode.jjtGetNumChildren() - 1;
+                for (int i = 0; i < numChildren; i++) {
+                    JexlNode nindex = propertyNode.jjtGetChild(i);
+                    Object index = nindex.jjtAccept(this, null);
+                    object = getAttribute(object, index, nindex);
+                }
+                propertyNode = propertyNode.jjtGetChild(numChildren);
+                Object property = propertyNode.jjtAccept(this, null);
+                return new ArrayPointer(propertyNode, object, property);
+            } else {
+                throw new JexlException(objectNode, "illegal pointer form");
+            }
+        }
+    }
+
+    @Override
     protected Object visit(ASTBitwiseComplNode node, Object data) {
         Object arg = node.jjtGetChild(0).jjtAccept(this, data);
         try {
@@ -622,6 +987,116 @@
     }
 
     @Override
+    protected Object visit(ASTEnumerationReference node, Object data) {
+        cancelCheck(node);
+        final int numChildren = node.jjtGetNumChildren();
+        // pass first piece of data in and loop through children
+        Object object = data;
+        JexlNode objectNode = null;
+        for (int c = 0; c < numChildren; c++) {
+            objectNode = node.jjtGetChild(c);
+            // attempt to evaluate the property within the object)
+            object = objectNode.jjtAccept(this, object);
+            cancelCheck(node);
+        }
+        return object;
+    }
+
+    @Override
+    protected Object visit(ASTEnumerationNode node, Object data) {
+        final int numChildren = node.jjtGetNumChildren();
+        if (numChildren == 1) {
+            JexlNode valNode = node.jjtGetChild(0);
+            Object iterableValue = valNode.jjtAccept(this, data);
+
+            if (iterableValue != null) {
+                Object forEach = operators.tryOverload(node, JexlOperator.FOR_EACH_INDEXED, iterableValue);
+                Iterator<?> itemsIterator = forEach instanceof Iterator
+                                      ? (Iterator<?>) forEach
+                                      : uberspect.getIndexedIterator(iterableValue);
+                return itemsIterator;
+            } else {
+                return null;
+            }
+        } else {
+            Object initialValue = node.jjtGetChild(0).jjtAccept(this, data);
+
+            ASTJexlLambda generator = (ASTJexlLambda) node.jjtGetChild(1);
+            return new GeneratorIterator(initialValue, generator);
+        }
+    }
+
+    public class GeneratorIterator implements Iterator<Object> {
+
+        protected final ASTJexlLambda node;
+        protected final Closure generator;
+
+        protected int i;
+
+        protected Object value;
+
+        protected GeneratorIterator(Object initialValue, ASTJexlLambda node) {
+            this.node = node;
+            generator = new Closure(Interpreter.this, node);
+
+            i = 0;
+            value = initialValue;
+        }
+
+        protected void nextValue() {
+
+            i += 1;
+
+            int argCount = node.getArgCount();
+
+            Object[] argv = null;
+
+            if (argCount == 0) {
+                argv = EMPTY_PARAMS;
+            } else if (argCount == 1) {
+                argv = new Object[] {value};
+            } else if (argCount == 2) {
+                argv = new Object[] {i, value};
+            }
+
+            value = generator.execute(null, argv);
+        }
+
+        @Override
+        public boolean hasNext() {
+            return value != null;
+        }
+
+        @Override
+        public Object next() {
+            cancelCheck(node);
+
+            if (value == null)
+                throw new NoSuchElementException();
+
+            Object result = value;
+
+            nextValue();
+
+            return result;
+        }
+
+        @Override
+        public void remove() throws UnsupportedOperationException {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+
+    @Override
+    protected Object visit(ASTExpressionStatement node, Object data) {
+        cancelCheck(node);
+        Object result = node.jjtGetChild(0).jjtAccept(this, data);
+        return result;
+    }
+
+    @Override
     protected Object visit(ASTIfStatement node, Object data) {
         int n = 0;
         final int numChildren = node.jjtGetNumChildren();
@@ -652,8 +1127,17 @@
         int numChildren = node.jjtGetNumChildren();
         Object result = null;
         for (int i = 0; i < numChildren; i++) {
-            cancelCheck(node);
-            result = node.jjtGetChild(i).jjtAccept(this, data);
+            try {
+                cancelCheck(node);
+                result = node.jjtGetChild(i).jjtAccept(this, data);
+            } catch (JexlException.Break stmtBreak) {
+                String target = stmtBreak.getLabel();
+                if (target != null && target.equals(node.getLabel())) {
+                    break;
+                } else {
+                    throw stmtBreak;
+                }
+            }
         }
         return result;
     }
@@ -667,63 +1151,356 @@
 
     @Override
     protected Object visit(ASTContinue node, Object data) {
-        throw new JexlException.Continue(node);
+        throw new JexlException.Continue(node, node.getLabel());
+    }
+
+    @Override
+    protected Object visit(ASTRemove node, Object data) {
+        throw new JexlException.Remove(node, node.getLabel());
     }
 
     @Override
     protected Object visit(ASTBreak node, Object data) {
-        throw new JexlException.Break(node);
+        throw new JexlException.Break(node, node.getLabel());
+    }
+
+    @Override
+    protected Object visit(ASTForStatement node, Object data) {
+        // Initialize for-loop
+        Object result = node.jjtGetChild(0).jjtAccept(this, data);
+        boolean when = false;
+        while (when = (Boolean) node.jjtGetChild(1).jjtAccept(this, data)) {
+            try {
+                // Execute loop body
+                if (node.jjtGetNumChildren() > 3) 
+                    result = node.jjtGetChild(3).jjtAccept(this, data);
+            } catch (JexlException.Break stmtBreak) {
+                String target = stmtBreak.getLabel();
+                if (target == null || target.equals(node.getLabel())) {
+                    break;
+                } else {
+                    throw stmtBreak;
+                }
+            } catch (JexlException.Continue stmtContinue) {
+                String target = stmtContinue.getLabel();
+                if (target != null && !target.equals(node.getLabel())) {
+                    throw stmtContinue;
+                }
+                // continue;
+            }
+            // for-increment node
+            result = node.jjtGetChild(2).jjtAccept(this, data);
+        }
+        return result;
+    }
+
+    @Override
+    protected Object visit(ASTForInitializationNode node, Object data) {
+        Object result = null;
+        if (node.jjtGetNumChildren() > 0) 
+            result = node.jjtGetChild(0).jjtAccept(this, data);
+        return result;
+    }
+
+    @Override
+    protected Object visit(ASTForTerminationNode node, Object data) {
+        Boolean result = Boolean.TRUE;
+        if (node.jjtGetNumChildren() > 0) 
+            result = arithmetic.toBoolean(node.jjtGetChild(0).jjtAccept(this, data));
+        return result;
+    }
+
+    @Override
+    protected Object visit(ASTForIncrementNode node, Object data) {
+        Object result = null;
+        if (node.jjtGetNumChildren() > 0) 
+            result = node.jjtGetChild(0).jjtAccept(this, data);
+        return result;
     }
 
     @Override
     protected Object visit(ASTForeachStatement node, Object data) {
         Object result = null;
         /* first objectNode is the loop variable */
-        ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
+        ASTForeachVar loopReference = (ASTForeachVar) 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 on and a statement to execute
         if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
             /* third objectNode is the statement to execute */
             JexlNode statement = node.jjtGetChild(2);
-            // get an iterator for the collection/array etc via the introspector.
-            Object forEach = null;
-            try {
-                forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, iterableValue);
+            if (loopReference.jjtGetNumChildren() > 1) {
+
+                ASTIdentifier loopValueVariable = (ASTIdentifier) loopReference.jjtGetChild(1);
+
+                int symbol = loopVariable.getSymbol();
+                int valueSymbol = loopValueVariable.getSymbol();
+
+                // get an iterator for the collection/array etc via the introspector.
+                Object forEach = operators.tryOverload(node, JexlOperator.FOR_EACH_INDEXED, iterableValue);
                 Iterator<?> itemsIterator = forEach instanceof Iterator
-                                            ? (Iterator<?>) forEach
-                                            : uberspect.getIterator(iterableValue);
+                                        ? (Iterator<?>) forEach
+                                        : uberspect.getIndexedIterator(iterableValue);
+
+                int i = -1;
+
                 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);
-                        } else {
-                            frame.set(symbol, value);
+                    try {
+                        while (itemsIterator.hasNext()) {
+                            cancelCheck(node);
+                            i += 1;
+                            // set loopVariable to value of iterator
+                            Object value = itemsIterator.next();
+
+                            if (value instanceof Map.Entry<?,?>) {
+                                Map.Entry<?,?> entry = (Map.Entry<?,?>) value;
+                                if (symbol < 0) {
+                                    context.set(loopVariable.getName(), entry.getKey());
+                                } else {
+                                    frame.set(symbol, entry.getKey());
+                                }
+                                if (valueSymbol < 0) {
+                                    context.set(loopValueVariable.getName(), entry.getValue());
+                                } else {
+                                    frame.set(valueSymbol, entry.getValue());
+                                }
+                            } else {
+                                if (symbol < 0) {
+                                    context.set(loopVariable.getName(), i);
+                                } else {
+                                    frame.set(symbol, i);
+                                }
+                                if (valueSymbol < 0) {
+                                    context.set(loopValueVariable.getName(), value);
+                                } else {
+                                    frame.set(valueSymbol, value);
+                                }
+                            }
+
+                            try {
+                                // execute statement
+                                result = statement.jjtAccept(this, data);
+                            } catch (JexlException.Break stmtBreak) {
+                                String target = stmtBreak.getLabel();
+                                if (target == null || target.equals(node.getLabel())) {
+                                    break;
+                                } else {
+                                    throw stmtBreak;
+                                }
+                            } catch (JexlException.Continue stmtContinue) {
+                                String target = stmtContinue.getLabel();
+                                if (target != null && !target.equals(node.getLabel())) {
+                                    throw stmtContinue;
+                                }
+                                // continue
+                            } catch (JexlException.Remove stmtRemove) {
+                                String target = stmtRemove.getLabel();
+                                if (target != null && !target.equals(node.getLabel())) {
+                                    throw stmtRemove;
+                                }
+                                itemsIterator.remove();
+                                i -= 1;
+                                // and continue
+                            }
                         }
-                        try {
-                            // execute statement
-                            result = statement.jjtAccept(this, data);
-                        } catch (JexlException.Break stmtBreak) {
-                            break;
-                        } catch (JexlException.Continue stmtContinue) {
-                            //continue;
-                        }
+                    } finally {
+                        // closeable iterator handling
+                        closeIfSupported(itemsIterator);
                     }
                 }
-            } finally {
-                //  closeable iterator handling
-                closeIfSupported(forEach);
+
+            } else {
+                int symbol = loopVariable.getSymbol();
+
+                // get an iterator for the collection/array etc via the introspector.
+                Object forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, iterableValue);
+                Iterator<?> itemsIterator = forEach instanceof Iterator
+                                        ? (Iterator<?>) forEach
+                                        : uberspect.getIterator(iterableValue);
+                if (itemsIterator != null) {
+                    try {
+                        while (itemsIterator.hasNext()) {
+                            cancelCheck(node);
+                            // set loopVariable to value of iterator
+                            Object value = itemsIterator.next();
+                            if (symbol < 0) {
+                                context.set(loopVariable.getName(), value);
+                            } else {
+                                frame.set(symbol, value);
+                            }
+                            try {
+                                // execute statement
+                                result = statement.jjtAccept(this, data);
+                            } catch (JexlException.Break stmtBreak) {
+                                String target = stmtBreak.getLabel();
+                                if (target == null || target.equals(node.getLabel())) {
+                                    break;
+                                } else {
+                                    throw stmtBreak;
+                                }
+                            } catch (JexlException.Continue stmtContinue) {
+                                String target = stmtContinue.getLabel();
+                                if (target != null && !target.equals(node.getLabel())) {
+                                    throw stmtContinue;
+                                }
+                                // continue
+                            } catch (JexlException.Remove stmtRemove) {
+                                String target = stmtRemove.getLabel();
+                                if (target != null && !target.equals(node.getLabel())) {
+                                    throw stmtRemove;
+                                }
+                                itemsIterator.remove();
+                                // and continue
+                            }
+                        }
+                    } finally {
+                        // closeable iterator handling
+                        closeIfSupported(itemsIterator);
+                    }
+                }
             }
         }
         return result;
     }
 
     @Override
+    protected Object visit(ASTForeachVar node, Object data) {
+        return null;
+    }
+
+    @Override
+    protected Object visit(ASTTryStatement node, Object data) {
+        Object result = null;
+        int num = node.jjtGetNumChildren();
+        try {
+            // execute try block
+            result = node.jjtGetChild(0).jjtAccept(this, data);
+        } catch (JexlException.Break e) {
+            throw e;
+        } catch (JexlException.Continue e) {
+            throw e;
+        } catch (JexlException.Remove e) {
+            throw e;
+        } catch (JexlException.Return e) {
+            throw e;
+        } catch(JexlException.Cancel e) {
+            throw e;
+        } catch (Throwable t) {
+            // if there is no catch block just rethrow
+            if (num < 3) 
+                throw t;
+            // Set catch variable
+            node.jjtGetChild(1).jjtAccept(this, t);
+            // execute catch block
+            node.jjtGetChild(2).jjtAccept(this, data);
+        } finally {
+            // execute finally block if any
+            if (num == 2) {
+                node.jjtGetChild(1).jjtAccept(this, data);
+            } else if (num == 4) {
+                node.jjtGetChild(3).jjtAccept(this, data);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    protected Object visit(ASTTryWithResourceStatement node, Object data) {
+        Object result = null;
+        int num = node.jjtGetNumChildren();
+        try {
+            ASTTryResource resReference = (ASTTryResource) node.jjtGetChild(0);
+            // Last child is expression that returns the resource
+            Object r = resReference.jjtGetChild(resReference.jjtGetNumChildren() - 1).jjtAccept(this, data);
+            // get a resource manager for the resource via the introspector
+            Object rman = operators.tryOverload(node, JexlOperator.TRY_WITH, r);
+            if (JexlEngine.TRY_FAILED != rman)
+                r = rman;
+            if (resReference.jjtGetNumChildren() == 2) {
+               // Set variable
+               resReference.jjtGetChild(0).jjtAccept(this, r);
+            }
+            try (ResourceManager rm = new ResourceManager(r)) {
+                // execute try block
+                result = node.jjtGetChild(1).jjtAccept(this, data);
+            }
+        } catch (JexlException.Break e) {
+            throw e;
+        } catch (JexlException.Continue e) {
+            throw e;
+        } catch (JexlException.Remove e) {
+            throw e;
+        } catch (JexlException.Return e) {
+            throw e;
+        } catch(JexlException.Cancel e) {
+            throw e;
+        } catch (Throwable t) {
+            // if there is no catch block just rethrow
+            if (num < 4) 
+                InterpreterBase.<RuntimeException>doThrow(t);
+            // set catch variable
+            node.jjtGetChild(2).jjtAccept(this, t);
+            // execute catch block
+            node.jjtGetChild(3).jjtAccept(this, data);
+        } finally {
+            // execute finally block if any
+            if (num == 3) {
+                node.jjtGetChild(2).jjtAccept(this, data);
+            } else if (num == 5) {
+                node.jjtGetChild(4).jjtAccept(this, data);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    protected Object visit(ASTTryVar node, Object data) {
+        ASTIdentifier variable = (ASTIdentifier) node.jjtGetChild(0);
+        int symbol = variable.getSymbol();
+        if (symbol < 0) {
+            context.set(variable.getName(), data);
+        } else {
+            frame.set(symbol, data);
+        }
+        return null;
+    }
+
+    @Override
+    protected Object visit(ASTTryResource node, Object data) {
+        return null;
+    }
+
+    @Override
+    protected Object visit(ASTThrowStatement node, Object data) {
+        cancelCheck(node);
+        Object val = node.jjtGetChild(0).jjtAccept(this, data);
+        if (val instanceof Throwable)
+            InterpreterBase.<RuntimeException>doThrow((Throwable) val);
+        throw new RuntimeException(arithmetic.toString(val));
+    }
+
+    @Override
+    protected Object visit(ASTAssertStatement node, Object data) {
+        if (isAssertions()) {
+            cancelCheck(node);
+            boolean test = arithmetic.toBoolean(node.jjtGetChild(0).jjtAccept(this, data));
+            if (!test) {
+                if (node.jjtGetNumChildren() > 1) {
+                    Object val = node.jjtGetChild(1).jjtAccept(this, data);
+                    throw new AssertionError(val);
+                } else {
+                    throw new AssertionError();
+                }
+            }
+        }
+        return null;
+    }
+ 
+    @Override
     protected Object visit(ASTWhileStatement node, Object data) {
         Object result = null;
         /* first objectNode is the expression */
@@ -735,9 +1512,18 @@
                     // execute statement
                     result = node.jjtGetChild(1).jjtAccept(this, data);
                 } catch (JexlException.Break stmtBreak) {
-                    break;
+                    String target = stmtBreak.getLabel();
+                    if (target == null || target.equals(node.getLabel())) {
+                        break;
+                    } else {
+                        throw stmtBreak;
+                    }
                 } catch (JexlException.Continue stmtContinue) {
-                    //continue;
+                    String target = stmtContinue.getLabel();
+                    if (target != null && !target.equals(node.getLabel())) {
+                        throw stmtContinue;
+                    }
+                    // continue
                 }
             }
         }
@@ -756,17 +1542,107 @@
                 // execute statement
                 result = node.jjtGetChild(0).jjtAccept(this, data);
             } catch (JexlException.Break stmtBreak) {
-                break;
+                String target = stmtBreak.getLabel();
+                if (target == null || target.equals(node.getLabel())) {
+                    break;
+                } else {
+                    throw stmtBreak;
+                }
             } catch (JexlException.Continue stmtContinue) {
-                //continue;
+                String target = stmtContinue.getLabel();
+                if (target != null && !target.equals(node.getLabel())) {
+                    throw stmtContinue;
+                }
+                // continue
             }
-
         } while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data)));
 
         return result;
     }
 
     @Override
+    protected Object visit(ASTSynchronizedStatement node, Object data) {
+        Object result = null;
+        /* first objectNode is the synchronization expression */
+        Node expressionNode = node.jjtGetChild(0);
+        synchronized (expressionNode.jjtAccept(this, data)) {
+            cancelCheck(node);
+            if (node.jjtGetNumChildren() > 1) {
+                // execute statement
+                result = node.jjtGetChild(1).jjtAccept(this, data);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    protected Object visit(ASTSwitchStatement node, Object data) {
+        Object result = null;
+        /* first objectNode is the switch expression */
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        try {
+            int childCount = node.jjtGetNumChildren();
+            boolean matched = false;
+            // check all cases first
+            for (int i = 0; i < childCount; i++) {
+                JexlNode child = node.jjtGetChild(i);
+                if (!matched && child instanceof ASTSwitchStatementCase) {
+                    Object right = child.jjtGetChild(0).jjtAccept(this, data);
+                    try {
+                        Object caseMatched = operators.tryOverload(child, JexlOperator.EQ, left, right);
+                        if (caseMatched == JexlEngine.TRY_FAILED)
+                            caseMatched = arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
+                        matched = arithmetic.toBoolean(caseMatched);
+                    } catch (ArithmeticException xrt) {
+                        throw new JexlException(node, "== error", xrt);
+                    }
+                }
+                if (matched) 
+                    result = child.jjtAccept(this, data);
+            }
+            // otherwise jump to default case
+            if (!matched) {
+                for (int i = 0; i < childCount; i++) {
+                    JexlNode child = node.jjtGetChild(i);
+                    if (child instanceof ASTSwitchStatementDefault)
+                        matched = true;
+                    if (matched) 
+                        result = child.jjtAccept(this, data);
+                }
+            }
+        } catch (JexlException.Break stmtBreak) {
+            String target = stmtBreak.getLabel();
+            if (target != null && !target.equals(node.getLabel())) {
+                throw stmtBreak;
+            }
+            // break
+        }
+        return result;
+    }
+
+    @Override
+    protected Object visit(ASTSwitchStatementCase node, Object data) {
+        Object result = null;
+        int childCount = node.jjtGetNumChildren();
+        for (int i = 1; i < childCount; i++) {
+            cancelCheck(node);
+            result = node.jjtGetChild(i).jjtAccept(this, data);
+        }
+        return result;
+    }
+
+    @Override
+    protected Object visit(ASTSwitchStatementDefault node, Object data) {
+        Object result = null;
+        int childCount = node.jjtGetNumChildren();
+        for (int i = 0; i < childCount; i++) {
+            cancelCheck(node);
+            result = node.jjtGetChild(i).jjtAccept(this, data);
+        }
+        return result;
+    }
+
+    @Override
     protected Object visit(ASTAndNode node, Object data) {
         /**
          * The pattern for exception mgmt is to let the child*.jjtAccept out of the try/catch loop so that if one fails,
@@ -823,6 +1699,11 @@
     }
 
     @Override
+    protected Object visit(ASTThisNode node, Object data) {
+        return context;
+    }
+
+    @Override
     protected Object visit(ASTTrueNode node, Object data) {
         return Boolean.TRUE;
     }
@@ -854,50 +1735,140 @@
     }
 
     @Override
-    protected Object visit(ASTArrayLiteral node, Object data) {
-        int childCount = node.jjtGetNumChildren();
-        JexlArithmetic.ArrayBuilder ab = arithmetic.arrayBuilder(childCount);
-        boolean extended = false;
-        for (int i = 0; i < childCount; i++) {
-            cancelCheck(node);
-            JexlNode child = node.jjtGetChild(i);
-            if (child instanceof ASTExtendedLiteral) {
-                extended = true;
-            } else {
-                Object entry = node.jjtGetChild(i).jjtAccept(this, data);
-                ab.add(entry);
-            }
-        }
-        return ab.create(extended);
+    protected Object visit(ASTClassLiteral node, Object data) {
+        return node.getLiteral();
     }
 
     @Override
-    protected Object visit(ASTExtendedLiteral node, Object data) {
-        return node;
+    protected Object visit(ASTArrayLiteral node, Object data) {
+        int childCount = node.jjtGetNumChildren();
+        JexlArithmetic.ArrayBuilder ab = arithmetic.arrayBuilder(childCount);
+        boolean extended = node.isExtended();
+        boolean immutable = node.isImmutable();
+        final boolean cacheable = cache && immutable && node.isConstant();
+        Object cached = cacheable ? node.jjtGetValue() : null;
+        if (cached != null)
+            return cached;
+
+        for (int i = 0; i < childCount; i++) {
+            cancelCheck(node);
+            JexlNode child = node.jjtGetChild(i);
+            if (child instanceof ASTEnumerationNode || child instanceof ASTEnumerationReference) {
+                Iterator<?> it = (Iterator<?>) child.jjtAccept(this, data);
+                if (it != null) {
+                    try {
+                        while (it.hasNext()) {
+                            Object entry = it.next();
+                            ab.add(entry);
+                        }
+                    } finally {
+                        closeIfSupported(it);
+                    }
+                }
+            } else {
+                Object entry = child.jjtAccept(this, data);
+                ab.add(entry);
+            }
+        }
+        if (immutable) {
+            Object result = ab.create(true);
+            if (result instanceof List<?>)
+                result = Collections.unmodifiableList((List<?>) result);
+            if (cacheable)
+                node.jjtSetValue(result);
+            return result;
+        } else {
+            return ab.create(extended);
+        }
     }
 
     @Override
     protected Object visit(ASTSetLiteral node, Object data) {
+        boolean immutable = node.isImmutable();
+        final boolean cacheable = cache && immutable && node.isConstant();
+        Object cached = cacheable ? node.jjtGetValue() : null;
+        if (cached != null)
+            return cached;
         int childCount = node.jjtGetNumChildren();
         JexlArithmetic.SetBuilder mb = arithmetic.setBuilder(childCount);
         for (int i = 0; i < childCount; i++) {
             cancelCheck(node);
-            Object entry = node.jjtGetChild(i).jjtAccept(this, data);
-            mb.add(entry);
+            JexlNode child = node.jjtGetChild(i);
+            if (child instanceof ASTEnumerationNode || child instanceof ASTEnumerationReference) {
+                Iterator<?> it = (Iterator<?>) child.jjtAccept(this, data);
+                if (it != null) {
+                    try {
+                        while (it.hasNext()) {
+                            Object entry = it.next();
+                            mb.add(entry);
+                        }
+                    } finally {
+                        closeIfSupported(it);
+                    }
+                }
+            } else {
+                Object entry = child.jjtAccept(this, data);
+                mb.add(entry);
+            }
         }
-        return mb.create();
+        if (immutable) {
+            Object result = mb.create();
+            if (result instanceof Set<?>)
+                result = Collections.unmodifiableSet((Set<?>) result);
+            if (cacheable)
+                node.jjtSetValue(result);
+            return result;
+        } else {
+            return mb.create();
+        }
     }
 
     @Override
     protected Object visit(ASTMapLiteral node, Object data) {
+        boolean immutable = node.isImmutable();
+        final boolean cacheable = cache && immutable && node.isConstant();
+        Object cached = cacheable ? node.jjtGetValue() : null;
+        if (cached != null)
+            return cached;
         int childCount = node.jjtGetNumChildren();
         JexlArithmetic.MapBuilder mb = arithmetic.mapBuilder(childCount);
         for (int i = 0; i < childCount; i++) {
             cancelCheck(node);
-            Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
-            mb.put(entry[0], entry[1]);
+            JexlNode child = node.jjtGetChild(i);
+            if (child instanceof ASTMapEntry) {
+                Object[] entry = (Object[]) (child).jjtAccept(this, data);
+                mb.put(entry[0], entry[1]);
+            } else {
+                Iterator<Object> it = (Iterator<Object>) (child).jjtAccept(this, data);
+                int j = 0;
+                if (it != null) {
+                    try {
+                        while (it.hasNext()) {
+                            Object value = it.next();
+                            if (value instanceof Map.Entry<?,?>) {
+                                Map.Entry<?,?> entry = (Map.Entry<?,?>) value;
+                                mb.put(entry.getKey(), entry.getValue());
+                            } else {
+                                mb.put(i, value);
+                            }
+                            i++;
+                        }
+                    } finally {
+                        closeIfSupported(it);
+                    }
+                }
+            }
         }
-        return mb.create();
+        if (immutable) {
+            Object result = mb.create();
+            if (result instanceof Map<?,?>)
+                result = Collections.unmodifiableMap((Map<?,?>) result);
+            if (cacheable)
+                node.jjtSetValue(result);
+            return result;
+        } else {
+            return mb.create();
+        }
     }
 
     @Override
@@ -908,15 +1879,91 @@
     }
 
     @Override
-    protected Object visit(ASTTernaryNode node, Object data) {
-        Object condition = node.jjtGetChild(0).jjtAccept(this, data);
-        if (node.jjtGetNumChildren() == 3) {
-            if (condition != null && arithmetic.toBoolean(condition)) {
-                return node.jjtGetChild(1).jjtAccept(this, data);
+    protected Object visit(ASTMapEnumerationNode node, Object data) {
+        JexlNode valNode = node.jjtGetChild(0);
+        Object iterableValue = valNode.jjtAccept(this, data);
+
+        if (iterableValue != null) {
+            Object forEach = operators.tryOverload(node, JexlOperator.FOR_EACH_INDEXED, iterableValue);
+            Iterator<?> itemsIterator = forEach instanceof Iterator
+                                   ? (Iterator<?>) forEach
+                                   : uberspect.getIndexedIterator(iterableValue);
+            return itemsIterator;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    protected Object visit(ASTInlinePropertyAssignment node, Object data) {
+
+        int childCount = node.jjtGetNumChildren();
+
+        for (int i = 0; i < childCount; i++) {
+            cancelCheck(node);
+
+            JexlNode p = node.jjtGetChild(i);
+
+            if (p instanceof ASTInlinePropertyEntry) {
+
+               Object[] entry = (Object[]) p.jjtAccept(this, null);
+
+               String name = String.valueOf(entry[0]);
+               Object value = entry[1];
+
+               setAttribute(data, name, value, p, JexlOperator.PROPERTY_SET);
+
+            } else if (p instanceof ASTInlinePropertyArrayEntry) {
+
+               Object[] entry = (Object[]) p.jjtAccept(this, null);
+
+               Object key = entry[0];
+               Object value = entry[1];
+
+               setAttribute(data, key, value, p, JexlOperator.ARRAY_SET);
+
             } else {
-                return node.jjtGetChild(2).jjtAccept(this, data);
+
+               // ASTReference
+               p.jjtAccept(this, data);
             }
         }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTInlinePropertyArrayEntry node, Object data) {
+
+        Object key = node.jjtGetChild(0).jjtAccept(this, data);
+        Object value = node.jjtGetChild(1).jjtAccept(this, data);
+
+        return new Object[] {key, value};
+    }
+
+    @Override
+    protected Object visit(ASTInlinePropertyEntry node, Object data) {
+        JexlNode name = node.jjtGetChild(0);
+
+        Object key = name instanceof ASTIdentifier ? ((ASTIdentifier) name).getName() : name.jjtAccept(this, data);
+        Object value = node.jjtGetChild(1).jjtAccept(this, data);
+
+        return new Object[] {key, value};
+    }
+
+    @Override
+    protected Object visit(ASTTernaryNode node, Object data) {
+        Object condition = node.jjtGetChild(0).jjtAccept(this, data);
+        if (condition != null && arithmetic.toBoolean(condition))
+            return node.jjtGetChild(1).jjtAccept(this, data);
+        if (node.jjtGetNumChildren() == 3) {
+            return node.jjtGetChild(2).jjtAccept(this, data);
+        }
+        return null;
+    }
+
+    @Override
+    protected Object visit(ASTElvisNode node, Object data) {
+        Object condition = node.jjtGetChild(0).jjtAccept(this, data);
         if (condition != null && arithmetic.toBoolean(condition)) {
             return condition;
         } else {
@@ -984,6 +2031,11 @@
     }
 
     @Override
+    protected Object visit(ASTExtVar node, Object data) {
+        return visit((ASTIdentifier) node, data);
+    }
+
+    @Override
     protected Object visit(ASTReferenceExpression node, Object data) {
         return node.jjtGetChild(0).jjtAccept(this, data);
     }
@@ -998,6 +2050,31 @@
                 return frame.get(symbol);
             }
             Object value = context.get(name);
+
+            if (value == null && node.jjtGetParent() instanceof ASTExpressionStatement) {
+
+               JexlMethod vm = uberspect.getMethod(arithmetic, name, EMPTY_PARAMS);
+
+               if (vm != null) {
+
+                   try {
+                      Object eval = vm.invoke(arithmetic, EMPTY_PARAMS);
+
+                      if (cache && vm.isCacheable()) {
+                          Funcall funcall = new ArithmeticFuncall(vm, false);
+                          node.jjtSetValue(funcall);
+                      }
+
+                      return eval;
+
+                   } catch (JexlException xthru) {
+                       throw xthru;
+                   } catch (Exception xany) {
+                       throw invocationException(node, name, xany);
+                   }
+               }
+            }
+
             if (value == null
                     && !(node.jjtGetParent() instanceof ASTReference)
                     && !context.has(name)
@@ -1089,7 +2166,7 @@
         final int numChildren = node.jjtGetNumChildren();
         final JexlNode parent = node.jjtGetParent();
         // pass first piece of data in and loop through children
-        Object object = null;
+        Object object = data;
         JexlNode objectNode = null;
         JexlNode ptyNode = null;
         StringBuilder ant = null;
@@ -1181,63 +2258,192 @@
     }
 
     @Override
+    protected Object visit(ASTMultipleAssignment node, Object data) {
+        final int num = node.jjtGetNumChildren();
+        Object result = null;
+        Object assignableValue = node.jjtGetChild(num - 1).jjtAccept(this, data);
+
+        // Use separate logic for maps and non-iterable objects for destructuring
+        if (assignableValue instanceof Map<?,?>) {
+            Map<?,?> assignableMap = (Map<?,?>) assignableValue;
+            for (int i = 0; i < num - 1; i++) {
+                JexlNode left = node.jjtGetChild(i);
+                ASTIdentifier var = (ASTIdentifier) left;
+                Object right = assignableMap.get(var.getName());
+                result = executeAssign(node, left, right, null, data);
+            }
+        } else if (assignableValue != null) {
+            Object forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, assignableValue);
+            Iterator<?> itemsIterator = forEach instanceof Iterator
+                                    ? (Iterator<?>) forEach
+                                    : uberspect.getIterator(assignableValue);
+            if (itemsIterator != null) {
+                try {
+                    int i = -1;
+                    while (itemsIterator.hasNext()) {
+                        cancelCheck(node);
+                        i += 1;
+                        // Stop if we are out of variables to assign to
+                        if (i == num - 1)
+                            break;
+                        // The value to assign
+                        Object right = itemsIterator.next();
+                        // The identifier to assign to
+                        JexlNode left = node.jjtGetChild(i);
+                        result = executeAssign(node, left, right, null, data);
+                    }
+                    while (i < num - 1) {
+                        JexlNode left = node.jjtGetChild(i++);
+                        ASTIdentifier var = (ASTIdentifier) left;
+                        result = executeAssign(node, left, null, null, data);
+                    }
+                } finally {
+                    //  closeable iterator handling
+                    closeIfSupported(itemsIterator);
+                }
+            } else {
+                for (int i = 0; i < num - 1; i++) {
+                    JexlNode left = node.jjtGetChild(i);
+                    ASTIdentifier var = (ASTIdentifier) left;
+                    Object right = getAttribute(assignableValue, var.getName(), node);
+                    result = executeAssign(node, left, right, null, data);
+                }
+            }
+        } else {
+            for (int i = 0; i < num - 1; i++) {
+                JexlNode left = node.jjtGetChild(i);
+                ASTIdentifier var = (ASTIdentifier) left;
+                result = executeAssign(node, left, null, null, data);
+            }
+
+        }
+
+        return result;
+    }
+
+    @Override
     protected Object visit(ASTAssignment node, Object data) {
-        return executeAssign(node, null, data);
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, null, data);
     }
 
     @Override
     protected Object visit(ASTSetAddNode node, Object data) {
-        return executeAssign(node, JexlOperator.SELF_ADD, data);
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_ADD, data);
     }
 
     @Override
     protected Object visit(ASTSetSubNode node, Object data) {
-        return executeAssign(node, JexlOperator.SELF_SUBTRACT, data);
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_SUBTRACT, data);
     }
 
     @Override
     protected Object visit(ASTSetMultNode node, Object data) {
-        return executeAssign(node, JexlOperator.SELF_MULTIPLY, data);
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_MULTIPLY, data);
     }
 
     @Override
     protected Object visit(ASTSetDivNode node, Object data) {
-        return executeAssign(node, JexlOperator.SELF_DIVIDE, data);
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_DIVIDE, data);
     }
 
     @Override
     protected Object visit(ASTSetModNode node, Object data) {
-        return executeAssign(node, JexlOperator.SELF_MOD, data);
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_MOD, data);
     }
 
     @Override
     protected Object visit(ASTSetAndNode node, Object data) {
-        return executeAssign(node, JexlOperator.SELF_AND, data);
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_AND, data);
     }
 
     @Override
     protected Object visit(ASTSetOrNode node, Object data) {
-        return executeAssign(node, JexlOperator.SELF_OR, data);
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_OR, data);
     }
 
     @Override
     protected Object visit(ASTSetXorNode node, Object data) {
-        return executeAssign(node, JexlOperator.SELF_XOR, data);
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_XOR, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetShlNode node, Object data) {
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_SHL, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetSarNode node, Object data) {
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_SAR, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetShrNode node, Object data) {
+        JexlNode left = node.jjtGetChild(0);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return executeAssign(node, left, right, JexlOperator.SELF_SHR, data);
+    }
+
+    @Override
+    protected Object visit(ASTIncrementNode node, Object data) {
+        JexlNode left = node.jjtGetChild(0);
+        return executeAssign(node, left, 1, JexlOperator.INCREMENT, data);
+    }
+
+    @Override
+    protected Object visit(ASTDecrementNode node, Object data) {
+        JexlNode left = node.jjtGetChild(0);
+        return executeAssign(node, left, 1, JexlOperator.DECREMENT, data);
+    }
+
+    @Override
+    protected Object visit(ASTIncrementPostfixNode node, Object data) {
+        JexlNode left = node.jjtGetChild(0);
+        Object value = left.jjtAccept(this, data);
+        executeAssign(node, left, 1, JexlOperator.INCREMENT, data);
+        return value;
+    }
+
+    @Override
+    protected Object visit(ASTDecrementPostfixNode node, Object data) {
+        JexlNode left = node.jjtGetChild(0);
+        Object value = left.jjtAccept(this, data);
+        executeAssign(node, left, 1, JexlOperator.DECREMENT, data);
+        return value;
     }
 
     /**
      * Executes an assignment with an optional side-effect operator.
      * @param node     the node
+     * @param left     the reference to assign to
+     * @param right    the value expression to assign
      * @param assignop the assignment operator or null if simply assignment
      * @param data     the data
      * @return the left hand side
      */
-    protected Object executeAssign(JexlNode node, JexlOperator assignop, Object data) { // CSOFF: MethodLength
+    protected Object executeAssign(JexlNode node, JexlNode left, Object right, JexlOperator assignop, Object data) { // CSOFF: MethodLength
         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);
         Object object = null;
         int symbol = -1;
         boolean antish = true;
@@ -1251,7 +2457,8 @@
                 if (last < 0) {
                     if (assignop != null) {
                         Object self = frame.get(symbol);
-                        right = operators.tryAssignOverload(node, assignop, self, right);
+                        right = assignop.getArity() == 1 ? operators.tryAssignOverload(node, assignop, self) :
+                            operators.tryAssignOverload(node, assignop, self, right);
                         if (right == JexlOperator.ASSIGN) {
                             return self;
                         }
@@ -1271,7 +2478,8 @@
                 if (last < 0) {
                     if (assignop != null) {
                         Object self = context.get(var.getName());
-                        right = operators.tryAssignOverload(node, assignop, self, right);
+                        right = assignop.getArity() == 1 ? operators.tryAssignOverload(node, assignop, self) :
+                            operators.tryAssignOverload(node, assignop, self, right);
                         if (right == JexlOperator.ASSIGN) {
                             return self;
                         }
@@ -1289,6 +2497,40 @@
                     antish = false;
                 }
             }
+        } else if (left instanceof ASTIndirectNode) {
+            if (assignop == null) {
+                Object self = left.jjtGetChild(0).jjtAccept(this, data);
+                if (self == null)
+                    throw new JexlException(left, "illegal assignment form *0");
+                if (self instanceof Pointer) {
+                    ((Pointer) self).set(right);
+                } else {
+                    Object result = operators.indirectAssign(node, self, right);
+                    if (result == JexlEngine.TRY_FAILED)
+                        throw new JexlException(left, "illegal dereferenced assignment");
+                }
+                return right;
+            } else {
+                Object self = left.jjtAccept(this, data);
+                if (self == null)
+                    throw new JexlException(left, "illegal assignment form *0");
+                Object result = operators.tryAssignOverload(node, assignop, self, right);
+                if (result == JexlOperator.ASSIGN) {
+                    return self;
+                } else if (result != JexlEngine.TRY_FAILED) {
+                    self = left.jjtGetChild(0).jjtAccept(this, data);
+                    if (self == null)
+                        throw new JexlException(left, "illegal assignment form *0");
+                    if (self instanceof Pointer) {
+                        ((Pointer) self).set(result);
+                    } else {
+                        result = operators.indirectAssign(node, self, result);
+                        if (result == JexlEngine.TRY_FAILED)
+                            throw new JexlException(left, "illegal dereferenced assignment");
+                    }
+                }
+                return right;
+            }
         } else if (!(left instanceof ASTReference)) {
             throw new JexlException(left, "illegal assignment form 0");
         }
@@ -1339,7 +2581,8 @@
                 ant.append(String.valueOf(property));
                 if (assignop != null) {
                     Object self = context.get(ant.toString());
-                    right = operators.tryAssignOverload(node, assignop, self, right);
+                    right = assignop.getArity() == 1 ? operators.tryAssignOverload(node, assignop, self) :
+                        operators.tryAssignOverload(node, assignop, self, right);
                     if (right == JexlOperator.ASSIGN) {
                         return self;
                     }
@@ -1375,23 +2618,48 @@
         // 3: one before last, assign
         if (assignop != null) {
             Object self = getAttribute(object, property, propertyNode);
-            right = operators.tryAssignOverload(node, assignop, self, right);
+            right = assignop.getArity() == 1 ? operators.tryAssignOverload(node, assignop, self) :
+                operators.tryAssignOverload(node, assignop, self, right);
             if (right == JexlOperator.ASSIGN) {
                 return self;
             }
         }
-        setAttribute(object, property, right, propertyNode);
+
+        final JexlOperator operator = propertyNode != null && propertyNode.jjtGetParent() instanceof ASTArrayAccess
+                                      ? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
+
+        setAttribute(object, property, right, propertyNode, operator);
         return right; // 4
     }
 
     @Override
     protected Object[] visit(ASTArguments node, Object data) {
-        final int argc = node.jjtGetNumChildren();
-        final Object[] argv = new Object[argc];
-        for (int i = 0; i < argc; i++) {
-            argv[i] = node.jjtGetChild(i).jjtAccept(this, data);
+        int childCount = node.jjtGetNumChildren();
+        if (childCount > 0) {
+            List<Object> av = new ArrayList<Object> (childCount);
+            for (int i = 0; i < childCount; i++) {
+                JexlNode child = node.jjtGetChild(i);
+                if (child instanceof ASTEnumerationNode || child instanceof ASTEnumerationReference) {
+                    Iterator<?> it = (Iterator<?>) child.jjtAccept(this, data);
+                    if (it != null) {
+                       try {
+                           while (it.hasNext()) {
+                               Object entry = it.next();
+                               av.add(entry);
+                           }
+                       } finally {
+                           closeIfSupported(it);
+                       }
+                    }
+                } else {
+                    Object entry = child.jjtAccept(this, data);
+                    av.add(entry);
+                }
+            }
+            return av.toArray();
+        } else {
+            return EMPTY_PARAMS;
         }
-        return argv;
     }
 
     @Override
@@ -1488,7 +2756,7 @@
                 } else if (context.has(methodName)) {
                     functor = context.get(methodName);
                     isavar = functor != null;
-                } 
+                }
                 // name is a variable, cant be cached
                 cacheable &= !isavar;
             }
@@ -1506,7 +2774,7 @@
         } else {
             return unsolvableMethod(node, "?");
         }
-        
+
         // solving the call site
         CallDispatcher call = new CallDispatcher(node, cacheable);
         try {
@@ -1514,7 +2782,7 @@
             Object eval = call.tryEval(target, methodName, argv);
             if (JexlEngine.TRY_FAILED != eval) {
                 return eval;
-            } 
+            }
             boolean functorp = false;
             boolean narrow = false;
             // pseudo loop to try acquiring methods without and with argument narrowing
@@ -1566,7 +2834,17 @@
                 if (functor != null) {
                     // lambda, script or jexl method will do
                     if (functor instanceof JexlScript) {
-                        return ((JexlScript) functor).execute(context, argv);
+                        JexlScript s = (JexlScript) functor;
+                        boolean varArgs = s.isVarArgs();
+                        if (!varArgs && isStrictEngine()) {
+                            String[] params = s.getUnboundParameters();
+                            int paramCount = params != null ? params.length : 0;
+                            int argCount = argv != null ? argv.length : 0;
+                            if (argCount > paramCount)
+                                return unsolvableMethod(node, "(...)");
+                        }
+
+                        return s.execute(context, argv);
                     }
                     if (functor instanceof JexlMethod) {
                         return ((JexlMethod) functor).invoke(target, argv);
@@ -1622,7 +2900,7 @@
         final Object target = node.jjtGetChild(0).jjtAccept(this, data);
         // get the ctor args
         int argc = node.jjtGetNumChildren() - 1;
-        Object[] argv = new Object[argc];
+        Object[] argv = argc > 0 ? new Object[argc] : EMPTY_PARAMS;
         for (int i = 0; i < argc; i++) {
             argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, data);
         }
@@ -1689,6 +2967,124 @@
         }
     }
 
+    @Override
+    protected Object visit(ASTQualifiedConstructorNode node, Object data) {
+        if (isCancelled()) {
+            throw new JexlException.Cancel(node);
+        }
+        // first child is class or class name
+        final Class target = (Class) node.jjtGetChild(0).jjtAccept(this, data);
+        // get the ctor args
+        int argc = node.jjtGetNumChildren() - 1;
+        Object[] argv = argc > 0 ? new Object[argc] : EMPTY_PARAMS;
+        for (int i = 0; i < argc; i++) {
+            argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, data);
+        }
+
+        try {
+            boolean cacheable = cache;
+            // attempt to reuse last funcall cached in volatile JexlNode.value
+            if (cacheable) {
+                Object cached = node.jjtGetValue();
+                if (cached instanceof Funcall) {
+                    Object eval = ((Funcall) cached).tryInvoke(this, null, target, argv);
+                    if (JexlEngine.TRY_FAILED != eval) {
+                        return eval;
+                    }
+                }
+            }
+            boolean narrow = false;
+            JexlMethod ctor = null;
+            Funcall funcall = null;
+            while (true) {
+                // try as stated
+                ctor = uberspect.getConstructor(target, argv);
+                if (ctor != null) {
+                    if (cacheable && ctor.isCacheable()) {
+                        funcall = new Funcall(ctor, narrow);
+                    }
+                    break;
+                }
+                // try with prepending context as first argument
+                Object[] nargv = callArguments(context, narrow, argv);
+                ctor = uberspect.getConstructor(target, nargv);
+                if (ctor != null) {
+                    if (cacheable && ctor.isCacheable()) {
+                        funcall = new ContextualCtor(ctor, narrow);
+                    }
+                    argv = nargv;
+                    break;
+                }
+                // if we did not find an exact method by name and we haven't tried yet,
+                // attempt to narrow the parameters and if this succeeds, try again in next loop
+                if (!narrow && arithmetic.narrowArguments(argv)) {
+                    narrow = true;
+                    continue;
+                }
+                // we are done trying
+                break;
+            }
+            // we have either evaluated and returned or might have found a ctor
+            if (ctor != null) {
+                Object eval = ctor.invoke(target, argv);
+                // cache executor in volatile JexlNode.value
+                if (funcall != null) {
+                    node.jjtSetValue(funcall);
+                }
+                return eval;
+            }
+            String tstr = target != null ? target.toString() : "?";
+            return unsolvableMethod(node, tstr);
+        } catch (JexlException.Method xmethod) {
+            throw xmethod;
+        } catch (Exception xany) {
+            String tstr = target != null ? target.toString() : "?";
+            throw invocationException(node, tstr, xany);
+        }
+    }
+
+    @Override
+    protected Object visit(ASTArrayConstructorNode node, Object data) {
+        if (isCancelled()) {
+            throw new JexlException.Cancel(node);
+        }
+        // first child is class or class name
+        final Class target = (Class) node.jjtGetChild(0).jjtAccept(this, data);
+        // get the dimensions
+        int argc = node.jjtGetNumChildren() - 1;
+        int[] argv = new int[argc];
+        for (int i = 0; i < argc; i++) {
+            argv[i] = arithmetic.toInteger(node.jjtGetChild(i + 1).jjtAccept(this, data));
+        }
+        try {
+            return Array.newInstance(target, argv);
+        } catch (Exception xany) {
+            String tstr = target != null ? target.toString() : "?";
+            throw invocationException(node, tstr, xany);
+        }
+    }
+
+    @Override
+    protected Object visit(ASTInitializedArrayConstructorNode node, Object data) {
+        if (isCancelled()) {
+            throw new JexlException.Cancel(node);
+        }
+        // first child is class or class name
+        final Class target = (Class) node.jjtGetChild(0).jjtAccept(this, data);
+        // get the length of the array
+        int argc = node.jjtGetNumChildren() - 1;
+        try {
+            Object result = Array.newInstance(target, argc);
+            for (int i = 0; i < argc; i++) {
+                Array.set(result, i, node.jjtGetChild(i + 1).jjtAccept(this, data));
+            }
+            return result;
+        } catch (Exception xany) {
+            String tstr = target != null ? target.toString() : "?";
+            throw invocationException(node, tstr, xany);
+        }
+    }
+
     /**
      * Gets an attribute of an object.
      *
@@ -1772,7 +3168,7 @@
      * @param value     the value to assign to the object's attribute
      */
     public void setAttribute(Object object, Object attribute, Object value) {
-        setAttribute(object, attribute, value, null);
+        setAttribute(object, attribute, value, null, JexlOperator.PROPERTY_SET);
     }
 
     /**
@@ -1783,10 +3179,8 @@
      * @param value     the value to assign to the object's attribute
      * @param node      the node that evaluated as the object
      */
-    protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
+    protected void setAttribute(Object object, Object attribute, Object value, JexlNode node, JexlOperator operator) {
         cancelCheck(node);
-        final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
-                                      ? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
         Object result = operators.tryOverload(node, operator, object, attribute, value);
         if (result != JexlEngine.TRY_FAILED) {
             return;
@@ -1901,12 +3295,14 @@
                 processed[0] = true;
                 try {
                     return processAnnotation(stmt, index + 1, data);
-                } catch(JexlException.Return xreturn) {
+                } catch (JexlException.Return xreturn) {
                     return xreturn;
-                } catch(JexlException.Break xbreak) {
+                } catch (JexlException.Break xbreak) {
                     return xbreak;
-                } catch(JexlException.Continue xcontinue) {
+                } catch (JexlException.Continue xcontinue) {
                     return xcontinue;
+                } catch (JexlException.Remove xremove) {
+                    return xremove;
                 }
             }
         };
@@ -1949,4 +3345,407 @@
                 ? ((JexlContext.AnnotationProcessor) context).processAnnotation(annotation, args, stmt)
                 : stmt.call();
     }
+
+    protected Iterator<?> prepareIndexedIterator(JexlNode node, Object iterableValue) {
+
+        if (iterableValue != null) {
+            Object forEach = operators.tryOverload(node, JexlOperator.FOR_EACH_INDEXED, iterableValue);
+            Iterator<?> itemsIterator = forEach instanceof Iterator
+                                    ? (Iterator<?>) forEach
+                                    : uberspect.getIndexedIterator(iterableValue);
+            return itemsIterator;
+        }
+
+        return null;
+    }
+
+    protected abstract class IteratorBase implements Iterator<Object>, AutoCloseable {
+
+        protected final Iterator<?> itemsIterator;
+        protected final JexlNode node;
+
+        protected int i;
+
+        protected IteratorBase(Iterator<?> iterator, JexlNode projection) {
+            itemsIterator = iterator;
+            node = projection;
+
+            i = 0;
+        }
+
+        protected Object[] prepareArgs(ASTJexlLambda lambda, Object data) {
+
+            int argCount = lambda.getArgCount();
+            boolean varArgs = lambda.isVarArgs();
+
+            Object[] argv = null;
+
+            if (argCount == 0) {
+                argv = EMPTY_PARAMS;
+            } else if (argCount == 1) {
+                argv = new Object[] {data};
+            } else if (!varArgs && data instanceof Object[]) {
+                int len = ((Object[]) data).length;
+                if (argCount > len) {
+                    argv = new Object[len + 1];
+                    argv[0] = i;
+                    System.arraycopy(data, 0, argv, 1, len);
+                } else if (argCount == len) {
+                    argv = (Object[]) data;
+                } else {
+                    argv = new Object[] {i, data};
+                }
+            } else {
+                argv = new Object[] {i, data};
+            }
+
+            return argv;
+        }
+
+        @Override
+        public void close() {
+            closeIfSupported(itemsIterator);
+        }
+    }
+
+    public class ProjectionIterator extends IteratorBase {
+
+        protected Map<Integer,Closure> scripts;
+
+        protected ProjectionIterator(Iterator<?> iterator, JexlNode projection) {
+            super(iterator, projection);
+
+            scripts = new HashMap<Integer,Closure> ();
+            i = -1;
+        }
+
+        protected Object evaluateProjection(int i, Object data) {
+            JexlNode child = node.jjtGetChild(i);
+
+            if (child instanceof ASTJexlLambda) {
+                ASTJexlLambda lambda = (ASTJexlLambda) child;
+                Closure c = scripts.get(i);
+                if (c == null) {
+                    c = new Closure(Interpreter.this, lambda);
+                    scripts.put(i, c);
+                }
+                Object[] argv = prepareArgs(lambda, data);
+                return c.execute(null, argv);
+            } else {
+                return child.jjtAccept(Interpreter.this, data);
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            return itemsIterator.hasNext();
+        }
+
+        @Override
+        public Object next() {
+
+            cancelCheck(node);
+
+            Object data = itemsIterator.next();
+
+            i += 1;
+
+            // can have multiple nodes
+            int numChildren = node.jjtGetNumChildren();
+
+            if (numChildren == 1) {
+                return evaluateProjection(0, data);
+            } else {
+                List<Object> value = new ArrayList(numChildren);
+                for (int child = 0; child < numChildren; child++) {
+                    value.add(evaluateProjection(child, data));
+                }
+                return Collections.unmodifiableList(value);
+            }
+        }
+
+        @Override
+        public void remove() {
+            itemsIterator.remove();
+        }
+    }
+
+    @Override
+    protected Object visit(ASTProjectionNode node, Object data) {
+        Iterator<?> itemsIterator = prepareIndexedIterator(node, data);
+        return itemsIterator != null ? new ProjectionIterator(itemsIterator, node) : null;
+    }
+
+    public class MapProjectionIterator extends ProjectionIterator {
+
+        protected MapProjectionIterator(Iterator<?> iterator, JexlNode projection) {
+            super(iterator, projection);
+        }
+
+        @Override
+        public Object next() {
+
+            cancelCheck(node);
+
+            Object data = itemsIterator.next();
+
+            i += 1;
+
+            Object key = evaluateProjection(0, data);
+            Object value = evaluateProjection(1, data);
+
+            return new AbstractMap.SimpleImmutableEntry<Object,Object> (key, value);
+        }
+    }
+
+    @Override
+    protected Object visit(ASTMapProjectionNode node, Object data) {
+        Iterator<?> itemsIterator = prepareIndexedIterator(node, data);
+        return itemsIterator != null ? new MapProjectionIterator(itemsIterator, node) : null;
+    }
+
+    public class SelectionIterator extends IteratorBase {
+
+        protected final Closure closure;
+
+        protected Object nextItem;
+        protected boolean hasNextItem;
+
+        protected SelectionIterator(Iterator<?> iterator, ASTJexlLambda filter) {
+            super(iterator, filter);
+            closure = new Closure(Interpreter.this, filter);
+        }
+
+        protected void findNextItem() {
+            if (!itemsIterator.hasNext()) {
+                hasNextItem = false;
+                nextItem = null;
+            } else {
+                Object data = null;
+                boolean selected = false;
+
+                do {
+                    data = itemsIterator.next();
+                    Object[] argv = prepareArgs((ASTJexlLambda) node, data);
+                    selected = arithmetic.toBoolean(closure.execute(null, argv));
+                } while (!selected && itemsIterator.hasNext());
+
+                if (selected) {
+                    hasNextItem = true;
+                    nextItem = data;
+                }
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+
+            if (!hasNextItem)
+                findNextItem();
+
+            return hasNextItem;
+        }
+
+        @Override
+        public Object next() {
+            cancelCheck(node);
+
+            if (!hasNextItem)
+                findNextItem();
+
+            if (!hasNextItem)
+                throw new NoSuchElementException();
+
+            i += 1;
+            hasNextItem = false;
+
+            return nextItem;
+        }
+
+        @Override
+        public void remove() {
+            itemsIterator.remove();
+        }
+    }
+
+    public class StopCountIterator extends IteratorBase {
+
+        protected final int limit;
+
+        protected StopCountIterator(Iterator<?> iterator, JexlNode node, int stopCount) {
+            super(iterator, node);
+            limit = stopCount;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return itemsIterator.hasNext() && i < limit;
+        }
+
+        @Override
+        public Object next() {
+            cancelCheck(node);
+
+            if (!hasNext())
+                throw new NoSuchElementException();
+
+            i += 1;
+
+            return itemsIterator.next();
+        }
+
+        @Override
+        public void remove() {
+            itemsIterator.remove();
+        }
+    }
+
+    public class StartCountIterator extends IteratorBase {
+
+        protected StartCountIterator(Iterator<?> iterator, JexlNode node, int startCount) {
+            super(iterator, node);
+
+            if (startCount > 0)
+                skipItems(startCount);
+        }
+
+        protected void skipItems(int skipCount) {
+            while (i < skipCount) {
+                if (hasNext()) {
+                    next();
+                } else {
+                    break;
+                }
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            return itemsIterator.hasNext();
+        }
+
+        @Override
+        public Object next() {
+            cancelCheck(node);
+
+            if (!hasNext())
+                throw new NoSuchElementException();
+
+            i += 1;
+
+            return itemsIterator.next();
+        }
+
+        @Override
+        public void remove() {
+            itemsIterator.remove();
+        }
+    }
+
+    @Override
+    protected Object visit(ASTSelectionNode node, Object data) {
+        JexlNode child = node.jjtGetChild(0);
+
+        if (child instanceof ASTStopCountNode) {
+            int stopCount = (Integer) child.jjtAccept(this, null);
+            Iterator<?> itemsIterator = prepareIndexedIterator(child, data);
+            return itemsIterator != null ? new StopCountIterator(itemsIterator, node, stopCount) : null;
+        } else if (child instanceof ASTStartCountNode) {
+            int startCount = (Integer) child.jjtAccept(this, null);
+            Iterator<?> itemsIterator = prepareIndexedIterator(child, data);
+            return itemsIterator != null ? new StartCountIterator(itemsIterator, node, startCount) : null;
+        }
+
+        ASTJexlLambda script = (ASTJexlLambda) child;
+        Iterator<?> itemsIterator = prepareIndexedIterator(child, data);
+        return itemsIterator != null ? new SelectionIterator(itemsIterator, script) : null;
+    }
+
+    @Override
+    protected Object visit(ASTStartCountNode node, Object data) {
+        JexlNode child = node.jjtGetChild(0);
+        Integer startCount = arithmetic.toInteger(child.jjtAccept(this, null));
+        return startCount;
+    }
+
+    @Override
+    protected Object visit(ASTStopCountNode node, Object data) {
+        JexlNode child = node.jjtGetChild(0);
+        Integer stopCount = arithmetic.toInteger(child.jjtAccept(this, null));
+        return stopCount;
+    }
+
+    @Override
+    protected Object visit(ASTReductionNode node, Object data) {
+        int numChildren = node.jjtGetNumChildren();
+
+        ASTJexlLambda reduction = null;
+        Object result = null;
+
+        if (numChildren > 1) {
+            result = node.jjtGetChild(0).jjtAccept(this, null);
+            reduction = (ASTJexlLambda) node.jjtGetChild(1);
+        } else {
+            reduction = (ASTJexlLambda) node.jjtGetChild(0);
+        }
+
+        Iterator<?> itemsIterator = prepareIndexedIterator(node, data);
+
+        if (itemsIterator != null) {
+            try {
+                Closure closure = new Closure(this, reduction);
+
+                boolean varArgs = reduction.isVarArgs();
+                int argCount = reduction.getArgCount();
+
+                int i = 0;
+
+                while (itemsIterator.hasNext()) {
+                    Object value = itemsIterator.next();
+
+                    Object[] argv = null;
+
+                    if (argCount == 0) {
+                        argv = EMPTY_PARAMS;
+                    } else if (argCount == 1) {
+                        argv = new Object[] {result};
+                    } else if (argCount == 2) {
+                        argv = new Object[] {result, value};
+                    } else if (argCount == 3) {
+                        argv = new Object[] {result, i, value};
+                    } else if (value instanceof Map.Entry<?,?>) {
+                        Map.Entry<?,?> entry = (Map.Entry<?,?>) value;
+                        argv = new Object[] {result, i, entry.getKey(), entry.getValue()};
+                    } else if (!varArgs && value instanceof Object[]) {
+
+                        int len = ((Object[]) value).length;
+                        if (argCount > len + 1) {
+                           argv = new Object[len + 2];
+                           argv[0] = result;
+                           argv[2] = i;
+                           System.arraycopy(value, 0, argv, 2, len);
+                        } else if (argCount == len + 1) {
+                           argv = new Object[len + 1];
+                           argv[0] = result;
+                           System.arraycopy(value, 0, argv, 1, len);
+                        } else {
+                           argv = new Object[] {result, i, value};
+                        }
+
+                    } else {
+                        argv = new Object[] {result, i, value};
+                    }
+
+                    result = closure.execute(null, argv);
+
+                    i += 1;
+                }
+            } finally {
+                closeIfSupported(itemsIterator);
+            }
+        }
+
+        return result;
+    }
+
 }
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 e8a0287..c301996 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.jexl3.internal;
 
-
 import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlContext;
 import org.apache.commons.jexl3.JexlEngine;
@@ -30,6 +29,7 @@
 import org.apache.commons.jexl3.parser.JexlNode;
 import org.apache.commons.jexl3.parser.ParserVisitor;
 
+import java.lang.reflect.InvocationTargetException;
 
 import org.apache.commons.logging.Log;
 
@@ -85,27 +85,19 @@
         arithmetic = ii.arithmetic;
     }
 
-
-    /** Java7 AutoCloseable interface defined?. */
-    protected static final Class<?> AUTOCLOSEABLE;
-    static {
-        Class<?> c;
-        try {
-            c = Class.forName("java.lang.AutoCloseable");
-        } catch (ClassNotFoundException xclass) {
-            c = null;
-        }
-        AUTOCLOSEABLE = c;
-    }
-
     /**
      * Attempt to call close() if supported.
      * <p>This is used when dealing with auto-closeable (duck-like) objects
      * @param closeable the object we'd like to close
      */
     protected void closeIfSupported(Object closeable) {
-        if (closeable != null) {
-            //if (AUTOCLOSEABLE == null || AUTOCLOSEABLE.isAssignableFrom(closeable.getClass())) {
+        if (closeable instanceof AutoCloseable) {
+            try {
+                ((AutoCloseable)closeable).close();
+            } catch (Exception xignore) {
+                logger.warn(xignore);
+            }
+        } else if (closeable != null) {
             JexlMethod mclose = uberspect.getMethod(closeable, "close", EMPTY_PARAMS);
             if (mclose != null) {
                 try {
@@ -114,7 +106,35 @@
                     logger.warn(xignore);
                 }
             }
-            //}
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected static <T extends Throwable> void doThrow(Throwable t) throws T {
+        throw (T) t;
+    }
+
+    protected class ResourceManager implements AutoCloseable {
+        protected Object r;
+
+        protected ResourceManager(Object resource) {
+            r = resource;
+        }
+
+        @Override
+        public void close() throws Exception {
+            if (r instanceof AutoCloseable) {
+                ((AutoCloseable)r).close();
+            } else if (r != null) {
+                JexlMethod mclose = uberspect.getMethod(r, "close", EMPTY_PARAMS);
+                if (mclose != null) {
+                    try {
+                        mclose.invoke(r, EMPTY_PARAMS);
+                    } catch (InvocationTargetException ex) {
+                        InterpreterBase.<RuntimeException>doThrow(ex.getCause());
+                    }
+                }
+            }
         }
     }
 
@@ -148,6 +168,21 @@
         return jexl.isSilent();
     }
 
+    /**
+     * Whether this interpreter is currently evaluating with assertions enabled.
+     * @return true if assertions enabled, false otherwise
+     */
+    protected boolean isAssertions() {
+        if (this.context instanceof JexlEngine.Options) {
+            JexlEngine.Options opts = (JexlEngine.Options) context;
+            Boolean assertions = opts.isAssertions();
+            if (assertions != null) {
+                return assertions.booleanValue();
+            }
+        }
+        return jexl.isAssertions();
+    }
+
     /** @return true if interrupt throws a JexlException.Cancel. */
     protected boolean isCancellable() {
         if (this.context instanceof JexlEngine.Options) {
@@ -425,7 +460,7 @@
          * @param args   the method arguments
          * @return the method invocation result (or JexlEngine.TRY_FAILED)
          */
-        protected Object tryInvoke(InterpreterBase ii, String name, Object target, Object[] args) {
+        protected Object tryInvoke(InterpreterBase ii, String name, Object target, Object[] args) throws Exception {
             return me.tryInvoke(name, target, ii.functionArguments(null, narrow, args));
         }
     }
@@ -444,7 +479,7 @@
         }
 
         @Override
-        protected Object tryInvoke(InterpreterBase ii, String name, Object target, Object[] args) {
+        protected Object tryInvoke(InterpreterBase ii, String name, Object target, Object[] args) throws Exception {
             return me.tryInvoke(name, ii.arithmetic, ii.functionArguments(target, narrow, args));
         }
     }
@@ -463,7 +498,7 @@
         }
 
         @Override
-        protected Object tryInvoke(InterpreterBase ii, String name, Object target, Object[] args) {
+        protected Object tryInvoke(InterpreterBase ii, String name, Object target, Object[] args) throws Exception {
             return me.tryInvoke(name, ii.context, ii.functionArguments(target, narrow, args));
         }
     }
@@ -482,7 +517,7 @@
         }
 
         @Override
-        protected Object tryInvoke(InterpreterBase ii, String name, Object target, Object[] args) {
+        protected Object tryInvoke(InterpreterBase ii, String name, Object target, Object[] args) throws Exception {
             return me.tryInvoke(name, target, ii.callArguments(ii.context, narrow, args));
         }
     }
@@ -603,7 +638,7 @@
          * @return TRY_FAILED if invocation was not possible or failed, the
          * result otherwise
          */
-        protected Object tryEval(final Object ntarget, final String mname, final Object[] arguments) {
+        protected Object tryEval(final Object ntarget, final String mname, final Object[] arguments) throws Exception {
             // do we have  a method/function name ?
             // attempt to reuse last funcall cached in volatile JexlNode.value (if it was not a variable)
             if (mname != null && cacheable && ntarget != null) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Operators.java b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
index d157445..746aa52 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Operators.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
@@ -70,7 +70,7 @@
     protected Object tryOverload(JexlNode node, JexlOperator operator, Object... args) {
         if (operators != null && operators.overloads(operator)) {
             final JexlArithmetic arithmetic = interpreter.arithmetic;
-            final boolean cache = interpreter.cache;
+            final boolean cache = interpreter.cache && node != null;
             try {
                 if (cache) {
                     Object cached = node.jjtGetValue();
@@ -121,10 +121,7 @@
         }
         // call base operator
         JexlOperator base = operator.getBaseOperator();
-        if (base == null) {
-            throw new IllegalArgumentException("must be called with a side-effect operator");
-        }
-        if (operators != null && operators.overloads(base)) {
+        if (operators != null && base != null && operators.overloads(base)) {
             // in case there is an overload
             try {
                 JexlMethod vm = operators.getOperator(base, args);
@@ -142,21 +139,31 @@
         try {
             switch (operator) {
                 case SELF_ADD:
-                    return arithmetic.add(args[0], args[1]);
+                    return arithmetic.selfAdd(args[0], args[1]);
                 case SELF_SUBTRACT:
-                    return arithmetic.subtract(args[0], args[1]);
+                    return arithmetic.selfSubtract(args[0], args[1]);
                 case SELF_MULTIPLY:
-                    return arithmetic.multiply(args[0], args[1]);
+                    return arithmetic.selfMultiply(args[0], args[1]);
                 case SELF_DIVIDE:
-                    return arithmetic.divide(args[0], args[1]);
+                    return arithmetic.selfDivide(args[0], args[1]);
                 case SELF_MOD:
-                    return arithmetic.mod(args[0], args[1]);
+                    return arithmetic.selfMod(args[0], args[1]);
                 case SELF_AND:
-                    return arithmetic.and(args[0], args[1]);
+                    return arithmetic.selfAnd(args[0], args[1]);
                 case SELF_OR:
-                    return arithmetic.or(args[0], args[1]);
+                    return arithmetic.selfOr(args[0], args[1]);
                 case SELF_XOR:
-                    return arithmetic.xor(args[0], args[1]);
+                    return arithmetic.selfXor(args[0], args[1]);
+                case SELF_SHL:
+                    return arithmetic.selfLeftShift(args[0], args[1]);
+                case SELF_SAR:
+                    return arithmetic.selfRightShift(args[0], args[1]);
+                case SELF_SHR:
+                    return arithmetic.selfRightShiftUnsigned(args[0], args[1]);
+                case INCREMENT:
+                    return arithmetic.increment(args[0]);
+                case DECREMENT:
+                    return arithmetic.decrement(args[0]);
                 default:
                     // unexpected, new operator added?
                     throw new UnsupportedOperationException(operator.getOperatorSymbol());
@@ -318,7 +325,7 @@
         }
         final JexlArithmetic arithmetic = interpreter.arithmetic;
         final JexlUberspect uberspect = interpreter.uberspect;
-        Object result = Operators.this.tryOverload(node, JexlOperator.EMPTY, object);
+        Object result = tryOverload(node, JexlOperator.EMPTY, object);
         if (result != JexlEngine.TRY_FAILED) {
             return result;
         }
@@ -354,7 +361,7 @@
         }
         final JexlArithmetic arithmetic = interpreter.arithmetic;
         final JexlUberspect uberspect = interpreter.uberspect;
-        Object result = Operators.this.tryOverload(node, JexlOperator.SIZE, object);
+        Object result = tryOverload(node, JexlOperator.SIZE, object);
         if (result != JexlEngine.TRY_FAILED) {
             return result;
         }
@@ -373,4 +380,65 @@
         }
         return result;
     }
+
+    /**
+     * Dereferences anything that has an Object get() method.
+     *
+     * @param node   the node holding the object
+     * @param object the object to be dereferenced
+     * @return the evaluation result
+     */
+    protected Object indirect(JexlNode node, Object object) {
+        final JexlArithmetic arithmetic = interpreter.arithmetic;
+        final JexlUberspect uberspect = interpreter.uberspect;
+        Object result = tryOverload(node, JexlOperator.INDIRECT, object);
+        if (result != JexlEngine.TRY_FAILED) {
+            return result;
+        }
+        result = arithmetic.indirect(object);
+        if (result == JexlEngine.TRY_FAILED) {
+            // check if there is a get() method on the object if so, just use it
+            JexlMethod vm = uberspect.getMethod(object, "get", Interpreter.EMPTY_PARAMS);
+            if (vm != null) {
+                try {
+                    result = vm.invoke(object, Interpreter.EMPTY_PARAMS);
+                } catch (Exception xany) {
+                    interpreter.operatorError(node, JexlOperator.INDIRECT, xany);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Assigns a value to anything that has an Object set(Object value) method.
+     *
+     * @param node   the node holding the object
+     * @param object the object to be dereferenced
+     * @param right  the value to be assigned
+     * @return the evaluation result
+     */
+    protected Object indirectAssign(JexlNode node, Object object, Object right) {
+        final JexlArithmetic arithmetic = interpreter.arithmetic;
+        final JexlUberspect uberspect = interpreter.uberspect;
+        Object result = tryOverload(node, JexlOperator.INDIRECT_ASSIGN, object, right);
+        if (result != JexlEngine.TRY_FAILED) {
+            return result;
+        }
+        result = arithmetic.indirectAssign(object, right);
+        if (result == JexlEngine.TRY_FAILED) {
+            // check if there is a set(Object) method on the object and if so, just use it
+            Object[] argv = {right};
+            JexlMethod vm = uberspect.getMethod(object, "set", argv);
+            if (vm != null) {
+                try {
+                    result = vm.invoke(object, argv);
+                } catch (Exception xany) {
+                    interpreter.operatorError(node, JexlOperator.INDIRECT_ASSIGN, xany);
+                }
+            }
+        }
+        return result;
+    }
+
 }
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 380cd4c..b6ce0b7 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
@@ -28,12 +28,16 @@
     /**
      * The parent scope.
      */
-    private Scope parent = null;
+    private final Scope parent;
     /**
      * The number of parameters.
      */
     private int parms;
     /**
+     * If params have variable number.
+     */
+    private boolean varArgs;
+    /**
      * The number of local variables.
      */
     private int vars;
@@ -88,6 +92,9 @@
         if (parms != scope.parms) {
             return false;
         }
+        if (varArgs != scope.varArgs) {
+            return false;
+        }
         if (namedVariables == null) {
             return scope.namedVariables == null;
         }
@@ -160,6 +167,13 @@
     }
 
     /**
+     * Declares a scope to support variable parameters.
+     */
+    public void declareVarArgs() {
+        varArgs = true;
+    }
+
+    /**
      * Declares a local variable.
      * <p>
      * This method creates an new entry in the symbol map.
@@ -239,6 +253,14 @@
     }
 
     /**
+     * If this script expects a variable number of arguments.
+     * @return true or false
+     */
+    public boolean isVarArgs() {
+        return varArgs;
+    }
+
+    /**
      * Gets this script symbols names, i.e. parameters and local variables.
      * @return the symbol names
      */
@@ -385,5 +407,15 @@
             }
             return this;
         }
+
+        /**
+         * Creates a clone of this frame.
+         * @return new frame
+         */
+        public Frame clone() {
+            Object[] copy = stack != null ? stack.clone() : null;
+            return new Frame(scope, copy, curried);
+        }
+
     }
 }
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..6b34bdc 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Script.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Script.java
@@ -187,17 +187,55 @@
     @Override
     public Object execute(JexlContext context, Object... args) {
         checkCacheVersion();
-        Scope.Frame frame = createFrame(args != null && args.length > 0 ? args : null);
+        Scope.Frame frame = createFrame(scriptArgs(args));
         Interpreter interpreter = createInterpreter(context, frame);
         return interpreter.interpret(script);
     }
 
+    /**
+     * @return the script parameter list with regard to vararg option
+     */
+
+    protected Object[] scriptArgs(Object[] args) {
+        return args != null && args.length > 0 ? scriptArgs(0, args) : args;
+    }
+
+    protected Object[] scriptArgs(int curried, Object[] args) {
+
+        boolean varArgs = script.isVarArgs();
+        int argCount = script.getArgCount() - curried;
+        Object[] params = null;
+
+        if (varArgs && args != null && args.length > 0 && args.length >= argCount) {
+
+            if (argCount > 0) {
+                params = new Object[argCount];
+                System.arraycopy(args, 0, params, 0, argCount - 1);
+                int varArgCount = args.length - argCount + 1;
+                Object[] varg = null;
+
+                if (varArgCount == 1 && args[args.length-1] instanceof Object[]) {
+                    varg = (Object[]) args[args.length-1];
+                } else {
+                    varg = new Object[varArgCount];
+                    System.arraycopy(args, argCount - 1, varg, 0, varArgCount);
+                }
+                params[argCount-1] = varg;
+            } else {
+                params = (args.length == 1 && args[0] instanceof Object[]) ? (Object[]) args[0] : new Object[] {args};
+            }
+        } else {
+            params = args;
+        }
+
+        return params;
+    }
+
     @Override
     public JexlScript curry(Object... args) {
         String[] parms = script.getParameters();
-        if (parms == null || parms.length == 0) {
+        if (parms == null || parms.length == 0 || args == null || args.length == 0)
             return this;
-        }
         return new Closure(this, args);
     }
 
@@ -211,6 +249,20 @@
         return getParameters();
     }
 
+    /**
+     * Returns true if this script support variable argument.
+     * @return boolean
+     * @since 3.2
+     */
+    @Override
+    public boolean isVarArgs() {
+        return script.isVarArgs();
+    }
+
+    /**
+     * Gets this script local variables.
+     * @return the local variables or null
+     */
     @Override
     public String[] getLocalVariables() {
         return script.getLocalVariables();
@@ -259,7 +311,7 @@
      */
     @Override
     public Callable callable(JexlContext context, Object... args) {
-        return new Callable(jexl.createInterpreter(context, script.createFrame(args)));
+        return new Callable(jexl.createInterpreter(context, script.createFrame(scriptArgs(args))));
     }
 
     /**
@@ -321,4 +373,4 @@
             return interpreter.isCancellable();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
index bfa1a80..4873130 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
@@ -24,7 +24,9 @@
 import org.apache.commons.jexl3.parser.ASTAnnotation;
 import org.apache.commons.jexl3.parser.ASTArguments;
 import org.apache.commons.jexl3.parser.ASTArrayAccess;
+import org.apache.commons.jexl3.parser.ASTArrayConstructorNode;
 import org.apache.commons.jexl3.parser.ASTArrayLiteral;
+import org.apache.commons.jexl3.parser.ASTAssertStatement;
 import org.apache.commons.jexl3.parser.ASTAssignment;
 import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
 import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
@@ -32,35 +34,60 @@
 import org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
 import org.apache.commons.jexl3.parser.ASTBlock;
 import org.apache.commons.jexl3.parser.ASTBreak;
+import org.apache.commons.jexl3.parser.ASTClassLiteral;
 import org.apache.commons.jexl3.parser.ASTConstructorNode;
 import org.apache.commons.jexl3.parser.ASTContinue;
+import org.apache.commons.jexl3.parser.ASTDecrementNode;
+import org.apache.commons.jexl3.parser.ASTDecrementPostfixNode;
 import org.apache.commons.jexl3.parser.ASTDivNode;
 import org.apache.commons.jexl3.parser.ASTDoWhileStatement;
 import org.apache.commons.jexl3.parser.ASTEQNode;
 import org.apache.commons.jexl3.parser.ASTERNode;
 import org.apache.commons.jexl3.parser.ASTEWNode;
+import org.apache.commons.jexl3.parser.ASTElvisNode;
 import org.apache.commons.jexl3.parser.ASTEmptyFunction;
 import org.apache.commons.jexl3.parser.ASTEmptyMethod;
-import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
+import org.apache.commons.jexl3.parser.ASTEnumerationNode;
+import org.apache.commons.jexl3.parser.ASTEnumerationReference;
+import org.apache.commons.jexl3.parser.ASTExpressionStatement;
+import org.apache.commons.jexl3.parser.ASTExtVar;
 import org.apache.commons.jexl3.parser.ASTFalseNode;
+import org.apache.commons.jexl3.parser.ASTForStatement;
+import org.apache.commons.jexl3.parser.ASTForInitializationNode;
+import org.apache.commons.jexl3.parser.ASTForTerminationNode;
+import org.apache.commons.jexl3.parser.ASTForIncrementNode;
 import org.apache.commons.jexl3.parser.ASTForeachStatement;
+import org.apache.commons.jexl3.parser.ASTForeachVar;
 import org.apache.commons.jexl3.parser.ASTFunctionNode;
 import org.apache.commons.jexl3.parser.ASTGENode;
 import org.apache.commons.jexl3.parser.ASTGTNode;
 import org.apache.commons.jexl3.parser.ASTIdentifier;
 import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
+import org.apache.commons.jexl3.parser.ASTIncrementNode;
+import org.apache.commons.jexl3.parser.ASTIncrementPostfixNode;
+import org.apache.commons.jexl3.parser.ASTIndirectNode;
+import org.apache.commons.jexl3.parser.ASTInitializedArrayConstructorNode;
+import org.apache.commons.jexl3.parser.ASTInlinePropertyAssignment;
+import org.apache.commons.jexl3.parser.ASTInlinePropertyArrayEntry;
+import org.apache.commons.jexl3.parser.ASTInlinePropertyEntry;
 import org.apache.commons.jexl3.parser.ASTIfStatement;
+import org.apache.commons.jexl3.parser.ASTIOFNode;
+import org.apache.commons.jexl3.parser.ASTISNode;
 import org.apache.commons.jexl3.parser.ASTJexlScript;
 import org.apache.commons.jexl3.parser.ASTJxltLiteral;
 import org.apache.commons.jexl3.parser.ASTLENode;
 import org.apache.commons.jexl3.parser.ASTLTNode;
 import org.apache.commons.jexl3.parser.ASTMapEntry;
+import org.apache.commons.jexl3.parser.ASTMapEnumerationNode;
 import org.apache.commons.jexl3.parser.ASTMapLiteral;
+import org.apache.commons.jexl3.parser.ASTMapProjectionNode;
 import org.apache.commons.jexl3.parser.ASTMethodNode;
 import org.apache.commons.jexl3.parser.ASTModNode;
 import org.apache.commons.jexl3.parser.ASTMulNode;
+import org.apache.commons.jexl3.parser.ASTMultipleAssignment;
 import org.apache.commons.jexl3.parser.ASTNENode;
 import org.apache.commons.jexl3.parser.ASTNEWNode;
+import org.apache.commons.jexl3.parser.ASTNINode;
 import org.apache.commons.jexl3.parser.ASTNRNode;
 import org.apache.commons.jexl3.parser.ASTNSWNode;
 import org.apache.commons.jexl3.parser.ASTNotNode;
@@ -68,12 +95,18 @@
 import org.apache.commons.jexl3.parser.ASTNullpNode;
 import org.apache.commons.jexl3.parser.ASTNumberLiteral;
 import org.apache.commons.jexl3.parser.ASTOrNode;
+import org.apache.commons.jexl3.parser.ASTPointerNode;
+import org.apache.commons.jexl3.parser.ASTProjectionNode;
+import org.apache.commons.jexl3.parser.ASTQualifiedConstructorNode;
 import org.apache.commons.jexl3.parser.ASTRangeNode;
+import org.apache.commons.jexl3.parser.ASTReductionNode;
 import org.apache.commons.jexl3.parser.ASTReference;
 import org.apache.commons.jexl3.parser.ASTReferenceExpression;
 import org.apache.commons.jexl3.parser.ASTRegexLiteral;
+import org.apache.commons.jexl3.parser.ASTRemove;
 import org.apache.commons.jexl3.parser.ASTReturnStatement;
 import org.apache.commons.jexl3.parser.ASTSWNode;
+import org.apache.commons.jexl3.parser.ASTSelectionNode;
 import org.apache.commons.jexl3.parser.ASTSetAddNode;
 import org.apache.commons.jexl3.parser.ASTSetAndNode;
 import org.apache.commons.jexl3.parser.ASTSetDivNode;
@@ -82,14 +115,33 @@
 import org.apache.commons.jexl3.parser.ASTSetMultNode;
 import org.apache.commons.jexl3.parser.ASTSetOrNode;
 import org.apache.commons.jexl3.parser.ASTSetSubNode;
+import org.apache.commons.jexl3.parser.ASTSetShlNode;
+import org.apache.commons.jexl3.parser.ASTSetSarNode;
+import org.apache.commons.jexl3.parser.ASTSetShrNode;
 import org.apache.commons.jexl3.parser.ASTSetXorNode;
+import org.apache.commons.jexl3.parser.ASTShiftLeftNode;
+import org.apache.commons.jexl3.parser.ASTShiftRightNode;
+import org.apache.commons.jexl3.parser.ASTShiftRightUnsignedNode;
 import org.apache.commons.jexl3.parser.ASTSizeFunction;
 import org.apache.commons.jexl3.parser.ASTSizeMethod;
+import org.apache.commons.jexl3.parser.ASTStartCountNode;
+import org.apache.commons.jexl3.parser.ASTStopCountNode;
 import org.apache.commons.jexl3.parser.ASTStringLiteral;
 import org.apache.commons.jexl3.parser.ASTSubNode;
+import org.apache.commons.jexl3.parser.ASTSwitchStatement;
+import org.apache.commons.jexl3.parser.ASTSwitchStatementCase;
+import org.apache.commons.jexl3.parser.ASTSwitchStatementDefault;
+import org.apache.commons.jexl3.parser.ASTSynchronizedStatement;
 import org.apache.commons.jexl3.parser.ASTTernaryNode;
+import org.apache.commons.jexl3.parser.ASTThisNode;
+import org.apache.commons.jexl3.parser.ASTThrowStatement;
 import org.apache.commons.jexl3.parser.ASTTrueNode;
+import org.apache.commons.jexl3.parser.ASTTryStatement;
+import org.apache.commons.jexl3.parser.ASTTryVar;
+import org.apache.commons.jexl3.parser.ASTTryWithResourceStatement;
+import org.apache.commons.jexl3.parser.ASTTryResource;
 import org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
+import org.apache.commons.jexl3.parser.ASTUnaryPlusNode;
 import org.apache.commons.jexl3.parser.ASTVar;
 import org.apache.commons.jexl3.parser.ASTWhileStatement;
 import org.apache.commons.jexl3.parser.JexlNode;
@@ -147,6 +199,11 @@
     }
 
     @Override
+    protected Object visit(ASTExpressionStatement node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTIfStatement node, Object data) {
         return visitNode(node, data);
     }
@@ -167,21 +224,106 @@
     }
 
     @Override
+    protected Object visit(ASTRemove node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTBreak node, Object data) {
         return visitNode(node, data);
     }
 
     @Override
+    protected Object visit(ASTForStatement node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTForInitializationNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTForTerminationNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTForIncrementNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTForeachStatement node, Object data) {
         return visitNode(node, data);
     }
 
     @Override
+    protected Object visit(ASTForeachVar node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTReturnStatement node, Object data) {
         return visitNode(node, data);
     }
 
     @Override
+    protected Object visit(ASTTryStatement node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTTryVar node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTTryWithResourceStatement node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTTryResource node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTThrowStatement node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTAssertStatement node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTSynchronizedStatement node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTSwitchStatement node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTSwitchStatementCase node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTSwitchStatementDefault node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTMultipleAssignment node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTAssignment node, Object data) {
         return visitNode(node, data);
     }
@@ -192,6 +334,11 @@
     }
 
     @Override
+    protected Object visit(ASTExtVar node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTReference node, Object data) {
         return visitNode(node, data);
     }
@@ -202,6 +349,11 @@
     }
 
     @Override
+    protected Object visit(ASTElvisNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTNullpNode node, Object data) {
         return visitNode(node, data);
     }
@@ -232,6 +384,16 @@
     }
 
     @Override
+    protected Object visit(ASTISNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTNINode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTEQNode node, Object data) {
         return visitNode(node, data);
     }
@@ -288,8 +450,13 @@
 
     @Override
     protected Object visit(ASTNEWNode node, Object data) {
+        return visitNode(node, data);    
+    }
 
-        return visitNode(node, data);    }
+    @Override
+    protected Object visit(ASTIOFNode node, Object data) {
+        return visitNode(node, data);
+    }
 
     @Override
     protected Object visit(ASTAddNode node, Object data) {
@@ -317,11 +484,61 @@
     }
 
     @Override
+    protected Object visit(ASTShiftLeftNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTShiftRightNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTShiftRightUnsignedNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTUnaryMinusNode node, Object data) {
         return visitNode(node, data);
     }
 
     @Override
+    protected Object visit(ASTUnaryPlusNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTIncrementNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTDecrementNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTIncrementPostfixNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTDecrementPostfixNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTIndirectNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTPointerNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTBitwiseComplNode node, Object data) {
         return visitNode(node, data);
     }
@@ -332,6 +549,16 @@
     }
 
     @Override
+    protected Object visit(ASTEnumerationNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTEnumerationReference node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTIdentifier node, Object data) {
         return visitNode(node, data);
     }
@@ -342,6 +569,11 @@
     }
 
     @Override
+    protected Object visit(ASTThisNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTTrueNode node, Object data) {
         return visitNode(node, data);
     }
@@ -367,12 +599,12 @@
     }
 
     @Override
-    protected Object visit(ASTSetLiteral node, Object data) {
+    protected Object visit(ASTClassLiteral node, Object data) {
         return visitNode(node, data);
     }
 
     @Override
-    protected Object visit(ASTExtendedLiteral node, Object data) {
+    protected Object visit(ASTSetLiteral node, Object data) {
         return visitNode(node, data);
     }
 
@@ -397,6 +629,26 @@
     }
 
     @Override
+    protected Object visit(ASTMapEnumerationNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTInlinePropertyAssignment node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTInlinePropertyArrayEntry node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTInlinePropertyEntry node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTEmptyFunction node, Object data) {
         return visitNode(node, data);
     }
@@ -432,6 +684,21 @@
     }
 
     @Override
+    protected Object visit(ASTQualifiedConstructorNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTArrayConstructorNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTInitializedArrayConstructorNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTArrayAccess node, Object data) {
         return visitNode(node, data);
     }
@@ -492,6 +759,21 @@
     }
 
     @Override
+    protected Object visit(ASTSetShlNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetSarNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetShrNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
     protected Object visit(ASTJxltLiteral node, Object data) {
         return visitNode(node, data);
     }
@@ -505,4 +787,35 @@
     protected Object visit(ASTAnnotatedStatement node, Object data) {
         return visitNode(node, data);
     }
+
+    @Override
+    protected Object visit(ASTProjectionNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTMapProjectionNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTSelectionNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTStartCountNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTStopCountNode node, Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTReductionNode node, Object data) {
+        return visitNode(node, data);
+    }
+
 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/SetBuilder.java b/src/main/java/org/apache/commons/jexl3/internal/SetBuilder.java
index cdb0a36..07534b1 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/SetBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/SetBuilder.java
@@ -28,8 +28,8 @@
     protected final Set<Object> set;
 
     /**
-     * Creates a new builder.
-     * @param size the expected set size
+     * Creates a new builder with specified set size.
+     * @param size the initial set size
      */
     public SetBuilder(int size) {
         set = new HashSet<Object>(size);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateDebugger.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateDebugger.java
index 575d713..cf32b5b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateDebugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateDebugger.java
@@ -26,6 +26,7 @@
 
 import org.apache.commons.jexl3.parser.ASTBlock;
 import org.apache.commons.jexl3.parser.ASTFunctionNode;
+import org.apache.commons.jexl3.parser.ASTExpressionStatement;
 import org.apache.commons.jexl3.parser.ASTIdentifier;
 import org.apache.commons.jexl3.parser.ASTJexlScript;
 import org.apache.commons.jexl3.parser.ASTNumberLiteral;
@@ -157,6 +158,11 @@
      * @return the expression number or -1 if the node is not a jexl:print
      */
     private int getPrintStatement(JexlNode child) {
+
+        if (child instanceof ASTExpressionStatement && child.jjtGetNumChildren() == 1) {
+            child = child.jjtGetChild(0);
+        }
+
         if (child instanceof ASTFunctionNode) {
             ASTFunctionNode node = (ASTFunctionNode) child;
             ASTIdentifier ns = (ASTIdentifier) node.jjtGetChild(0);
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 17a66e1..295f446 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
@@ -93,15 +93,12 @@
     }
 
     @Override
-    public Object tryInvoke(String name, Object obj, Object... args) {
-        MethodKey tkey = new MethodKey(name, args);
+    public Object tryInvoke(String name, Object obj, Object... args) throws InvocationTargetException {
         // let's assume that invocation will fly if the declaring class is the
         // same and arguments have the same type
-        if (objectClass.equals(obj.getClass()) && tkey.equals(key)) {
+        if (objectClass.equals(obj.getClass()) && key.equals(new MethodKey(name, args))) {
             try {
                 return invoke(obj, args);
-            } catch (InvocationTargetException xinvoke) {
-                return TRY_FAILED; // fail
             } catch (IllegalAccessException xill) {
                 return TRY_FAILED;// fail
             } catch (IllegalArgumentException xarg) {
@@ -154,5 +151,3 @@
         return actual;
     }
 }
-
-
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
index bb7a980..caf591d 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
@@ -145,6 +145,11 @@
     }
 
     @Override
+    public Iterator<?> getIndexedIterator(final Object obj) {
+        return uberspect.getIndexedIterator(obj);
+    }
+
+    @Override
     public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic arithmetic) {
         return uberspect.getArithmetic(arithmetic);
     }
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 c9b5b82..309987e 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
@@ -272,11 +272,12 @@
                         }
                         break;
                     case FIELD:
-                        // a field may be? (can not be a number)
-                        executor = FieldGetExecutor.discover(is, claz, property);
-                        // static class fields (enums included)
                         if (obj instanceof Class<?>) {
+                            // static class fields (enums included)
                             executor = FieldGetExecutor.discover(is, (Class<?>) obj, property);
+                        } else {
+                            // a field may be? (can not be a number)
+                            executor = FieldGetExecutor.discover(is, claz, property);
                         }
                         break;
                     case CONTAINER:
@@ -389,6 +390,17 @@
     }
 
     @Override
+    @SuppressWarnings("unchecked")
+    public Iterator<?> getIndexedIterator(Object obj) {
+
+        if (obj instanceof Map<?, ?>) {
+            return ((Map<?, ?>) obj).entrySet().iterator();
+        }
+
+        return getIterator(obj);
+    }
+
+    @Override
     public JexlMethod getConstructor(Object ctorHandle, Object... args) {
         return ConstructorMethod.discover(base(), ctorHandle, args);
     }
diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlMethod.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlMethod.java
index 22c862e..2d55b48 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlMethod.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlMethod.java
@@ -49,7 +49,7 @@
      * @return the result of the method invocation that should be checked by tryFailed to determine if it succeeded
      * or failed.
      */
-    Object tryInvoke(String name, Object obj, Object... params);
+    Object tryInvoke(String name, Object obj, Object... params) throws Exception;
 
     /**
      * Checks whether a tryInvoke return value indicates a failure or not.
diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
index 41e014c..54462e5 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
@@ -309,6 +309,15 @@
     Iterator<?> getIterator(Object obj);
 
     /**
+     * Gets an indexed iterator from an object.
+     * 
+     * @param obj to get the iterator from
+     * @return an iterator over obj or null
+     * @since 3.2
+     */
+    Iterator<?> getIndexedIterator(Object obj);
+
+    /**
      * Gets an arithmetic operator resolver for a given arithmetic instance.
      * 
      * @param arithmetic the arithmetic instance
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java
index e0d550e..a98ed7e 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java
@@ -25,6 +25,11 @@
     /** Whether this array is constant or not. */
     private boolean constant = false;
 
+    private boolean extended = false;
+
+    /** Whether this array is immutable. */
+    private boolean immutable = false;
+
     ASTArrayLiteral(int id) {
         super(id);
     }
@@ -44,6 +49,22 @@
         return constant;
     }
 
+    public boolean isExtended() {
+        return extended;
+    }
+
+    public void setExtended(boolean value) {
+        extended = value;
+    }
+
+    public boolean isImmutable() {
+        return immutable;
+    }
+
+    public void setImmutable(boolean value) {
+        immutable = value;
+    }
+
     @Override
     public void jjtClose() {
         constant = true;
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..5cd5cf7
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Block of statements.
+ */
+public class ASTBlock extends ASTLabelledStatement {
+
+    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);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTBreak.java b/src/main/java/org/apache/commons/jexl3/parser/ASTBreak.java
new file mode 100644
index 0000000..c4d35c9
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTBreak.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Break statements.
+ */
+public class ASTBreak extends ASTLabelledStatement {
+
+    public ASTBreak(int id) {
+        super(id);
+    }
+
+    public ASTBreak(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTClassLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTClassLiteral.java
new file mode 100644
index 0000000..359f328
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTClassLiteral.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+public final class ASTClassLiteral extends JexlNode implements JexlNode.Constant<Class> {
+
+    /** The actual literal value; the inherited 'value' member may host a cached getter. */
+
+    private Class literal = null;
+    private int array = 0;
+
+    ASTClassLiteral(int id) {
+        super(id);
+    }
+
+    ASTClassLiteral(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder();
+        if (literal != null) {
+            String qn = literal.getName();
+            Package pack = literal.getPackage();
+            String p = pack != null ? pack.getName() : null;
+            if (p == null || p.equals("java.lang") || p.equals("java.util") || p.equals("java.io") || p.equals("java.net")
+                || qn.equals("java.math.BigDecimal") || qn.equals("java.math.BigInteger")) {
+                result.append(literal.getSimpleName());
+            } else {
+                result.append(literal.getName());
+            }
+        }
+        for (int i = 0; i < array; i++)
+            result.append("[]");
+        return result.toString();
+    }
+
+    /**
+     * Gets the literal value.
+     * @return the Pattern literal
+     */
+    @Override
+    public Class getLiteral() {
+        return this.literal;
+    }
+
+    @Override
+    protected boolean isConstant(boolean literal) {
+        return true;
+    }
+
+    void setLiteral(Class literal) {
+        this.literal = literal;
+    }
+
+    void setArray() {
+        array++;
+    }
+
+    public int getArray() {
+        return array;
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTContinue.java b/src/main/java/org/apache/commons/jexl3/parser/ASTContinue.java
new file mode 100644
index 0000000..092f307
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTContinue.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Continue statements.
+ */
+public class ASTContinue extends ASTLabelledStatement {
+
+    public ASTContinue(int id) {
+        super(id);
+    }
+
+    public ASTContinue(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTDoWhileStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTDoWhileStatement.java
new file mode 100644
index 0000000..7dd25da
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTDoWhileStatement.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Do-While loop statement.
+ */
+public class ASTDoWhileStatement extends ASTLabelledStatement {
+
+    public ASTDoWhileStatement(int id) {
+        super(id);
+    }
+
+    public ASTDoWhileStatement(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTExtVar.java b/src/main/java/org/apache/commons/jexl3/parser/ASTExtVar.java
new file mode 100644
index 0000000..f0c410c
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTExtVar.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Declares a supplementary local variable.
+ */
+public class ASTExtVar extends ASTVar {
+    public ASTExtVar(int id) {
+        super(id);
+    }
+
+    public ASTExtVar(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTForStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTForStatement.java
new file mode 100644
index 0000000..733ff3a
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTForStatement.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * For loop statement.
+ */
+public class ASTForStatement extends ASTLabelledStatement {
+
+    public ASTForStatement(int id) {
+        super(id);
+    }
+
+    public ASTForStatement(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
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..5982c35
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Foreach loop statement.
+ */
+public class ASTForeachStatement extends ASTLabelledStatement {
+
+    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);
+    }
+}
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..50e6c5c 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
@@ -136,6 +136,14 @@
     }
 
     /**
+     * If this script expects a variable number of arguments.
+     * @return true or false
+     */
+    public boolean isVarArgs() {
+        return scope != null ? scope.isVarArgs() : false;
+    }
+
+    /**
      * Gets this script symbols, i.e. parameters and local variables.
      * @return the symbol names
      */
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTLabeledStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTLabeledStatement.java
new file mode 100644
index 0000000..a9a8bb4
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTLabeledStatement.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * Labeled Statement.
+ */
+public class ASTLabeledStatement extends JexlNode {
+
+    private String label = null;
+
+    ASTLabeledStatement(int id) {
+        super(id);
+    }
+
+    ASTLabeledStatement(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public String toString() {
+        return label;
+    }
+
+    void setLabel(String identifier) {
+        label = identifier;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTLabelledStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTLabelledStatement.java
new file mode 100644
index 0000000..d528f2e
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTLabelledStatement.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * Labeled Statement.
+ */
+public class ASTLabelledStatement extends JexlNode {
+
+    private String label = null;
+
+    ASTLabelledStatement(int id) {
+        super(id);
+    }
+
+    ASTLabelledStatement(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public String toString() {
+        return label;
+    }
+
+    void setLabel(String identifier) {
+        label = identifier;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTMapLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTMapLiteral.java
index 5650dbf..ebc8412 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTMapLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTMapLiteral.java
@@ -21,6 +21,8 @@
 public final class ASTMapLiteral extends JexlNode {
     /** Whether this array is constant or not. */
     private boolean constant = false;
+    /** Whether this array is immutable. */
+    private boolean immutable = false;
 
     ASTMapLiteral(int id) {
         super(id);
@@ -41,6 +43,14 @@
         return constant;
     }
 
+    public boolean isImmutable() {
+        return immutable;
+    }
+
+    public void setImmutable(boolean value) {
+        immutable = value;
+    }
+
     @Override
     public void jjtClose() {
         constant = true;
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTRemove.java b/src/main/java/org/apache/commons/jexl3/parser/ASTRemove.java
new file mode 100644
index 0000000..dc7a480
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTRemove.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Remove statements.
+ */
+public class ASTRemove extends ASTLabelledStatement {
+
+    public ASTRemove(int id) {
+        super(id);
+    }
+
+    public ASTRemove(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTSetLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTSetLiteral.java
index 4db2395..c00bfa1 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTSetLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSetLiteral.java
@@ -21,6 +21,8 @@
 public final class ASTSetLiteral extends JexlNode {
     /** Whether this set is constant or not. */
     private boolean constant = false;
+    /** Whether this set is immutable. */
+    private boolean immutable = false;
 
     ASTSetLiteral(int id) {
         super(id);
@@ -41,6 +43,14 @@
         return constant;
     }
 
+    public boolean isImmutable() {
+        return immutable;
+    }
+
+    public void setImmutable(boolean value) {
+        immutable = value;
+    }
+
     @Override
     public void jjtClose() {
         constant = true;
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java
new file mode 100644
index 0000000..8b0290d
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Switch/case statement.
+ */
+public class ASTSwitchStatement extends ASTLabelledStatement {
+
+    public ASTSwitchStatement(int id) {
+        super(id);
+    }
+
+    public ASTSwitchStatement(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTWhileStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTWhileStatement.java
new file mode 100644
index 0000000..c7edb09
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTWhileStatement.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * While loop statement.
+ */
+public class ASTWhileStatement extends ASTLabelledStatement {
+
+    public ASTWhileStatement(int id) {
+        super(id);
+    }
+
+    public ASTWhileStatement(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/FeatureController.java b/src/main/java/org/apache/commons/jexl3/parser/FeatureController.java
index e40192d..b2c819b 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/FeatureController.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/FeatureController.java
@@ -128,6 +128,14 @@
     }
 
     @Override
+    protected Object visit(ASTForStatement node, Object data) {
+        if (!features.supportsLoops()) {
+            throwFeatureException(JexlFeatures.LOOP, node);
+        }
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTConstructorNode node, Object data) {
         if (!features.supportsNewInstance()) {
             throwFeatureException(JexlFeatures.NEW_INSTANCE, node);
@@ -136,6 +144,30 @@
     }
 
     @Override
+    protected Object visit(ASTQualifiedConstructorNode node, Object data) {
+        if (!features.supportsNewInstance()) {
+            throwFeatureException(JexlFeatures.NEW_INSTANCE, node);
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTArrayConstructorNode node, Object data) {
+        if (!features.supportsNewInstance()) {
+            throwFeatureException(JexlFeatures.NEW_INSTANCE, node);
+        }
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTInitializedArrayConstructorNode node, Object data) {
+        if (!features.supportsNewInstance()) {
+            throwFeatureException(JexlFeatures.NEW_INSTANCE, node);
+        }
+        return data;
+    }
+
+    @Override
     protected Object visit(ASTMethodNode node, Object data) {
         if (!features.supportsMethodCall()) {
             throwFeatureException(JexlFeatures.METHOD_CALL, node);
@@ -200,6 +232,11 @@
     }
 
     @Override
+    protected Object visit(ASTMultipleAssignment node, Object data) {
+        return controlSideEffect(node, data);
+    }
+
+    @Override
     protected Object visit(ASTSetAddNode node, Object data) {
         return controlSideEffect(node, data);
     }
@@ -234,4 +271,39 @@
         return controlSideEffect(node, data);
     }
 
+    @Override
+    protected Object visit(ASTSetShlNode node, Object data) {
+        return controlSideEffect(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetSarNode node, Object data) {
+        return controlSideEffect(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetShrNode node, Object data) {
+        return controlSideEffect(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTIncrementNode node, Object data) {
+        return controlSideEffect(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTDecrementNode node, Object data) {
+        return controlSideEffect(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTIncrementPostfixNode node, Object data) {
+        return controlSideEffect(node, data);
+    }
+
+    @Override
+    protected Object visit(ASTDecrementPostfixNode node, Object data) {
+        return controlSideEffect(node, data);
+    }
+
 }
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 2a91f5e..1a6de1e 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
@@ -148,7 +148,8 @@
         do {
             if (walk instanceof ASTIdentifier
                 || walk instanceof ASTIdentifierAccess
-                || walk instanceof ASTArrayAccess) {
+                || walk instanceof ASTArrayAccess
+                || walk instanceof ASTIndirectNode) {
                 return true;
             }
             int nc = walk.jjtGetNumChildren() - 1;
@@ -246,6 +247,9 @@
             if (walk instanceof ASTTernaryNode) {
                 return true;
             }
+            if (walk instanceof ASTElvisNode) {
+                return true;
+            }
             if (walk instanceof ASTNullpNode) {
                 return true;
             }
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 61756ea..5a481c8 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -26,6 +26,7 @@
 import java.io.IOException;
 import java.io.StringReader;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -33,7 +34,11 @@
 import java.util.Set;
 import java.util.Stack;
 import java.util.TreeMap;
+import java.math.BigInteger;
+import java.math.BigDecimal;
 
+import static org.apache.commons.jexl3.parser.ParserConstants.EOF;
+import static org.apache.commons.jexl3.parser.ParserConstants.SEMICOL;
 
 /**
  * The base class for parsing, manages the parameter/local variable frame.
@@ -67,25 +72,6 @@
     protected Map<String, Object> pragmas = null;
 
     /**
-     * Utility function to create '.' separated string from a list of string.
-     * @param lstr the list of strings
-     * @return the dotted version
-     */
-    protected static String stringify(List<String> lstr) {
-        StringBuilder strb = new StringBuilder();
-        boolean dot = false;
-        for(String str : lstr) {
-            if (!dot) {
-               dot = true;
-            } else {
-               strb.append('.');
-            }
-            strb.append(str);
-        }
-        return strb.toString();
-    }
-
-    /**
      * Read a given source line.
      * @param src the source
      * @param lineno the line number
@@ -105,7 +91,7 @@
         }
         return msg;
     }
-    
+
     /**
      * Internal, for debug purpose only.
      * @param registers whether register syntax is recognized by this parser
@@ -156,12 +142,24 @@
             frames.push(frame);
         }
         frame = new Scope(frame, (String[]) null);
+
+        if (branchScope != null) {
+            branchScopes.push(branchScope);
+        }
+        branchScope = new BranchScope();
     }
 
     /**
      * Pops back to previous local variable frame.
      */
     protected void popFrame() {
+
+        if (!branchScopes.isEmpty()) {
+            branchScope = branchScopes.pop();
+        } else {
+            branchScope = null;
+        }
+
         if (!frames.isEmpty()) {
             frame = frames.pop();
         } else {
@@ -246,6 +244,15 @@
     }
 
     /**
+     * Declares a local vararg parameter.
+     * <p> This method creates an new entry in the symbol map. </p>
+     * @param token the parameter name toekn
+     */
+    protected void declareVarArgSupport() {
+        frame.declareVarArgs();
+    }
+
+    /**
      * Default implementation does nothing but is overriden by generated code.
      * @param top whether the identifier is beginning an l/r value
      * @throws ParseException subclasses may throw this
@@ -284,7 +291,14 @@
             ASTSetAndNode.class,
             ASTSetOrNode.class,
             ASTSetXorNode.class,
-            ASTSetSubNode.class
+            ASTSetSubNode.class,
+            ASTSetShlNode.class,
+            ASTSetSarNode.class,
+            ASTSetShrNode.class,
+            ASTIncrementNode.class,
+            ASTDecrementNode.class,
+            ASTIncrementPostfixNode.class,
+            ASTDecrementPostfixNode.class
         )
     );
 
@@ -322,7 +336,13 @@
             if (!lv.isLeftValue()) {
                 throwParsingException(JexlException.Assignment.class, null);
             }
+        } else if (node instanceof ASTPointerNode) {
+            JexlNode lv = node.jjtGetChild(0);
+            if (!lv.isLeftValue()) {
+                throwParsingException(JexlException.Assignment.class, null);
+            }
         }
+
         // heavy check
         featureController.controlNode(node);
     }
@@ -402,4 +422,182 @@
         // unlikely but safe
         throw xparse != null ? xparse : new JexlException.Parsing(xinfo, msg);
     }
+
+    protected static String[] implicitPackages = {"java.lang.","java.util.","java.io.","java.net."};
+
+    /**
+     * Resolves a type by its name.
+     * @param name the name of the type
+     * @return the Class
+     */
+    protected static Class resolveType(String name) {
+        if (name == null)
+            return null;
+        switch (name) {
+            case "char" : return Character.TYPE;
+            case "boolean" : return Boolean.TYPE;
+            case "byte" : return Byte.TYPE;
+            case "short" : return Short.TYPE;
+            case "int" : return Integer.TYPE;
+            case "long" : return Long.TYPE;
+            case "float" : return Float.TYPE;
+            case "double" : return Double.TYPE;
+            case "Character" : return Character.class;
+            case "Boolean" : return Boolean.class;
+            case "Byte" : return Byte.class;
+            case "Short" : return Short.class;
+            case "Integer" : return Integer.class;
+            case "Long" : return Long.class;
+            case "Float" : return Float.class;
+            case "Double" : return Double.class;
+            case "BigInteger" : return BigInteger.class;
+            case "BigDecimal" : return BigDecimal.class;
+        }
+
+        if (name.indexOf(".") == -1) {
+            for (String prefix : implicitPackages) {
+                String className = prefix + name;
+                try {
+                    return Class.forName(className);
+                } catch (ClassNotFoundException ex) {
+                }
+            }
+            return null;
+        }
+        try {
+            return Class.forName(name);
+        } catch (ClassNotFoundException ex) {
+            return null;
+        }
+    }
+
+    /**
+     * Resolves an instantiable type by its name.
+     * @param name the name of the type
+     * @return the Class
+     */
+    protected static Class resolveInstantiableType(String name) {
+        if (name == null)
+            return null;
+        switch (name) {
+            case "char" : 
+            case "boolean" : 
+            case "byte" : 
+            case "short" : 
+            case "int" : 
+            case "long" : 
+            case "float" : 
+            case "double" : return null;
+            case "Character" : return Character.class;
+            case "Boolean" : return Boolean.class;
+            case "Byte" : return Byte.class;
+            case "Short" : return Short.class;
+            case "Integer" : return Integer.class;
+            case "Long" : return Long.class;
+            case "Float" : return Float.class;
+            case "Double" : return Double.class;
+            case "BigInteger" : return BigInteger.class;
+            case "BigDecimal" : return BigDecimal.class;
+        }
+
+        Class result = null;
+
+        if (name.indexOf(".") == -1) {
+            for (String prefix : implicitPackages) {
+                String className = prefix + name;
+                try {
+                    result = Class.forName(className);
+                    break;                   
+                } catch (ClassNotFoundException ex) {
+                }
+            }
+        }
+        if (result == null) {
+            try {
+                result = Class.forName(name);
+            } catch (ClassNotFoundException ex) {
+                return null;
+            }
+        }
+        return (result.isInterface() || Modifier.isAbstract(result.getModifiers())) ? null : result;
+    }
+
+    /**
+     * The target scope class for break/continue/remove statements.
+     */
+    protected class BranchScope {
+
+        protected int loopCount = 0;
+        protected int foreachLoopCount = 0;
+        protected int switchCount = 0;
+
+        protected Stack<String> breakLabels = new Stack<String> ();
+        protected Stack<String> continueLabels = new Stack<String> ();
+        protected Stack<String> removeLabels = new Stack<String> ();
+
+        protected boolean breakSupported() {
+            return loopCount != 0 || foreachLoopCount != 0 || switchCount != 0;
+        }
+
+        protected boolean breakSupported(String label) {
+            return breakLabels.contains(label);
+        }
+
+        protected boolean continueSupported() {
+            return loopCount != 0 || foreachLoopCount != 0;
+        }
+
+        protected boolean continueSupported(String label) {
+            return continueLabels.contains(label);
+        }
+
+        protected boolean removeSupported() {
+            return foreachLoopCount != 0;
+        }
+
+        protected boolean removeSupported(String label) {
+            return removeLabels.contains(label);
+        }
+
+        protected void pushBlockLabel(String label) {
+            breakLabels.push(label);
+        }
+
+        protected void popBlockLabel() {
+            breakLabels.pop();
+        }
+
+        protected void pushLoopLabel(String label) {
+            breakLabels.push(label);
+            continueLabels.push(label);
+        }
+
+        protected void popLoopLabel() {
+            breakLabels.pop();
+            continueLabels.pop();
+        }
+
+        protected void pushForeachLabel(String label) {
+            breakLabels.push(label);
+            continueLabels.push(label);
+            removeLabels.push(label);
+        }
+
+        protected void popForeachLabel() {
+            breakLabels.pop();
+            continueLabels.pop();
+            removeLabels.pop();
+        }
+    }
+
+    /**
+     * The current scope for break/continue/remove statements.
+     */
+    protected BranchScope branchScope = null;
+
+    /**
+     * When parsing inner functions/lambda, need to stack the target scope
+     */
+    protected Stack<BranchScope> branchScopes = new Stack<BranchScope> ();
+
 }
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 590b575..dfa8f74 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -44,8 +44,6 @@
 
 public final class Parser extends JexlParser
 {
-    private int loopCount = 0;
-
     public ASTJexlScript parse(JexlInfo jexlInfo, JexlFeatures jexlFeatures, String jexlSrc, Scope scope) {
         JexlFeatures previous = getFeatures();
         try {
@@ -59,6 +57,7 @@
             source = jexlSrc;
             pragmas = null;
             frame = scope;
+            branchScope = new BranchScope();
             ReInit(new java.io.StringReader(jexlSrc));
             ASTJexlScript script = jexlFeatures.supportsScript()? JexlScript(scope) : JexlExpression(scope);
             script.jjtSetValue(info);
@@ -100,6 +99,19 @@
             dotLexState = defaultLexState;
         }
     }
+
+    public void pushQ() {
+        dotLexState = curLexState;
+        curLexState = QUALIFIED;
+    }
+
+    public void popQ() {
+        if (curLexState == QUALIFIED) {
+            curLexState = dotLexState;
+            dotLexState = defaultLexState;
+        }
+    }
+
 }
 /***************************************
  *     Skip & Number literal tokens
@@ -124,57 +136,82 @@
     | < FOR : "for" >
     | < WHILE : "while" >
     | < DO : "do" >
-    | < NEW : "new" >
+    | < SWITCH : "switch" >
+    | < CASE : "case" >
+    | < DCASE : "default" >
+    | < TRY : "try" >
+    | < CATCH : "catch" >
+    | < FINALLY : "finally" >
+    | < THROW : "throw" >
+    | < ASSERT : "assert" >
+    | < SYNCHRONIZED : "synchronized" >
+    | < NEW : "new" > { pushQ(); } /* Lexical state is now QUALIFIED */
     | < VAR : "var" >
     | < EMPTY : "empty" > { popDot(); } /* Revert state to default if was DOT_ID. */
     | < SIZE : "size" > { popDot(); } /* Revert state to default if was DOT_ID. */
-    | < NULL : "null" >
+    | < THIS : "this" >
+    | < NULL : "null" | "\u2205" >
     | < TRUE : "true" >
     | < FALSE : "false" >
     | < RETURN : "return" >
     | < FUNCTION : "function" >
-    | < LAMBDA : "->" >
+    | < LAMBDA : "->" | "\u2192" >
+    | < LAMBDAE : "=>" | "\u21D2" >
     | < BREAK : "break" >
     | < CONTINUE : "continue" >
-    | < PRAGMA : "#pragma" >
+    | < REMOVE : "remove" > { popDot(); } /* Revert state to default if was DOT_ID. */
+    | < PRAGMA : "#pragma" > { pushQ(); } /* Lexical state is now QUALIFIED */
 }
 
 <*> TOKEN : { /* SEPARATORS */
-      < LPAREN : "(" >
+      < LPAREN : "(" > { popQ(); } /* Revert state to default. */
     | < RPAREN : ")" >
     | < LCURLY : "{" >
     | < RCURLY : "}" >
-    | < LBRACKET : "[" >
+    | < LBRACKET : "[" > { popQ(); } /* Revert state to default. */
     | < RBRACKET : "]" >
     | < SEMICOL : ";" >
-    | < COLON : ":" > 
+    | < COLON : ":" >
     | < COMMA : "," >
     | < DOT : "." > { pushDot(); } /* Lexical state is now DOT_ID */
     | < QDOT : "?." > { pushDot(); } /* Lexical state is now DOT_ID */
-    | < ELIPSIS : "..." >
+    | < ELLIPSIS : "..." | "\u2026">
+    | < HBRACKET : "#[" >
+    | < HCURLY : "#{" >
+}
+
+<*> TOKEN : { /* STREAMS */
+      < DOTP : ".(" >
+    | < DOTB : ".[" >
+    | < DOTC : ".{" >
+    | < DOTS : ".@" >
 }
 
 <*> TOKEN : { /* CONDITIONALS */
       < QMARK : "?" >
     | < ELVIS : "?:" >
     | < NULLP : "??" >
-    | < AND : "&&" | "and" >
-    | < OR : "||" | "or" >
+    | < AND : "&&" | "and" | "\u2227" >
+    | < OR : "||" | "or" | "\u2228" >
 }
 
 <*> TOKEN : { /* COMPARISONS */
-      < eq : "==" | "eq" >
-    | < ne : "!=" | "ne" >
-    | < req : "=~" > // regexp equal
-    | < rne : "!~" > // regexp not equal
+      < eq : "==" | "eq" | "\u2261">
+    | < ne : "!=" | "ne" | "\u2260" >
+    | < req : "=~" | "in" | "\u2208" > // regexp equal
+    | < rne : "!~" | "!in" | "\u2209" > // regexp not equal
+    | < is : "===" > // identitical
+    | < ni : "!==" > // not identitical
     | < seq : "=^" > // starts equal
     | < eeq : "=$" > // ends equal
     | < sne : "!^" > // start not equal
     | < ene : "!$" > // ends not equal
     | < gt : ">" | "gt" >
-    | < ge : ">=" | "ge" >
+    | < ge : ">=" | "ge" | "\u2265" >
     | < lt : "<" | "lt" >
-    | < le : "<=" | "le" >
+    | < le : "<=" | "le" | "\u2264" >
+    | < iof : "instanceof" > { pushQ(); }
+
 }
 
 <*> TOKEN : { /* OPERATORS */
@@ -186,20 +223,29 @@
     | < and_assign : "&=" >
     | < or_assign : "|=" >
     | < xor_assign : "^=" >
+    | < shl_assign : "<<=" >
+    | < sar_assign : ">>=" >
+    | < shr_assign : ">>>=" >
 
     | < assign : "=" >
+    | < increment : "++" >
+    | < decrement : "--" >
     | < plus : "+" >
     | < minus : "-" >
-    | < mult : "*" >
+    | < unary_minus : "\u2212" >
+    | < mult : "*" | "\u22C5" >
     | < div : "/" | "div" >
     | < mod : "%" | "mod" >
-    | < not : "!" | "not" >
+    | < not : "!" | "not" | "\u00AC" >
     | < and : "&" >
     | < or : "|" >
     | < xor : "^" >
+    | < shl : "<<" >
+    | < shr : ">>>" >
+    | < sar : ">>" >
 
     | < tilda : "~" >
-    | < range : ".." >
+    | < range : ".." | "\u2025" >
 }
 
 /***************************************
@@ -231,6 +277,13 @@
   < #ESCAPE: "\\" [" ", "'", "\"", "\\"] >
 }
 
+<QUALIFIED> TOKEN : /* IDENTIFIERS */
+{
+  < QUALIFIED_IDENTIFIER: <QNAME> ("." <QNAME>)* > { popQ(); } /* Revert state to default. */
+|
+  < #QNAME: [ "a"-"z", "A"-"Z", "_", "$", "@" ] ([ "a"-"z", "A"-"Z", "_", "$", "@", "0"-"9" ])* >
+}
+
 <REGISTERS> TOKEN : /* REGISTERS: parser.ALLOW_REGISTER must be set to true before calling parse */
 {
   < REGISTER: "#" (["0"-"9"])+ >
@@ -283,10 +336,12 @@
     jjtThis.setScope(frame);
 }
 {
-   ( ( Statement() )*) <EOF>
-   {
-        return jjtThis.script();
-   }
+   (LOOKAHEAD(<PRAGMA>) Pragma())*
+   (
+      LOOKAHEAD( LambdaLookahead() ) Lambda() (LOOKAHEAD(1) <SEMICOL>)? <EOF> { return jjtThis.script(); }
+      |
+      ( ( Statement() )*) <EOF> { return jjtThis.script(); }
+   )
 }
 
 ASTJexlScript JexlExpression(Scope frame) #JexlScript : {
@@ -316,31 +371,82 @@
 {
     <SEMICOL>
     | LOOKAHEAD(<ANNOTATION>) AnnotatedStatement()
+    | LOOKAHEAD(LabelledStatementLookahead()) LabelledStatement()
     | LOOKAHEAD(<LCURLY> Expression() <SEMICOL>) Block() // to disambiguate the set literals
     | LOOKAHEAD(<LCURLY> Statement() <SEMICOL>) Block() //  to disambiguate the set literals
     | IfStatement()
-    | ForeachStatement()
+    | LOOKAHEAD(<FOR> <LPAREN> ForeachVar() <COLON>) ForeachStatement()
+    | ForStatement()
     | WhileStatement()
     | DoWhileStatement()
-    | ExpressionStatement()
+    | LOOKAHEAD(<TRY> <LPAREN>) TryWithResourceStatement()
+    | TryStatement()
+    | ThrowStatement()
+    | AssertStatement()
+    | SynchronizedStatement()
+    | SwitchStatement()
+    | LOOKAHEAD(MultipleAssignmentIdentifier() <assign>) MultipleAssignmentStatement()
+    | LOOKAHEAD(2) ExpressionStatement()
     | ReturnStatement()
     | Continue()
+    | Remove()
     | Break()
     | Var()
-    | Pragma()
 }
 
-void Block() #Block : {}
+void LabelledStatementLookahead() #void() : {}
+{
+  <IDENTIFIER> <COLON> ( <LCURLY> | <FOR> | <WHILE> | <DO> | <SWITCH> )
+}
+
+void LabelledStatement() #void : 
+{
+    Token t = null;
+    String label = null;
+    ASTBlock b;
+    ASTForStatement f;
+    ASTForeachStatement fe;
+    ASTWhileStatement w;
+    ASTDoWhileStatement dw;
+    ASTSwitchStatement s;
+}
+{
+    t=<IDENTIFIER> { label = t.image; } <COLON>
+    (
+        LOOKAHEAD(<LCURLY>) { branchScope.pushBlockLabel(label); } b = Block() { b.setLabel(label); branchScope.popBlockLabel(); }
+        |
+        LOOKAHEAD(<FOR> <LPAREN> ForeachVar() <COLON>) { branchScope.pushForeachLabel(label); } fe = ForeachStatement() { fe.setLabel(label); branchScope.popForeachLabel(); }
+        |
+        { branchScope.pushLoopLabel(label); } f = ForStatement() { f.setLabel(label); branchScope.popLoopLabel(); }
+        |
+        { branchScope.pushLoopLabel(label); } w = WhileStatement() { w.setLabel(label); branchScope.popLoopLabel(); }
+        |
+        { branchScope.pushLoopLabel(label); } dw = DoWhileStatement() { dw.setLabel(label); branchScope.popLoopLabel(); }
+        |
+        { branchScope.pushBlockLabel(label); } s = SwitchStatement() { s.setLabel(label); branchScope.popBlockLabel(); }
+    )
+}
+
+ASTBlock Block() #Block : {}
 {
     <LCURLY> ( Statement() )* <RCURLY>
+    { return jjtThis; }
 }
 
-
-void ExpressionStatement() #void : {}
+void ExpressionStatement() : {}
 {
-    Expression() (LOOKAHEAD(1) Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)?
+    Expression() (LOOKAHEAD(2) Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)?
 }
 
+void MultipleAssignmentStatement() #MultipleAssignment : {}
+{
+    MultipleAssignmentIdentifier() <assign> Expression()
+}
+
+void MultipleAssignmentIdentifier() #void : {}
+{
+    <LPAREN> Identifier() (LOOKAHEAD(1) <COMMA> Identifier())* <RPAREN>
+}
 
 void IfStatement() : {}
 {
@@ -349,14 +455,78 @@
     ( LOOKAHEAD(1) <ELSE>  (LOOKAHEAD(1) Block() | Statement()) )?
 }
 
-void WhileStatement() : {}
+ASTWhileStatement WhileStatement() : {}
 {
-    <WHILE> <LPAREN> Expression() <RPAREN>  { loopCount += 1; }  (LOOKAHEAD(1) Block() | Statement()) { loopCount -= 1; }
+    <WHILE> <LPAREN> Expression() <RPAREN>  { branchScope.loopCount += 1; }  (LOOKAHEAD(1) Block() | Statement()) { branchScope.loopCount -= 1; }
+    { return jjtThis; }
 }
 
-void DoWhileStatement() : {}
+ASTDoWhileStatement DoWhileStatement() : {}
 {
-    <DO> { loopCount += 1; } (LOOKAHEAD(1) Block() | Statement()) <WHILE> <LPAREN> Expression() <RPAREN> { loopCount -= 1; }
+    <DO> { branchScope.loopCount += 1; } (LOOKAHEAD(1) Block() | Statement()) <WHILE> <LPAREN> Expression() <RPAREN> { branchScope.loopCount -= 1; }
+    { return jjtThis; }
+}
+
+void TryStatement() : {}
+{
+    <TRY> Block() ( (<CATCH> <LPAREN> TryVar() <RPAREN> Block() (<FINALLY> Block())?) | <FINALLY> Block() )
+}
+
+void TryWithResourceStatement() : {}
+{
+    <TRY> <LPAREN> TryResource() <RPAREN> Block() ( (<CATCH> <LPAREN> TryVar() <RPAREN> Block() (<FINALLY> Block())?) | <FINALLY> Block() )?
+}
+
+void TryResource() : {}
+{
+     LOOKAHEAD(TryVar() <assign>) TryVar() <assign> Expression()
+     |
+     Expression()
+}
+
+void TryVar() : {}
+{
+    <VAR> DeclareVar()
+    |
+    Identifier()
+}
+
+void ThrowStatement() : {}
+{
+    <THROW> Expression()
+}
+
+void AssertStatement() : {}
+{
+    <ASSERT> Expression() [ <COLON> Expression() ]
+}
+
+void SynchronizedStatement() : {}
+{
+    <SYNCHRONIZED> <LPAREN> Expression() <RPAREN> (LOOKAHEAD(1) Block() | Statement())
+}
+
+ASTSwitchStatement SwitchStatement() : {}
+{
+    <SWITCH> <LPAREN> Expression() <RPAREN> { branchScope.switchCount += 1; } <LCURLY> SwitchStatementBlock() <RCURLY> { branchScope.switchCount -= 1; }
+    { return jjtThis; }
+}
+
+void SwitchStatementBlock() #void : {}
+{
+    SwitchStatementCase() (LOOKAHEAD(SwitchStatementBlock()) SwitchStatementBlock())*
+    |
+    SwitchStatementDefault() (LOOKAHEAD(SwitchStatementCase()) SwitchStatementCase())*
+}
+
+void SwitchStatementCase() : {}
+{
+    <CASE> Literal() <COLON> ((LOOKAHEAD(1) Block() | Statement()) )*
+}
+
+void SwitchStatementDefault() : {}
+{
+    <DCASE> <COLON> ((LOOKAHEAD(1) Block() | Statement()) )*
 }
 
 void ReturnStatement() : {}
@@ -364,31 +534,75 @@
     <RETURN> ExpressionStatement()
 }
 
-void Continue() #Continue : {}
+void Continue() #Continue : 
 {
-    <CONTINUE> { if (loopCount == 0) { throwParsingException(jjtThis); } }
+    Token t = null;
+}
+{
+    <CONTINUE> (LOOKAHEAD(<IDENTIFIER>) t = <IDENTIFIER> { if (!branchScope.continueSupported(t.image)) throwParsingException(jjtThis); jjtThis.setLabel(t.image); } )?
+    { if (t == null && !branchScope.continueSupported()) { throwParsingException(jjtThis); } }
 }
 
-void Break() #Break : {}
+void Remove() #Remove :
 {
-    <BREAK> { if (loopCount == 0) { throwParsingException(jjtThis); } }
+    Token t = null;
+}
+{
+    <REMOVE> (LOOKAHEAD(<IDENTIFIER>) t = <IDENTIFIER> { if (!branchScope.removeSupported(t.image)) throwParsingException(jjtThis); jjtThis.setLabel(t.image); } )?
+    { if (t == null && !branchScope.removeSupported()) { throwParsingException(jjtThis); } }
 }
 
-void ForeachStatement() : {}
+void Break() #Break :
 {
-    <FOR> <LPAREN> ForEachVar() <COLON>  Expression() <RPAREN> { loopCount += 1; } (LOOKAHEAD(1) Block() | Statement()) { loopCount -= 1; }
+    Token t = null;
+}
+{
+    <BREAK> (LOOKAHEAD(<IDENTIFIER>) t = <IDENTIFIER> { if (!branchScope.breakSupported(t.image)) throwParsingException(jjtThis); jjtThis.setLabel(t.image); } )?
+    { if (t == null && !branchScope.breakSupported()) { throwParsingException(jjtThis); } }
 }
 
-void ForEachVar() #Reference : {}
+ASTForStatement ForStatement() : {}
 {
-    <VAR> DeclareVar()
-|
-    Identifier()
+    <FOR> <LPAREN> ForInitializationNode() <SEMICOL> ForTerminationNode() <SEMICOL> ForIncrementNode() <RPAREN> { branchScope.loopCount += 1; } (LOOKAHEAD(1) Block() | Statement()) { branchScope.loopCount -= 1; }
+    { return jjtThis; }
+}
+
+void ForInitializationNode() : {}
+{
+    (Expression() | Var())?
+}
+
+void ForTerminationNode() : {}
+{
+    (ConditionalExpression())?
+}
+
+void ForIncrementNode() : {}
+{
+    (Expression())?
+}
+
+ASTForeachStatement ForeachStatement() : {}
+{
+    <FOR> <LPAREN> ForeachVar() <COLON> Expression() <RPAREN> { branchScope.foreachLoopCount += 1; } (LOOKAHEAD(1) Block() | Statement()) { branchScope.foreachLoopCount -= 1; }
+    { return jjtThis; }
+}
+
+void ForeachVar() : {}
+{
+    <VAR> DeclareVar() (LOOKAHEAD(1) <COMMA> DeclareExtVar())?
+    |
+    Identifier() (LOOKAHEAD(1) <COMMA> Identifier())?
 }
 
 void Var() #void : {}
 {
-    <VAR> DeclareVar() (LOOKAHEAD(1) <assign> Expression() #Assignment(2))?
+    <VAR>
+    (
+       LOOKAHEAD(1) ((<LPAREN> DeclareExtVar() (LOOKAHEAD(1) <COMMA> DeclareExtVar())* <RPAREN> <assign> Expression()) #MultipleAssignment())
+       |
+       DeclareVar() (LOOKAHEAD(1) <assign> Expression() #Assignment(2))?
+    )
 }
 
 void DeclareVar() #Var :
@@ -399,33 +613,32 @@
     t=<IDENTIFIER> { declareVariable(jjtThis, t); }
 }
 
-void Pragma() #void :
-{
-    LinkedList<String> lstr = new LinkedList<String>();
-    Object value;
-}
-{
-<PRAGMA> pragmaKey(lstr)  value=pragmaValue() { declarePragma(stringify(lstr), value); }
-}
-
-void pragmaKey(LinkedList<String> lstr) #void :
+void DeclareExtVar() #ExtVar :
 {
     Token t;
 }
 {
-    t=<IDENTIFIER> (LOOKAHEAD(2) <DOT> pragmaKey(lstr) )* { lstr.addFirst(t.image); }
+    t=<IDENTIFIER> { declareVariable(jjtThis, t); }
 }
 
-Object pragmaValue() #void :
+void Pragma() #void :
 {
-Token v;
-LinkedList<String> lstr = new LinkedList<String>();
+    Token t;
+    Object value;
+}
+{
+    <PRAGMA> t=<QUALIFIED_IDENTIFIER> value=PragmaValue() { declarePragma(t.image, value); }
+}
+
+Object PragmaValue() #void :
+{
+    Token v;
 }
 {
       LOOKAHEAD(1) v=<INTEGER_LITERAL> { return NumberParser.parseInteger(v.image); }
     | LOOKAHEAD(1) v=<FLOAT_LITERAL> { return NumberParser.parseDouble(v.image); }
     | LOOKAHEAD(1) v=<STRING_LITERAL> { return Parser.buildString(v.image, true); }
-    | LOOKAHEAD(1)  pragmaKey(lstr) { return stringify(lstr); }
+    | LOOKAHEAD(1) v=<QUALIFIED_IDENTIFIER> { return v.image; }
     | LOOKAHEAD(1) <TRUE> { return true; }
     | LOOKAHEAD(1) <FALSE> { return false; }
     | LOOKAHEAD(1) <NULL> { return null; }
@@ -439,29 +652,35 @@
 
 void Expression() #void : {}
 {
-      AssignmentExpression()
+  AssignmentExpression()
 }
 
 void AssignmentExpression() #void : {}
 {
   ConditionalExpression()
   ( LOOKAHEAD(2) (
-    <plus_assign>  Expression() #SetAddNode(2)
-  |
-    <mult_assign>  Expression() #SetMultNode(2)
-  |
-    <div_assign>  Expression() #SetDivNode(2)
-  |
-    <mod_assign>  Expression() #SetModNode(2)
-  |
-    <and_assign>  Expression() #SetAndNode(2)
-  |
-    <or_assign>  Expression() #SetOrNode(2)
-  |
+    <plus_assign> Expression() #SetAddNode(2)
+    |
+    <mult_assign> Expression() #SetMultNode(2)
+    |
+    <div_assign> Expression() #SetDivNode(2)
+    |
+    <mod_assign> Expression() #SetModNode(2)
+    |
+    <and_assign> Expression() #SetAndNode(2)
+    |
+    <or_assign> Expression() #SetOrNode(2)
+    |
     <xor_assign> Expression() #SetXorNode(2)
-  |
-    <minus_assign>  Expression() #SetSubNode(2)
-  |
+    |
+    <minus_assign> Expression() #SetSubNode(2)
+    |
+    <shl_assign> Expression() #SetShlNode(2)
+    |
+    <sar_assign> Expression() #SetSarNode(2)
+    |
+    <shr_assign> Expression() #SetShrNode(2)
+    |
     <assign> Expression() #Assignment(2)
   ) )*
 }
@@ -473,87 +692,130 @@
 void ConditionalExpression() #void : {}
 {
   ConditionalOrExpression()
-  (
-    <QMARK> Expression() <COLON> Expression() #TernaryNode(3)
-  |
-    <ELVIS> Expression() #TernaryNode(2)
-  |
+  ( LOOKAHEAD(2) (
+    <QMARK> TernaryExpression()
+    |
+    <ELVIS> Expression() #ElvisNode(2)
+    |
     <NULLP> Expression() #NullpNode(2)
-  )?
+  ) )?
+}
+
+void TernaryExpression() #void : {}
+{
+  LOOKAHEAD(Expression() <COLON>) Expression() <COLON> Expression() #TernaryNode(3)
+  |
+  Expression() #TernaryNode(2)
 }
 
 void ConditionalOrExpression() #void : {}
 {
   ConditionalAndExpression()
-  ( <OR> ConditionalAndExpression() #OrNode(2) )*
+  ( LOOKAHEAD(2) (
+     <OR> ConditionalAndExpression() #OrNode(2)
+  ) )*
 }
 
 void ConditionalAndExpression() #void : {}
 {
   InclusiveOrExpression()
-  ( <AND> InclusiveOrExpression() #AndNode(2) )*
+  ( LOOKAHEAD(2) (
+     <AND> InclusiveOrExpression() #AndNode(2)
+  ) )*
 }
 
 void InclusiveOrExpression() #void : {}
 {
   ExclusiveOrExpression()
-  ( <or> ExclusiveOrExpression() #BitwiseOrNode(2) )*
+  ( LOOKAHEAD(2) (
+     <or> ExclusiveOrExpression() #BitwiseOrNode(2)
+  ) )*
 }
 
 void ExclusiveOrExpression() #void : {}
 {
   AndExpression()
-  ( <xor> AndExpression() #BitwiseXorNode(2) )*
+  ( LOOKAHEAD(2) (
+     <xor> AndExpression() #BitwiseXorNode(2)
+  ) )*
 }
 
 void AndExpression() #void : {}
 {
   EqualityExpression()
-  ( <and> EqualityExpression() #BitwiseAndNode(2) )*
+  ( LOOKAHEAD(2) (
+     <and> EqualityExpression() #BitwiseAndNode(2)
+  ) )*
 }
 
 void EqualityExpression() #void : {}
 {
   RelationalExpression()
-  (
+  ( LOOKAHEAD(2) (
      <eq> RelationalExpression() #EQNode(2)
    |
      <ne> RelationalExpression() #NENode(2)
    |
-     <range> RelationalExpression() #RangeNode(2) // range
-  )?
+     <req> RelationalExpression() #ERNode(2) // equals regexp
+   |
+     <rne> RelationalExpression() #NRNode(2) // not equals regexp
+   |
+     <is> RelationalExpression() #ISNode(2) // identical
+   |
+     <ni> RelationalExpression() #NINode(2) // not identical
+
+  ) )?
 }
 
 void RelationalExpression() #void : {}
 {
-  AdditiveExpression()
-  (
-    <lt> AdditiveExpression() #LTNode(2)
+  RangeExpression()
+  ( LOOKAHEAD(2) (
+    <lt> RangeExpression() #LTNode(2)
    |
-    <gt> AdditiveExpression() #GTNode(2)
+    <gt> RangeExpression() #GTNode(2)
    |
-    <le> AdditiveExpression() #LENode(2)
+    <le> RangeExpression() #LENode(2)
    |
-    <ge> AdditiveExpression() #GENode(2)
+    <ge> RangeExpression() #GENode(2)
    |
-    <req> AdditiveExpression() #ERNode(2) // equals regexp
+    <seq> RangeExpression() #SWNode(2) // starts with
    |
-    <rne> AdditiveExpression() #NRNode(2) // not equals regexp
+    <sne> RangeExpression() #NSWNode(2) // not starts with
    |
-    <seq> AdditiveExpression() #SWNode(2) // starts with
+    <eeq> RangeExpression() #EWNode(2) // ends with
    |
-    <sne> AdditiveExpression() #NSWNode(2) // not starts with
+    <ene> RangeExpression() #NEWNode(2) // not ends with
    |
-    <eeq> AdditiveExpression() #EWNode(2) // ends with
-   |
-    <ene> AdditiveExpression() #NEWNode(2) // not ends with
-  )?
+    <iof> TypeReference() #IOFNode(2) // instanceof 
+
+  ) )?
+}
+
+void RangeExpression() #void : {}
+{
+  ShiftExpression()
+  ( LOOKAHEAD(2) (
+     <range> ShiftExpression() #RangeNode(2) // range
+  ) )?
 }
 
 /***************************************
  *      Arithmetic
  ***************************************/
 
+void ShiftExpression() #void : {}
+{
+  AdditiveExpression()
+  ( LOOKAHEAD(2) (
+    <shl> AdditiveExpression() #ShiftLeftNode(2)
+  |
+    <shr> AdditiveExpression() #ShiftRightUnsignedNode(2)
+  |
+    <sar> AdditiveExpression() #ShiftRightNode(2)
+  ) )*
+}
+
 void AdditiveExpression() #void : {}
 {
   MultiplicativeExpression()
@@ -567,30 +829,143 @@
 void MultiplicativeExpression() #void : {}
 {
   UnaryExpression()
-  (
+  ( LOOKAHEAD(2) (
     <mult> UnaryExpression() #MulNode(2)
   |
     <div> UnaryExpression() #DivNode(2)
   |
     <mod> UnaryExpression() #ModNode(2)
-  )*
+  ) )*
 }
 
 void UnaryExpression() #void : {}
 {
-    <minus> UnaryExpression() #UnaryMinusNode(1)
+  <minus> UnaryExpression() #UnaryMinusNode(1)
   |
-    <tilda> UnaryExpression() #BitwiseComplNode(1)
+  <unary_minus> UnaryExpression() #UnaryMinusNode(1)
   |
-    <not> UnaryExpression() #NotNode(1)
+  <plus> UnaryExpression() #UnaryPlusNode(1)
   |
-    <EMPTY> UnaryExpression() #EmptyFunction(1)
+  <increment> UnaryExpression() #IncrementNode(1)
   |
-    <SIZE> UnaryExpression() #SizeFunction(1)
+  <decrement> UnaryExpression() #DecrementNode(1)
   |
-    ValueExpression()
+  <mult> UnaryExpression() #IndirectNode(1)
+  |
+  <tilda> UnaryExpression() #BitwiseComplNode(1)
+  |
+  <not> UnaryExpression() #NotNode(1)
+  |
+  <EMPTY> UnaryExpression() #EmptyFunction(1)
+  |
+  <SIZE> UnaryExpression() #SizeFunction(1)
+  |
+  <ELLIPSIS> EnumerationExpression()
+  |
+  PostfixExpression()
 }
 
+void PostfixExpression() #void : {}
+{
+  PointerExpression()
+  ( LOOKAHEAD(1) (
+    <increment> #IncrementPostfixNode(1)
+  |
+    <decrement> #DecrementPostfixNode(1)
+  ) )*
+}
+
+void PointerExpression() #void : {}
+{
+  <and> ValueExpression() #PointerNode(1)
+  |
+  ValueExpression()
+}
+
+void EnumerationExpression() #void : {}
+{
+    ((IteratorExpression() (LOOKAHEAD(2) EnumerationAccess() )*) #EnumerationReference(>1) (LOOKAHEAD(<DOTS>) Reduction())?) #Reference(>1)
+}
+
+void EnumerationAccess() #void : {}
+{
+    LOOKAHEAD(<DOTB>) ArrayProjection()
+    |
+    LOOKAHEAD(<DOTC>) MapProjection()
+    |
+    LOOKAHEAD(<DOTP>) Selection()
+}
+
+void Reduction() #ReductionNode : {}
+{
+    <DOTS> <LPAREN> [LOOKAHEAD(Expression() <COLON>) Expression() <COLON>] Lambda() <RPAREN>
+}
+
+void Selection() #SelectionNode : {}
+{
+    <DOTP> (
+      StopCountSelection()
+      |
+      StartCountSelection()
+      |
+      Lambda()
+    ) <RPAREN>
+}
+
+void StopCountSelection() #StopCountNode : {}
+{
+    <lt> Expression()
+}
+
+void StartCountSelection() #StartCountNode : {}
+{
+    <gt> Expression()
+}
+
+void ArrayProjection() #ProjectionNode : {}
+{
+    <DOTB> ProjectionExpression() ( LOOKAHEAD(2) <COMMA> ProjectionExpression() )* <RBRACKET>
+}
+
+void MapProjection() #MapProjectionNode : {}
+{
+    <DOTC> ProjectionExpression() <COLON> ProjectionExpression() <RCURLY>
+}
+
+void ProjectionExpression() #void : {}
+{
+    LOOKAHEAD( LambdaLookahead() ) Lambda()
+    |
+    (Identifier() ( LOOKAHEAD(2) ProjectionMemberExpression() )*) #Reference(>1)
+}
+
+void ProjectionMemberExpression() #void : {}
+{
+    LOOKAHEAD(ProjectionMethodCall()) ProjectionMethodCall()
+    |
+    ProjectionMemberAccess()
+}
+
+void ProjectionMemberAccess() #void : {}
+{
+    LOOKAHEAD(<LBRACKET>) ArrayAccess()
+    |
+    LOOKAHEAD(<DOT>) IdentifierAccess()
+    |
+    LOOKAHEAD(<QDOT>) IdentifierAccess()
+}
+
+void ProjectionMethodCall() #void : {}
+{
+    (ProjectionMemberAccess() (LOOKAHEAD(<LPAREN>) Arguments())+) #MethodNode(>1)
+}
+
+void IteratorExpression() #void : {}
+{
+  LOOKAHEAD(<LPAREN> Expression() <COLON>) <LPAREN> Expression() <COLON> Lambda() <RPAREN> #EnumerationNode(2)
+  |
+  ValueExpression() #EnumerationNode(1)
+}
 
 /***************************************
  *      Identifier & Literals
@@ -606,7 +981,6 @@
     t=<REGISTER> { jjtThis.setSymbol(t.image); }
 }
 
-
 void NamespaceIdentifier()  #NamespaceIdentifier :
 {
     Token ns;
@@ -621,7 +995,20 @@
     Token t;
 }
 {
-    t=<STRING_LITERAL>  { jjtThis.setSymbol(Parser.buildString(t.image, true));  }
+    t=<STRING_LITERAL> { jjtThis.setSymbol(Parser.buildString(t.image, true));  }
+}
+
+void RemoveIdentifier() #Identifier :
+{
+    Token t;
+}
+{
+    t=<REMOVE> { jjtThis.setSymbol(t.image); }
+}
+
+void This() #void : {}
+{
+    <THIS> #ThisNode
 }
 
 void Literal() #void :
@@ -641,13 +1028,10 @@
 |
   RegexLiteral()
 |
-  NullLiteral()
-|
   NaNLiteral()
 }
 
-void NaNLiteral() #NumberLiteral :
-{}
+void NaNLiteral() #NumberLiteral : {}
 {
     <NAN_LITERAL> { jjtThis.setReal("NaN"); }
 }
@@ -657,8 +1041,7 @@
     <NULL>
 }
 
-void BooleanLiteral() #void :
-{}
+void BooleanLiteral() #void : {}
 {
   <TRUE> #TrueNode
 |
@@ -674,7 +1057,6 @@
   { jjtThis.setNatural(t.image); }
 }
 
-
 void FloatLiteral() #NumberLiteral:
 {
   Token t;
@@ -693,7 +1075,6 @@
   { jjtThis.setLiteral(Parser.buildString(t.image, true)); }
 }
 
-
 void JxltLiteral() #JxltLiteral :
 {
    Token t;
@@ -712,43 +1093,98 @@
   { jjtThis.setLiteral(Parser.buildRegex(t.image)); }
 }
 
-
-void ExtendedLiteral() #ExtendedLiteral() : {}
+void TypeReference() #ClassLiteral() :
 {
-   <ELIPSIS>
+   Token t; 
+   Class value;
+}
+{
+  (LOOKAHEAD(<LBRACKET>) <LBRACKET> <RBRACKET> { jjtThis.setArray(); })+
+  |
+  t=<QUALIFIED_IDENTIFIER>
+  { value = Parser.resolveType(t.image); if (value == null) throwParsingException(jjtThis); jjtThis.setLiteral(value); }
+  (LOOKAHEAD(<LBRACKET>) <LBRACKET> <RBRACKET> { jjtThis.setArray(); })*
+}
+
+void NewTypeReference() #ClassLiteral() :
+{
+   Token t; 
+   Class value;
+}
+{
+  t=<QUALIFIED_IDENTIFIER>
+  { value = Parser.resolveInstantiableType(t.image); if (value == null) throwParsingException(jjtThis); jjtThis.setLiteral(value); }
+}
+
+void ArrayTypeReference() #ClassLiteral() :
+{
+   Token t; 
+   Class value;
+}
+{
+  t=<QUALIFIED_IDENTIFIER>
+  { value = Parser.resolveType(t.image); if (value == null) throwParsingException(jjtThis); jjtThis.setLiteral(value); }
+}
+
+void EmptyListLiteral() #ArrayLiteral() : {}
+{
+   <LBRACKET> <ELLIPSIS> { jjtThis.setExtended(true); } <RBRACKET>
 }
 
 void ArrayLiteral() : {}
 {
    <LBRACKET>
-   (
-        LOOKAHEAD(1) ExtendedLiteral()
-    |
-        (Expression() (LOOKAHEAD(2) <COMMA> Expression() )*)? (<COMMA> ExtendedLiteral())?
-   )
+      (Expression() (LOOKAHEAD(<COMMA> Expression()) <COMMA> Expression() )*)? (LOOKAHEAD(2) <COMMA> <ELLIPSIS> { jjtThis.setExtended(true); })?
    <RBRACKET>
 }
 
+void ImmutableArrayLiteral() #ArrayLiteral() : {}
+{
+   <HBRACKET> (Expression() (LOOKAHEAD(<COMMA> Expression()) <COMMA> Expression() )*)? <RBRACKET> { jjtThis.setImmutable(true); }
+}
+
 void MapLiteral() : {}
 {
     <LCURLY>
     (
-        MapEntry() ( <COMMA> MapEntry() )*
+        MapElement() ( <COMMA> MapElement() )*
     |
         <COLON>
     ) <RCURLY>
 }
 
+void MapElement() #void : {}
+{
+    LOOKAHEAD(<mult> <COLON> <ELLIPSIS>) <mult> <COLON> <ELLIPSIS> ValueExpression() #MapEnumerationNode(1)
+    |
+    MapEntry()
+}
+
 void MapEntry() : {}
 {
     Expression() <COLON> Expression()
 }
 
+void ImmutableMapLiteral() #MapLiteral() : {}
+{
+    <HCURLY>
+    (
+        MapElement() ( <COMMA> MapElement() )*
+    |
+        <COLON>
+    ) <RCURLY> { jjtThis.setImmutable(true); }
+}
+
 void SetLiteral() : {}
 {
     <LCURLY> (Expression() ( <COMMA> Expression() )*)? <RCURLY>
 }
 
+void ImmutableSetLiteral() #SetLiteral : {}
+{
+    <HCURLY> (Expression() ( <COMMA> Expression() )*)? <RCURLY> { jjtThis.setImmutable(true); }
+}
+
 /***************************************
  *      Functions & Methods
  ***************************************/
@@ -775,18 +1211,51 @@
     LOOKAHEAD(2) <IDENTIFIER> <LPAREN>
     |
     LOOKAHEAD(2) <REGISTER> <LPAREN>
+    |
+    LOOKAHEAD(2) <REMOVE> <LPAREN>
 }
 
 void FunctionCall() #void : {}
 {
-      LOOKAHEAD(2) NamespaceIdentifier() Arguments() #FunctionNode(2)
+    LOOKAHEAD(2) NamespaceIdentifier() Arguments() #FunctionNode(2)
     |
-      LOOKAHEAD(2) Identifier(true) Arguments() #FunctionNode(2)
+    LOOKAHEAD(2) Identifier(true) Arguments() #FunctionNode(2)
+    |
+    LOOKAHEAD(2) RemoveIdentifier() Arguments() #FunctionNode(2)
 }
 
-void Constructor() #ConstructorNode() : {}
+void Constructor() #void : {}
 {
-  <NEW> <LPAREN> [ Expression() ( <COMMA> Expression() )* ] <RPAREN>
+    <NEW> 
+    (
+       LOOKAHEAD(<LPAREN>) ForNameConstructor()
+       |
+       LOOKAHEAD(<QUALIFIED_IDENTIFIER> <LPAREN>) QualifiedConstructor()
+       |
+       LOOKAHEAD(<QUALIFIED_IDENTIFIER> <LBRACKET> <RBRACKET>) InitializedArrayConstructor()
+       |
+       LOOKAHEAD(<QUALIFIED_IDENTIFIER> <LBRACKET>) ArrayConstructor()
+    )
+}
+
+void ForNameConstructor() #ConstructorNode() : {}
+{
+    <LPAREN> [ Expression() ( <COMMA> Expression() )* ] <RPAREN>
+}
+
+void QualifiedConstructor() #QualifiedConstructorNode() : {}
+{
+    NewTypeReference() <LPAREN> [ Expression() ( <COMMA> Expression() )* ] <RPAREN>
+}
+
+void ArrayConstructor() #ArrayConstructorNode() : {}
+{
+    ArrayTypeReference() (LOOKAHEAD(2) <LBRACKET> Expression() <RBRACKET>)+ 
+}
+
+void InitializedArrayConstructor() #InitializedArrayConstructorNode() : {}
+{
+    ArrayTypeReference() <LBRACKET> <RBRACKET> <LCURLY> [ Expression() ( <COMMA> Expression() )* ] <RCURLY>
 }
 
 void Parameter() #void :
@@ -799,17 +1268,18 @@
 
 void Parameters() #void : {}
 {
-     <LPAREN> [Parameter() (<COMMA> Parameter())* ] <RPAREN>
+    <LPAREN> [Parameter() (LOOKAHEAD(2) <COMMA> Parameter())* (<ELLIPSIS> { declareVarArgSupport(); })? ] <RPAREN>
 }
 
-
 void LambdaLookahead() #void() : {}
 {
-  <FUNCTION> Parameters()
+  LOOKAHEAD(2) <FUNCTION> Parameters()
   |
-  Parameters() <LAMBDA>
+  LOOKAHEAD(2) <FUNCTION> <LCURLY>
   |
-  Parameter() <LAMBDA>
+  Parameters() ( <LAMBDA> | <LAMBDAE> )
+  |
+  Parameter() ( <LAMBDA> | <LAMBDAE> )
 }
 
 void Lambda() #JexlLambda() :
@@ -817,11 +1287,13 @@
    pushFrame();
 }
 {
-  <FUNCTION> Parameters() Block()
+  LOOKAHEAD(2) <FUNCTION> Parameters() Block()
   |
-  Parameters() <LAMBDA> Block()
+  LOOKAHEAD(2) <FUNCTION> Block()
   |
-  Parameter() <LAMBDA> Block()
+  Parameters() ( <LAMBDA> Block() | <LAMBDAE> Expression() )
+  |
+  Parameter() ( <LAMBDA> Block() | <LAMBDAE> Expression() )
 }
 
 
@@ -841,6 +1313,8 @@
         t=<STRING_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true)); } #IdentifierAccess
     |
         t=<JXLT_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true)); } #IdentifierAccessJxlt
+    |
+        t=<REMOVE> { jjtThis.setIdentifier(t.image); } #IdentifierAccess
     )
     |
     <QDOT> (
@@ -849,12 +1323,14 @@
         t=<STRING_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true)); } #IdentifierAccessSafe
     |
         t=<JXLT_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true)); } #IdentifierAccessSafeJxlt
+    |
+        t=<REMOVE> { jjtThis.setIdentifier(t.image); } #IdentifierAccessSafe
     )
 }
 
 void ArrayAccess() : {}
 {
-    (LOOKAHEAD(1) <LBRACKET> Expression() <RBRACKET>)+
+    (LOOKAHEAD(1) <LBRACKET> Expression() ( <COMMA> Expression() )* <RBRACKET>)+
 }
 
 void MemberAccess() #void : {}
@@ -873,27 +1349,41 @@
 
 void PrimaryExpression() #void : {}
 {
-       LOOKAHEAD( LambdaLookahead() ) Lambda()
+    LOOKAHEAD( LambdaLookahead() ) Lambda()
     |
-       LOOKAHEAD( <LPAREN> ) ReferenceExpression()
+    LOOKAHEAD( <LPAREN> ) ReferenceExpression()
     |
-       LOOKAHEAD( <LCURLY> Expression() <COLON>) MapLiteral()
+    LOOKAHEAD( <LCURLY> MapElement() ) MapLiteral()
     |
-       LOOKAHEAD( <LCURLY> <COLON>) MapLiteral()
+    LOOKAHEAD( <LCURLY> <COLON>) MapLiteral()
     |
-       LOOKAHEAD( <LCURLY> Expression() ) SetLiteral()
+    LOOKAHEAD( <HCURLY> MapElement() ) ImmutableMapLiteral()
     |
-       LOOKAHEAD( <LCURLY> <RCURLY> ) SetLiteral()
+    LOOKAHEAD( <HCURLY> <COLON>) ImmutableMapLiteral()
     |
-       LOOKAHEAD( <LBRACKET> ) ArrayLiteral()
+    LOOKAHEAD( <LCURLY> Expression() ) SetLiteral()
     |
-       LOOKAHEAD( <NEW> ) Constructor()
+    LOOKAHEAD( <LCURLY> <RCURLY> ) SetLiteral()
     |
-       LOOKAHEAD( FunctionCallLookahead() ) FunctionCall()
+    LOOKAHEAD( <HCURLY> Expression() ) ImmutableSetLiteral()
     |
-       Identifier(true)
+    LOOKAHEAD( <HCURLY> <RCURLY> ) ImmutableSetLiteral()
     |
-       Literal()
+    LOOKAHEAD( EmptyListLiteral() ) EmptyListLiteral()
+    |
+    LOOKAHEAD( <LBRACKET> ) ArrayLiteral()
+    |
+    LOOKAHEAD( <HBRACKET> ) ImmutableArrayLiteral()
+    |
+    LOOKAHEAD( <NEW> ) Constructor()
+    |
+    LOOKAHEAD( FunctionCallLookahead() ) FunctionCall()
+    |
+    Identifier(true)
+    |
+    This()
+    |
+    Literal()
 }
 
 void MethodCall() #void : {}
@@ -902,17 +1392,56 @@
     |
     LOOKAHEAD(<DOT> <EMPTY>) (<DOT> <EMPTY> <LPAREN> <RPAREN>) #EmptyMethod(1)
     |
+    LOOKAHEAD(<LCURLY>) (InlinePropertyAssignment() (LOOKAHEAD(<LPAREN>) Arguments())+) #MethodNode(>1)
+    |
     (MemberAccess() (LOOKAHEAD(<LPAREN>) Arguments())+) #MethodNode(>1)
 }
 
-
 void MemberExpression() #void : {}
 {
-    LOOKAHEAD(MethodCall()) MethodCall() | MemberAccess()
+    LOOKAHEAD(MethodCall()) MethodCall()
+    |
+    LOOKAHEAD(<LCURLY>) InlinePropertyAssignment()
+    |
+    MemberAccess()
+}
+
+void InlinePropertyAssignment() : {}
+{
+    <LCURLY> (InlinePropertyBlock() ( <COMMA> InlinePropertyBlock() )* ) <RCURLY>
+}
+
+void InlinePropertyBlock() #void : {}
+{
+    LOOKAHEAD(InlinePropertyName() <COLON>) InlinePropertyEntry()
+    |
+    LOOKAHEAD(<LBRACKET> Expression() <RBRACKET> <COLON>) InlinePropertyArrayEntry()
+    |
+    ((LOOKAHEAD(<LBRACKET>) ArrayAccess() | Identifier()) (LOOKAHEAD(2) MemberAccess() )* InlinePropertyAssignment()) #Reference()
+}
+
+void InlinePropertyName() #void : {}
+{
+    Identifier()
+    |
+    StringLiteral()
+    |
+    JxltLiteral()
+}
+
+void InlinePropertyEntry() : {}
+{
+    InlinePropertyName() <COLON> Expression()
+}
+
+void InlinePropertyArrayEntry() : {}
+{
+    <LBRACKET> Expression() <RBRACKET> <COLON> Expression()
 }
 
 void ValueExpression() #void : {}
 {
+    NullLiteral()
+    |
     ( PrimaryExpression() ( LOOKAHEAD(2) MemberExpression() )*) #Reference(>1)
 }
-
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
index c584459..0364e3b 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
@@ -44,6 +44,8 @@
 
     protected abstract Object visit(ASTBlock node, Object data);
 
+    protected abstract Object visit(ASTExpressionStatement node, Object data);
+
     protected abstract Object visit(ASTIfStatement node, Object data);
 
     protected abstract Object visit(ASTWhileStatement node, Object data);
@@ -52,20 +54,58 @@
 
     protected abstract Object visit(ASTContinue node, Object data);
 
+    protected abstract Object visit(ASTRemove node, Object data);
+
     protected abstract Object visit(ASTBreak node, Object data);
 
+    protected abstract Object visit(ASTForStatement node, Object data);
+
+    protected abstract Object visit(ASTForInitializationNode node, Object data);
+
+    protected abstract Object visit(ASTForTerminationNode node, Object data);
+
+    protected abstract Object visit(ASTForIncrementNode node, Object data);
+
     protected abstract Object visit(ASTForeachStatement node, Object data);
 
+    protected abstract Object visit(ASTForeachVar node, Object data);
+
+    protected abstract Object visit(ASTTryStatement node, Object data);
+
+    protected abstract Object visit(ASTTryVar node, Object data);
+
+    protected abstract Object visit(ASTTryWithResourceStatement node, Object data);
+
+    protected abstract Object visit(ASTTryResource node, Object data);
+
+    protected abstract Object visit(ASTThrowStatement node, Object data);
+
+    protected abstract Object visit(ASTAssertStatement node, Object data);
+
+    protected abstract Object visit(ASTSynchronizedStatement node, Object data);
+
+    protected abstract Object visit(ASTSwitchStatement node, Object data);
+
+    protected abstract Object visit(ASTSwitchStatementCase node, Object data);
+
+    protected abstract Object visit(ASTSwitchStatementDefault node, Object data);
+
     protected abstract Object visit(ASTReturnStatement node, Object data);
 
+    protected abstract Object visit(ASTMultipleAssignment node, Object data);
+
     protected abstract Object visit(ASTAssignment node, Object data);
 
     protected abstract Object visit(ASTVar node, Object data);
 
+    protected abstract Object visit(ASTExtVar node, Object data);
+
     protected abstract Object visit(ASTReference node, Object data);
 
     protected abstract Object visit(ASTTernaryNode node, Object data);
 
+    protected abstract Object visit(ASTElvisNode node, Object data);
+
     protected abstract Object visit(ASTNullpNode node, Object data);
 
     protected abstract Object visit(ASTOrNode node, Object data);
@@ -78,6 +118,10 @@
 
     protected abstract Object visit(ASTBitwiseAndNode node, Object data);
 
+    protected abstract Object visit(ASTISNode node, Object data);
+
+    protected abstract Object visit(ASTNINode node, Object data);
+
     protected abstract Object visit(ASTEQNode node, Object data);
 
     protected abstract Object visit(ASTNENode node, Object data);
@@ -102,6 +146,8 @@
 
     protected abstract Object visit(ASTNEWNode node, Object data);
 
+    protected abstract Object visit(ASTIOFNode node, Object data);
+
     protected abstract Object visit(ASTAddNode node, Object data);
 
     protected abstract Object visit(ASTSubNode node, Object data);
@@ -112,8 +158,28 @@
 
     protected abstract Object visit(ASTModNode node, Object data);
 
+    protected abstract Object visit(ASTShiftLeftNode node, Object data);
+
+    protected abstract Object visit(ASTShiftRightNode node, Object data);
+
+    protected abstract Object visit(ASTShiftRightUnsignedNode node, Object data);
+
     protected abstract Object visit(ASTUnaryMinusNode node, Object data);
 
+    protected abstract Object visit(ASTUnaryPlusNode node, Object data);
+
+    protected abstract Object visit(ASTIncrementNode node, Object data);
+
+    protected abstract Object visit(ASTDecrementNode node, Object data);
+
+    protected abstract Object visit(ASTIncrementPostfixNode node, Object data);
+
+    protected abstract Object visit(ASTDecrementPostfixNode node, Object data);
+
+    protected abstract Object visit(ASTIndirectNode node, Object data);
+
+    protected abstract Object visit(ASTPointerNode node, Object data);
+
     protected abstract Object visit(ASTBitwiseComplNode node, Object data);
 
     protected abstract Object visit(ASTNotNode node, Object data);
@@ -122,6 +188,8 @@
 
     protected abstract Object visit(ASTNullLiteral node, Object data);
 
+    protected abstract Object visit(ASTThisNode node, Object data);
+
     protected abstract Object visit(ASTTrueNode node, Object data);
 
     protected abstract Object visit(ASTFalseNode node, Object data);
@@ -132,9 +200,9 @@
 
     protected abstract Object visit(ASTRegexLiteral node, Object data);
 
-    protected abstract Object visit(ASTSetLiteral node, Object data);
+    protected abstract Object visit(ASTClassLiteral node, Object data);
 
-    protected abstract Object visit(ASTExtendedLiteral node, Object data);
+    protected abstract Object visit(ASTSetLiteral node, Object data);
 
     protected abstract Object visit(ASTArrayLiteral node, Object data);
 
@@ -144,6 +212,14 @@
 
     protected abstract Object visit(ASTMapEntry node, Object data);
 
+    protected abstract Object visit(ASTMapEnumerationNode node, Object data);
+
+    protected abstract Object visit(ASTInlinePropertyAssignment node, Object data);
+
+    protected abstract Object visit(ASTInlinePropertyArrayEntry node, Object data);
+
+    protected abstract Object visit(ASTInlinePropertyEntry node, Object data);
+
     protected abstract Object visit(ASTEmptyFunction node, Object data);
 
     protected abstract Object visit(ASTEmptyMethod node, Object data);
@@ -158,6 +234,12 @@
 
     protected abstract Object visit(ASTConstructorNode node, Object data);
 
+    protected abstract Object visit(ASTQualifiedConstructorNode node, Object data);
+
+    protected abstract Object visit(ASTArrayConstructorNode node, Object data);
+
+    protected abstract Object visit(ASTInitializedArrayConstructorNode node, Object data);
+
     protected abstract Object visit(ASTArrayAccess node, Object data);
 
     protected abstract Object visit(ASTIdentifierAccess node, Object data);
@@ -182,9 +264,31 @@
 
     protected abstract Object visit(ASTSetXorNode node, Object data);
 
+    protected abstract Object visit(ASTSetShlNode node, Object data);
+
+    protected abstract Object visit(ASTSetSarNode node, Object data);
+
+    protected abstract Object visit(ASTSetShrNode node, Object data);
+
     protected abstract Object visit(ASTJxltLiteral node, Object data);
 
     protected abstract Object visit(ASTAnnotation node, Object data);
 
     protected abstract Object visit(ASTAnnotatedStatement node, Object data);
+
+    protected abstract Object visit(ASTEnumerationNode node, Object data);
+
+    protected abstract Object visit(ASTEnumerationReference node, Object data);
+
+    protected abstract Object visit(ASTProjectionNode node, Object data);
+
+    protected abstract Object visit(ASTMapProjectionNode node, Object data);
+
+    protected abstract Object visit(ASTSelectionNode node, Object data);
+
+    protected abstract Object visit(ASTStartCountNode node, Object data);
+
+    protected abstract Object visit(ASTStopCountNode node, Object data);
+
+    protected abstract Object visit(ASTReductionNode node, Object data);
 }
diff --git a/src/main/java/org/apache/commons/jexl3/parser/StringParser.java b/src/main/java/org/apache/commons/jexl3/parser/StringParser.java
index 6072a3f..fb56e28 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/StringParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/StringParser.java
@@ -215,7 +215,7 @@
         strb.append(delim);
         return strb.toString();
     }
-    
+
     /**
      * Remove escape char ('\') from an identifier.
      * @param str the identifier escaped string, ie with a backslash before space, quote, double-quote and backslash
@@ -232,7 +232,7 @@
                     if (strb == null) {
                         strb = new StringBuilder(last);
                         strb.append(str.substring(0, n));
-                    } 
+                    }
                 } else if (strb != null) {
                     strb.append(c);
                 }
@@ -277,4 +277,4 @@
         }
         return strb == null ? str : strb.toString();
     }
-}
\ No newline at end of file
+}
diff --git a/src/site/xdoc/reference/syntax.xml b/src/site/xdoc/reference/syntax.xml
index 904edce..25ccb6b 100644
--- a/src/site/xdoc/reference/syntax.xml
+++ b/src/site/xdoc/reference/syntax.xml
@@ -42,7 +42,7 @@
                         <a href="#Access">Access</a>
                     </li>
                     <li>
-                        <a href="#Conditional">Conditional Statements</a>
+                        <a href="#Control">Control Statements</a>
                     </li>
                 </ol>
             </p>
@@ -71,7 +71,7 @@
                     </td>
                 </tr>
                 <tr>
-                    <td>Identifiers / variables</td>
+                    <td>Identifiers</td>
                     <td>
                         Must start with <code>a-z</code>, <code>A-Z</code>, <code>_</code> or <code>$</code>.
                         Can then be followed by <code>0-9</code>, <code>a-z</code>, <code>A-Z</code>, <code>_</code> or <code>$</code>.
@@ -81,25 +81,13 @@
                             <li>Invalid: <code>9v</code>,<code>!a99</code>,<code>1$</code></li>
                         </ul>
                         <p>
-                            Variable names are <strong>case-sensitive</strong>, e.g. <code>var1</code> and <code>Var1</code> are different variables.
-                        </p>
-                        <p>
-                            <strong>NOTE:</strong> JEXL does not support variables with hyphens in them, e.g.
-                            <code>commons-logging // invalid variable name (hyphenated)</code> is not a valid variable, but instead is treated as a
-                            subtraction of the variable <code>logging</code> from the variable <code>commons</code>
-                        </p>
-                        <p>
-                            JEXL also supports <code>ant-style</code> variables, the following is a valid variable name:
-                            <code>my.dotted.var</code>
-                        </p>
-                        <p>
-                            <strong>N.B.</strong> the following keywords are reserved, and cannot be used as a variable name or property when using the dot operator:
-                            <code>or and eq ne lt gt le ge div mod not null true false new var do while break continue function return</code>
+                            <strong>N.B.</strong> the following keywords are reserved, and cannot be used as an identifier or the property name when using the dot operator:
+                            <code>or and eq ne lt gt le ge in div mod not this null true false instanceof new var do while break continue remove function return try catch finally throw synchronized switch case default</code>
                             For example, the following is invalid:
-                            <code>my.new.dotted.var // invalid ('new' is keyword)</code>
+                            <code>my.new.dotted.var1 // invalid ('new' is keyword)</code>
                             In such cases, quoted identifiers or the [ ] operator can be used, for example:
-                            <code>my.'new'.dotted.var</code>
-                            <code>my['new'].dotted.var</code>
+                            <code>my.'new'.dotted.var1</code>
+                            <code>my['new'].dotted.var1</code>
                         </p>
                     </td>
                 </tr>
@@ -108,7 +96,7 @@
                     <td>
                         <p>
                             A script in JEXL is made up of zero or more statements, optionally enclosed in a function definition block.
-                            Scripts can include one or more pragmas.
+                            One or more pragmas can be specified at the beginning of the script.
                         </p>
                         <p>
                             Scripts can be read from a String, File or URL.
@@ -137,7 +125,7 @@
                 <tr>
                     <td>Statements</td>
                     <td>
-                        A statement can be the empty statement, the semicolon (<code>;</code>), block, conditional, variable declaration or an expression.
+                        A statement can be the empty statement, the semicolon (<code>;</code>), block, control statement, variable declaration, multiple assignment, or an expression.
                         Statements are optionally terminated with a semicolon.
                         A single statement or a statement block can be annotated.
                     </td>
@@ -146,26 +134,48 @@
                     <td>Block</td>
                     <td>
                         A block is simply multiple statements inside curly braces (<code>{, }</code>).
+                        <p>The labeled syntax is also supported:
+                        <code>foo : {...} </code>
+                        Where the label <code>foo</code> can be used in the inner <code>break</code> statements.</p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Contextual variables</td>
+                    <td>Can be specified outside of the script to create the evaluation context.
+                        Contextual variables is of lower precedence in resolution over local variables.
+                        <p>Contextual variable names are <strong>case-sensitive</strong>, e.g. <code>var1</code> and <code>Var1</code> are different variables.
+                        </p>
+                        <p>
+                            JEXL supports <code>ant-style</code> contextual variables, the following is a valid variable name:
+                            <code>my.dotted.var</code>
+                        </p>
                     </td>
                 </tr>
                 <tr>
                     <td>Local variables</td>
-                    <td>Can be defined using the <code>var</code> keyword; their identifying rules are the same as contextual variables.
+                    <td>Can be defined using the <code>var</code> keyword; the local variable name should be a valid identifier.
                         <ul>
                             <li>Basic declaration: <code>var x;</code></li>
                             <li>Declaration with assignment: <code>var theAnswer = 42;</code></li>
                             <li>Invalid declaration: <code>var x.y;</code></li>
                         </ul>
                         Their scope is either the entire script scope or the function definition block;
-                        Local variables they take precedence in resolution over contextual variables.
+                        Local variables take precedence in resolution over contextual variables.
                         When scripts are created with named parameters, those behave as local variables.
-                        Local variables can not use <code>ant-style</code> naming, only one identifier.
+                        <p>Variable names are <strong>case-sensitive</strong>, e.g. <code>var1</code> and <code>Var1</code> are different variables.
+                        </p>
+                        <p>
+                            <strong>NOTE:</strong> JEXL does not support variables with hyphens in them, e.g.
+                            <code>commons-logging // invalid variable name (hyphenated)</code> is not a valid variable, but instead is treated as a
+                            subtraction of the variable <code>logging</code> from the variable <code>commons</code>
+                        </p>
+                        <p>Local variables can not use <code>ant-style</code> naming, only one identifier.</p>
                     </td>
                 </tr>
                 <tr>
                     <td>Expression</td>
                     <td>
-                        An expression can be the literal, variable, assignment, access operator, function definition, function call, method call or
+                        An expression can be the literal, variable, assignment, access operator, iterator operator, stream operator, function definition, function call, method call or
                         an evaluation operator.
                     </td>
                 </tr>
@@ -178,14 +188,36 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>Multiple Assignment</td>
+                    <td>
+                        Assigns the values to the multiple variables (<code>(x,y) = [4,2]</code>). 
+                        Assignment can be combined with local variable declaration using the <code>var</code> keyword, e.g.
+                        <code>var (x,y) = [4,2]</code> will assign <code>4</code> and <code>2</code> to the variables <code>x</code> 
+                        and <code>y</code> respectively. The syntax can be used for arrays as well as lists and sets, 
+                        as well as other expressions that return either of these.
+                        <p>If the list of variables assigned to has more variables than values, excess ones are filled with null values.
+                        If the list of variables has less variables than values, the extra values are ignored.</p>
+                        <p>Multiple assignment can also be used as destructuring assignment when applied to maps and beans, e.g.
+                        <code>var (city,street) = {"city":"NY","street":"5th avenue"}</code> will assign <code>NY</code> and <code>5th avenue</code> to 
+                        the variables <code>city</code> and <code>street</code> respectively. The variable names should match
+                        those of bean properties, or map keys, e.g.
+                        <code>var (city) = foo</code> and <code>var city = foo.city</code> are equivalent.
+                        </p> 
+                    </td>
+                </tr>
+                <tr>
                     <td>Function definition</td>
                     <td>
                         Defines a function within the script, usually associated with a local variable assignment.
                         <code>var fun = function(x, y) { x + y }</code>
                         The following syntax is also supported
                         <code>var fun = (x, y) -> { x + y }</code>
-                        If the function has only one argument the parentheses may be omitted
+                        If the function has only one argument the parentheses may be ommited
                         <code>var fun = x -> { x * x }</code>
+                        If the function is a simple expression the following syntax can also be used
+                        <code>var fun = (x, y) => x + y</code>
+                        Function can also take arbitraty number arguments, in that case the following syntax can be used
+                        <code>var fun = (x, y...) -> {x + y[0] + y[1]}</code>
                         <p>Note that functions can use local variables and parameters from their declaring script.
                             Those variables values are bound to the function environment at definition time.</p>
                         <code>var t = 20; var s = function(x, y) {x + y + t}; t = 54; s(15, 7)</code>
@@ -199,6 +231,9 @@
                         Calling a function follows the usual convention, e.g.
                         <code>fun(17, 25)</code> will call the <code>fun</code> function with arguments <code>17</code>
                         and <code>25</code>
+                        <p>Argument comprehentions syntax can also be used to pass variable number of arguments based on other iterable objects,
+                            like collections and arrays, e.g. <code>fun(...[17, 25])</code>
+                            will call the <code>fun</code> function with arguments <code>17</code> and <code>25</code></p>
                     </td>
                 </tr>
                 <tr>
@@ -220,6 +255,8 @@
                         of the <code>foo</code> Object.
                         <code>arr1[0]</code> will access the first element of the
                         of the <code>arr1</code> array.</p>
+                        <p>Multidimensional array access operator form <code>arr1[x,y]</code> can be used instead of 
+                        sequential <code>arr1[x][y]</code> form</p>
                         <p>The safe-access operator <code>foo?.bar</code> shortcuts any null or non-existent references
                         along the navigation path, allowing a safe-navigation free of errors. In the previous expression,
                         if 'foo' is null, the whole expression will evaluate as null. This is an efficient shortcut
@@ -234,6 +271,20 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>Iterator Operator</td>
+                    <td>
+                        Returns an iterator of iterable object, e.g.
+                        <code>...[1,2,3]</code> will return an iterator producing items of the corresponding array.
+                        <p>Iterator can also be specified in a form of generator function e.g.
+                        <code>...(initValue : x -> {x &lt; 10 ? x + 2 : null})</code>.
+                        Generator iterator terminates only when the generator function returns null; If the <code>initValue</code>
+                        is null, then the initally empty iterator will be created.</p>
+                        <p>The generator function can also take two arguments <code>(index, value) -> {index &lt; 10 ? value + 2 : null}</code>
+                        with the first argument <code>index</code> being the zero based index of the next generated element of the sequence,
+                        and the second <code>value</code> is the previously generated element.</p>
+                    </td>
+                </tr>
+                <tr>
                     <td>Evaluation Operator</td>
                     <td>
                         Performs computational, logical or comparative action between one, two or three arguments
@@ -245,14 +296,28 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>Stream Operator</td>
+                    <td>
+                        <p>Stream operators allows to perform transformations of iterable objects into iterators or values.
+                        Stream operator can be a projection operator, selection operator or reduction operator</p>
+                        <p>Projection operator creates an iterator that produces values specified in the projection.
+                        Projection operator can be either an array projection operator <code>a.[foo,bar]</code> or a
+                        key-value projection operator <code>a.{foo:bar}</code>.
+                        The projection dimensions can be defined either as the properties of the object, <code>a.[foo]</code>
+                        or as lambda expressions <code>a.[x - > {x.foo}]</code>.</p>
+                        <p>Selection operator creates an iterator that produces only the values that satisfy the specified
+                        selection condition <code>a.(x - > {x.foo.length() > 10})</code></p>
+                        <p>Reduction operator evaluates a scalar value from iterable object <code>a.$(0 : (x,y) - > {x+y})</code></p>
+                    </td>
+                </tr>
+                <tr>
                     <td>@annotation</td>
                     <td>
                         Annotations in JEXL are 'meta-statements'; they allow to wrap the execution of the JEXL statement in a user provided
-                        caller; typical example would be: <code>@synchronized(x) x.someMethod();</code>
+                        caller; typical example would be: <code>@lenient x.someMethod();</code>
                         <p>
                             Annotations may be declared with zero or more parameters;
                             <code>@lenient x.someMethod();</code>
-                            <code>@synchronized(x) x.someMethod();</code>
                             <code>@parallel(pool, 8) x.someMethod();</code>
                         </p>
                         <p>
@@ -372,6 +437,15 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>This literal</td>
+                    <td>
+                        The literal <code>this</code> allows to explicitly access contextual variables in situations when they are covered by local variables
+                        or have non-standard names, e.g.
+                        <code>this.val1</code>
+                        <code>this["var"]</code>
+                    </td>
+                </tr>
+                <tr>
                     <td>Null literal</td>
                     <td>
                         The null value is represented as in java using the literal <code>null</code>, e.g.
@@ -386,6 +460,8 @@
                         <code>[ 1, 2, "three" ]</code>
                         <p>This syntax creates an <code>Object[]</code>.</p>
                         <p>Empty array literal can be specified as <code>[]</code> with result of creating <code>Object[]</code></p>
+                        <p>Array comprehentions syntax can also be used to construct arrays based on other iterable objects,
+                            like collections and arrays, e.g.<code>[1, 2, 3, ...[4, 5, 6]]</code></p>
                         <p>
                             JEXL will attempt to strongly type the array; if all entries are of the same class or if all
                             entries are Number instance, the array literal will be an <code>MyClass[]</code> in the former
@@ -403,6 +479,19 @@
                         <code>[ 1, 2, "three",...]</code>
                         <p>This syntax creates an <code>ArrayList&lt;Object&gt;</code>.</p>
                         <p>Empty list literal can be specified as <code>[...]</code></p>
+                        <p>List comprehentions syntax can also be used to construct lists based on other iterable objects,
+                            like collections and arrays, e.g.<code>[1, 2, 3, ...[4, 5, 6],...]</code></p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Immutable List literal</td>
+                    <td>
+                        A <code>#[</code> followed by zero or more expressions separated by <code>,</code> and ending
+                        with <code>]</code>, e.g.
+                        <code>#[ 1, 2, "three"]</code>
+                        <p>This syntax creates an immutable <code>List&lt;Object&gt;</code>.</p>
+                        <p>Empty immutable list literal can be specified as <code>#[]</code></p>
+                        <p>List comprehentions can also be used like in conventional List literal syntax</p>
                     </td>
                 </tr>
                 <tr>
@@ -413,6 +502,19 @@
                         <code>{ "one" , 2, "more"}</code>
                         <p>This syntax creates a <code>HashSet&lt;Object&gt;</code>.</p>
                         <p>Empty set literal can be specified as <code>{}</code></p>
+                        <p>Set comprehentions syntax can also be used to construct sets based on other iterable objects,
+                            like collections and arrays, e.g.<code>{1, 2, 3, ...[4, 5, 6]}</code></p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Immutable Set literal</td>
+                    <td>
+                        A <code>#{</code> followed by zero or more expressions separated by <code>,</code> and ending
+                        with <code>}</code>, e.g.
+                        <code>#{ "one" , 2, "more"}</code>
+                        <p>This syntax creates an immutable <code>Set&lt;Object&gt;</code>.</p>
+                        <p>Empty immutable set literal can be specified as <code>#{}</code></p>
+                        <p>Set comprehentions can also be used like in conventional Set literal syntax</p>
                     </td>
                 </tr>
                 <tr>
@@ -423,9 +525,21 @@
                         <code>{ "one" : 1, "two" : 2, "three" : 3, "more": "many more" }</code>
                         <p>This syntax creates a <code>HashMap&lt;Object,Object&gt;</code>.</p>
                         <p>Empty map literal can be specified as <code>{:}</code></p>
+                        <p>Map comprehentions syntax can also be used to construct maps based on other maps,
+                             e.g.<code>{1:1, 2:2, 3:3, *:...{4:4, 5:5, 6:6}}</code></p>
                     </td>
                 </tr>
-
+                <tr>
+                    <td>Immutable Map literal</td>
+                    <td>
+                        A <code>#{</code> followed by zero or more sets of <code>key : value</code> pairs separated by <code>,</code> and ending
+                        with <code>}</code>, e.g.
+                        <code>#{ "one" : 1, "two" : 2, "three" : 3, "more": "many more" }</code>
+                        <p>This syntax creates an immutable <code>Map&lt;Object,Object&gt;</code>.</p>
+                        <p>Empty immutable map literal can be specified as <code>#{:}</code></p>
+                        <p>Map comprehentions can also be used like in conventional Map literal syntax</p>
+                    </td>
+                </tr>
                 <tr>
                     <td>Range literal</td>
                     <td>
@@ -459,6 +573,7 @@
                             <li>An array of length zero</li>
                             <li>A collection of size zero</li>
                             <li>An empty map</li>
+                            <li>An iterator which 'hasNext()' method returns false</li>
                             <li>Defining a method 'public boolean isEmpty()'
                                 that returns true when the instance is considered empty</li>
                         </ol>
@@ -585,10 +700,26 @@
                     <td>Ternary conditional <code>?:</code> </td>
                     <td>
                         The usual ternary conditional operator <code>condition ? if_true : if_false</code> operator can be
-                        used as well as the abbreviation <code>value ?: if_false</code> which returns the <code>value</code> if
+                        used as well as the abbreviation <code>value ? if_true</code> which returns the <code>if_true</code> if
                         its evaluation is defined, non-null and non-false, e.g.
-                        <code>val1 ? val1 : val2</code> and
-                        <code>val1 ?: val2 </code> are equivalent.
+                        <code>val1 ? val2 : null</code> and
+                        <code>val1 ? val2 </code> are equivalent.
+                        <p>
+                            <strong>NOTE:</strong> The condition will evaluate to <code>false</code> when it
+                            refers to an undefined variable or <code>null</code> for all <code>JexlEngine</code>
+                            flag combinations. This allows explicit syntactic leniency and treats the condition
+                            'if undefined or null or false' the same way in all cases.
+                        </p>
+                        <p>Note that this operator can not be overloaded</p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Binary conditional <code>?:</code> </td>
+                    <td>
+                        The binary conditional operator <code>value ?: if_false</code> returns the <code>value</code> if
+                        its evaluation is defined, non-null and non-false, e.g.
+                        <code>val1 ?: val2 </code> and
+                        <code>val1 ? val1 : val2</code> are equivalent.
                         <p>
                             <strong>NOTE:</strong> The condition will evaluate to <code>false</code> when it
                             refers to an undefined variable or <code>null</code> for all <code>JexlEngine</code>
@@ -645,6 +776,58 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>In or Match<code>=~</code></td>
+                    <td>
+                        The syntactically Perl inspired <code>=~</code> operator can be used to check that a <code>string</code> matches
+                        a regular expression (expressed either a Java String or a java.util.regex.Pattern).
+                        For example
+                        <code>"abcdef" =~ "abc.*</code> returns <code>true</code>.
+                        It also checks whether any collection, set or map (on keys) contains a value or not; in that case, it behaves
+                        as an "in" operator.
+                        Note that arrays and user classes exposing a public 'contains' method will allow their instances
+                        to behave as right-hand side operands of this operator.
+                        <code> "a" =~ ["a","b","c","d","e",f"]</code> returns <code>true</code>.
+                    </td>
+                </tr>
+                <tr>
+                    <td>Not-In or Not-Match<code>!~</code></td>
+                    <td>
+                        The syntactically Perl inspired <code>!~</code> operator can be used to check that a <code>string</code> does not
+                        match a regular expression (expressed either a Java String or a java.util.regex.Pattern).
+                        For example
+                        <code>"abcdef" !~ "abc.*</code> returns <code>false</code>.
+                        It also checks whether any collection, set or map (on keys) does not contain a value; in that case, it behaves
+                        as "not in" operator.
+                        Note that arrays and user classes exposing a public 'contains' method will allow their instances
+                        to behave as right-hand side operands of this operator.
+                        <code> "a" !~ ["a","b","c","d","e",f"]</code> returns <code>true</code>.
+                    </td>
+                </tr>
+                <tr>
+                    <td>Identity</td>
+                    <td>
+                        The <code>===</code> operator can be used to test that left and right arguments are the same object.
+                        For example
+                        <code>val1 === val2</code>.
+                        <ol>
+                            <li>
+                                <code>null</code> is considered to be identical only to null, that is if you compare null
+                                to any non-null value, the result is false.
+                            </li>
+                            <li>Identity differs from equality that it does not use the java <code>equals</code> method</li>
+                        </ol>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Difference</td>
+                    <td>
+                        The <code>!==</code> operator can be used to test that left and right arguments are different objects.
+                        For example
+                        <code>val1 !== val2</code>.
+                        This is the reverse of the identity operator.
+                    </td>
+                </tr>
+                <tr>
                     <td>Less Than</td>
                     <td>
                         The usual <code>&lt;</code> operator can be used as well as the abbreviation <code>lt</code>.
@@ -680,34 +863,6 @@
                         <code>val1 ge val2</code> are equivalent.
                     </td>
                 </tr>
-                <tr>
-                    <td>In or Match<code>=~</code></td>
-                    <td>
-                        The syntactically Perl inspired <code>=~</code> operator can be used to check that a <code>string</code> matches
-                        a regular expression (expressed either a Java String or a java.util.regex.Pattern).
-                        For example
-                        <code>"abcdef" =~ "abc.*</code> returns <code>true</code>.
-                        It also checks whether any collection, set or map (on keys) contains a value or not; in that case, it behaves
-                        as an "in" operator.
-                        Note that arrays and user classes exposing a public 'contains' method will allow their instances
-                        to behave as right-hand side operands of this operator.
-                        <code> "a" =~ ["a","b","c","d","e",f"]</code> returns <code>true</code>.
-                    </td>
-                </tr>
-                <tr>
-                    <td>Not-In or Not-Match<code>!~</code></td>
-                    <td>
-                        The syntactically Perl inspired <code>!~</code> operator can be used to check that a <code>string</code> does not
-                        match a regular expression (expressed either a Java String or a java.util.regex.Pattern).
-                        For example
-                        <code>"abcdef" !~ "abc.*</code> returns <code>false</code>.
-                        It also checks whether any collection, set or map (on keys) does not contain a value; in that case, it behaves
-                        as "not in" operator.
-                        Note that arrays and user classes exposing a public 'contains' method will allow their instances
-                        to behave as right-hand side operands of this operator.
-                        <code> "a" !~ ["a","b","c","d","e",f"]</code> returns <code>true</code>.
-                    </td>
-                </tr>
                  <tr>
                     <td>Starts With<code>=^</code></td>
                     <td>
@@ -740,6 +895,66 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>Type comparison <code>instanceof</code></td>
+                    <td>
+                        The 'instanceof' operator is used to test whether the object is an instance of the specified type (class or subclass or interface).
+                        For example <code>"abc" instanceof String</code> returns <code>true</code>.
+                        The type name can be either a java primitive type name, a simple name, or a fully qualified name, e.g.
+                        <code>"abc" instanceof String</code> and <code>"abc" instanceof java.lang.String</code> are equivalent.
+                        A simple class name can be used in the following cases: 
+                        <ol>
+                            <li>for the classes in package java.lang.*</li>
+                            <li>for the classes in package java.util.*</li>
+                            <li>for the classes in package java.io.*</li>
+                            <li>for the classes in package java.net.*</li>
+                            <li>for classes without packages</li>
+                            <li>for java.math.BigDecimal and java.math.BigInteger classes</li>
+                        </ol>
+                        The array syntax is also supported, e.g.
+                        <code>["abc", "def"] instanceof String[]</code>, or <code>[1,2,3] instanceof int[]</code>.
+                    </td>
+                </tr>
+                <tr>
+                    <td>Object creation<code>new</code></td>
+                    <td>
+                        Creates a new instance using a class name:
+                        <code>new Double(10)</code> returns 10.0.
+                        The type name can be either a simple name, or a fully qualified name, e.g.
+                        <code>new String("abc")</code> and <code> new java.lang.String("abc")</code> are equivalent.
+                        A simple class name can be used in the same cases as specified for the <code>instanceof</code> operator.
+                        <p>In case of multiple constructors, JEXL will make the best effort to find
+                            the most appropriate non ambiguous constructor to call.</p>
+                        <p>The array syntax is also supported, e.g.
+                           <code>new String[5]</code>, or <code>new int[5][7]</code></p>
+                        <p>The array initialization syntax is also supported for single-dimension arrays, e.g.
+                           <code>new String[] {'abc','def'}</code></p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Left Shift</td>
+                    <td>
+                        The usual binary signed left shift <code>&lt;&lt;</code> operator is used.
+                        For example
+                        <code>val1 &lt;&lt; 2</code>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Right Shift</td>
+                    <td>
+                        The usual binary signed right shift <code>&gt;&gt;</code> operator is used.
+                        For example
+                        <code>val1 &gt;&gt; 2</code>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Unsigned Right Shift</td>
+                    <td>
+                        The usual binary unsigned right shift <code>&gt;&gt;&gt;&gt;</code> operator is used.
+                        For example
+                        <code>val1 &gt;&gt;&gt; 2</code>
+                    </td>
+                </tr>
+                <tr>
                     <td>Addition</td>
                     <td>
                         The usual <code>+</code> operator is used.
@@ -798,10 +1013,33 @@
                             <li>&amp;=</li>
                             <li>|=</li>
                             <li>^=</li>
+                            <li>&lt;&lt;=</li>
+                            <li>&gt;&gt;=</li>
+                            <li>&gt;&gt;&gt;=</li>
                         </ul>
                     </td>
                 </tr>
                 <tr>
+                    <td>Increment operator</td>
+                    <td>
+                        The <code>++</code> operator is used. 
+                        The default behavior is to increment value and assign the operand with the result.
+                        For instance <code>++a</code> is equivalent to <code>a = a + 1</code>
+                        operator.
+                        <p>The postfix form is also supported: <code>a++</code></p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Decrement operator</td>
+                    <td>
+                        The <code>--</code> operator is used. 
+                        The default behavior is to decrement value and assign the operand with the result.
+                        For instance <code>--a</code> is equivalent to <code>a = a - 1</code>
+                        operator.
+                        <p>The postfix form is also supported: <code>a--</code></p>
+                    </td>
+                </tr>
+                <tr>
                     <td>Negation</td>
                     <td>
                         The unary <code>-</code> operator is used.
@@ -810,6 +1048,36 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>Confirmation</td>
+                    <td>
+                        The unary <code>+</code> operator is used.
+                        For example
+                        <code>+12</code>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Pointer</td>
+                    <td>
+                        The unary <code>&amp;</code> operator is used to get a pointer of a variable or a property. e.g.
+                        <code>&amp;a</code>
+                        Pointer values can be dereferenced via dereference operator, e.g.
+                        <code>var x = 1; var y = &amp;x; *y = 2</code>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Dereference</td>
+                    <td>
+                        The unary <code>*</code> operator is used. e.g.
+                        <code>*a</code>
+                        Note that through duck-typing, user classes exposing a <code>get()</code> method will allow their instances
+                        to behave as an operand of this operator.
+                        <p>The assignment to a dereferenced object is also supported: <code>*a = 10</code>, 
+                        any user classes exposing a <code>set(Object)</code> method will allow their instances
+                        to accept such assignments.
+                        </p>
+                    </td>
+                </tr>
+                <tr>
                     <td>Empty</td>
                     <td>
                         The unary <code>empty</code> operator behaves exactly as the corresponding function <code>empty()</code>.
@@ -894,7 +1162,54 @@
                 </tr>
             </table>
         </section>
-        <section name="Conditional">
+        <section name="Streams">
+            <table>
+                <tr>
+                    <th width="15%">Operator</th>
+                    <th>Description</th>
+                </tr>
+                <tr>
+                    <td>Array projection</td>
+                    <td>
+                        An array projection operator <code>a.[foo,bar]</code> can define one or more projection dimensions.
+                        In case of only one defined dimension <code>a.[foo]</code>, the projection operator creates an iterator
+                        that produces a single value. In case of more than one defined dimension <code>a.[foo,bar,quux]</code>,
+                        the projection operator creates an iterator that produces an array of the corresponding values.
+                    </td>
+                    <td>Key-value projection</td>
+                    <td>
+                        A key-value projection operator <code>a.{foo:bar}</code> creates an iterator
+                        that produces <code>Map.Entry</code> object with a key and a value.
+                    </td>
+                    <td>Selection</td>
+                    <td>
+                        A selection operator <code>a.(x -> {x.length() > 10})</code> creates an iterator
+                        that produces only the objects that satisfy the specified condition.
+                        In its basic form the function takes one argument which is the next element of the iterable object.
+                        In extended form the function can take two arguments <code>(index,value) -> {index &lt; 10}</code>
+                        with the first argument <code>index</code> being the zero based index of the next element of the iterable
+                        object, and the second <code>value</code> is the next element itself.
+                    </td>
+                    <td>Reduction</td>
+                    <td>
+                        A reduction operator <code>a.@(defaultValue : (result,value) -> {result + value})</code> evaluates
+                        the specified function to produce a value from series of values returned by an iterable object.
+                        If the iterable object contains no data, the <code>defaultValue</code> will be returned, otherwise
+                        the function <code>(result,value) -> {result + value}</code> will be called for each value returned
+                        by the iterable object. In its basic form the function takes two arguments, with the first argument
+                        <code>result</code> being a partial reduction operator result, and the second argument <code>value</code>
+                        being the next element of the iterable object. The function can also take only one argument
+                        <code>result -> {result + 1}</code>, or take no arguments at all<code>() -> {nextid()}</code>.
+                        In extended form the function can take three arguments <code>(result,index,value) -> {result + value}</code>
+                        with additional argument <code>index</code> being the zero based index of the next element of the iterable
+                        object.
+                        <p>Note that the default value of the reduction operator can be omitted
+                           <code>a.@((result,value) -> {result + value})</code>, in that case the default value will be null</p>
+                    </td>
+                </tr>
+            </table>
+        </section>
+        <section name="Control">
             <table>
                 <tr>
                     <th width="15%">Statement</th>
@@ -912,8 +1227,48 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>switch</td>
+                    <td>
+                        The switch statement evaluates its expression, then executes all statements that follow the matching case label, e.g.
+                        <code>switch (a) {
+                              case 0: return "zero";
+                              case 1: return "one"
+                              default: return "-"
+                            } </code>
+                        <p>The labeled syntax is also supported:
+                        <code>foo : switch (a) {
+                              case 0: return "zero";
+                              case 1: return "one"
+                              default: return "-"
+                            } </code>
+                        Where the label <code>foo</code> can be used in the inner <code>break</code> statements.</p>
+                    </td>
+                </tr>
+                <tr>
                     <td>for</td>
                     <td>
+                        <p>Classic, for(initialization; termination; increment) statement, e.g.
+                        <code>for (i = 0; i &lt; 10; i = i + 1) {
+                            x = x + item[i];
+                            }</code>
+                        Where <code>i</code> is a context variable.</p>
+                        <p>The following syntax is also supported:
+                        <code>for (var i = 0; i &lt; 10; i = i + 1) {
+                            x = x + item[i];
+                            }</code>
+
+                        Where <code>i</code> is a local variable.</p>
+                        <p>The labeled syntax is also supported:
+                        <code>foo : for (var i = 0; i &lt; 10; i = i + 1) {
+                            x = x + item;
+                            } </code>
+                        Where the label <code>foo</code> can be used in the inner <code>break</code> and <code>continue</code> statements.</p>
+                        <p>Note that the loop variable <code>i</code> is accessible after loop evaluation</p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>foreach</td>
+                    <td>
                         <p>Loop through items of an Array, Collection, Map, Iterator or Enumeration, e.g.
                         <code>for (item : list) {
                             x = x + item;
@@ -925,7 +1280,18 @@
                             }</code>
 
                         Where <code>item</code> is a local variable.</p>
+                        <p>The labeled syntax is also supported:
+                        <code>foo : for (var item : list) {
+                            x = x + item;
+                            } </code>
+                        Where the label <code>foo</code> can be used in the inner <code>break</code>,<code>continue</code> and <code>remove</code> statements.</p>
                         <p>Note that the loop variable <code>item</code> is accessible after loop evaluation</p>
+                        <p>The syntax with a key and a value variable is also supported:
+                        <code>for (var i, item : list) {
+                            if (i lt 3) x = x + item;
+                            }</code>
+                        Where <code>i</code> is the index and <code>item</code> is a value variable.
+                        For the instances of Map iterator the index variable is the Map key, and the value variable is the Map value</p>
                         <p>The JEXL 1.1 syntax using <code>foreach(item in list)</code> is now <strong>unsupported</strong>.</p>
                     </td>
                 </tr>
@@ -936,6 +1302,11 @@
                         <code>while (x lt 10) {
                             x = x + 2;
                             }</code>
+                        <p>The labeled syntax is also supported:
+                        <code>foo : while (x lt 10) {
+                            x = x + 2;
+                            } </code>
+                        Where the label <code>foo</code> can be used in the inner <code>break</code> and <code>continue</code> statements.</p>
                     </td>
                 </tr>
                 <tr>
@@ -945,18 +1316,129 @@
                         <code>do {
                             x = x + 2;
                             } while (x lt 10)</code>
+                        <p>The labeled syntax is also supported:
+                        <code>foo : do {
+                            x = x + 2;
+                            } while (x lt 10)</code>
+                        Where the label <code>foo</code> can be used in the inner <code>break</code> and <code>continue</code> statements.</p>
                     </td>
                 </tr>
                 <tr>
                     <td>continue</td>
                     <td>
                         Within loops (do/while/for), allows to skip to the next iteration.
+                        <p>The labeled syntax is also supported:
+                        <code>continue foo</code>
+                        Where the label <code>foo</code> specifies the loop to which the operation should apply to.</p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>remove</td>
+                    <td>
+                        Within 'for' loops, allows to remove current element from underlying iterator collection and skip to the next iteration.
+                        <p>The labeled syntax is also supported:
+                        <code>remove foo</code>
+                        Where the label <code>foo</code> specifies the loop to which the operation should apply to.</p>
                     </td>
                 </tr>
                 <tr>
                     <td>break</td>
                     <td>
-                        Allows to break from a loop (do/while/for) unconditionally.
+                        Allows to break from a statement (do/while/for/switch/block) unconditionally.
+                        <p>The labeled syntax is also supported:
+                        <code>break foo</code>
+                        Where the label <code>foo</code> specifies the statement to which the operation should apply to.</p>
+                        <p>Note that the unlabelled <code>break</code> statement can not be applied to an unlabelled block</p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>return</td>
+                    <td>
+                        Breaks current script/function execution and returns the value of the specified expression, e.g.
+                        <code>return 42</code>
+                        If the returned value is not specified, the <code>null</code> will be returned, e.g.
+                        <code>
+                        return;
+                        </code>
+                    </td>
+                </tr>
+                <tr>
+                    <td>try/catch/finally</td>
+                    <td>
+                        Executes a try block and catches the exception, e.g.
+                        <code>try {
+                              return 42/0;
+                            } catch (e) {
+                              ...
+                            } </code>
+                        <p>The following syntax is also supported:
+                        <code>try {
+                              return 42/0;
+                            } catch (var e) {
+                              ...
+                            } </code>
+                        Where <code>e</code> is a local variable.</p>
+                        <p>Note that the catch variable <code>e</code> is accessible inside <code>finally</code> block and
+                          after the whole statement evaluation</p>
+                        <p>Either the <code>catch</code> block, the <code>finally</code> block or both can be specified for the
+                        statement</p>
+                        <p>The <code>finally</code> block executes always when the try block exits
+                        <code>try {
+                              return 41;
+                            } finally {
+                              return 42
+                            } </code>
+                        </p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>try-with-resources</td>
+                    <td>
+                        A try statement that declares a resource - the object that must be closed after the statement ends, e.g.
+                        <code>try (e = lock()) {
+                              return 42/0;
+                            } </code>
+                        <p>The following syntax is also supported:
+                        <code>try (var e = lock()) {
+                              return 42/0;
+                            } </code>
+                        Where <code>e</code> is a local variable.</p>
+                        <p>The <code>catch</code> block, the <code>finally</code> block or both can be specified for the
+                        statement as described for the <code>try</code> statement</p>
+                        <p>Note that the resource variable <code>e</code> is accessible inside the <code>catch</code> or <code>finally</code> block and
+                          after the whole statement evaluation</p>
+                        <p>The syntax without variable declaration is also supported:
+                        <code>try (lock()) {
+                              return 42/0;
+                            } </code>
+                        </p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>throw</td>
+                    <td>
+                        Throws an exception with the specified text message, e.g.
+                        <code>throw "Something wrong here"</code>
+                        <p>If the specified argument is the instance of the <code>Throwable</code> class, 
+                           the provided exception will be rethrown</p>
+                    </td>
+                </tr>
+                <tr>
+                    <td>assert</td>
+                    <td>
+                        An assert is used to declare an expected boolean condition in a script, e.g.
+                        <code>assert x == 0</code> or <code>assert x == 0 : "Something wrong here"</code>
+                        If the script is evaluated with assertions enabled, then the condition is checked during evaluation. 
+                        If the condition is false, the assertion error is thrown.
+                    </td>
+                </tr>
+                <tr>
+                    <td>synchronized</td>
+                    <td>
+                        Executes synchronized code, e.g.
+                        <code>synchronized (x) {
+                            y = x + 2;
+                            }</code>
                     </td>
                 </tr>
             </table>
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticStatementTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticStatementTest.java
new file mode 100644
index 0000000..e9e9ded
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticStatementTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.apache.commons.jexl3.junit.Asserter;
+import java.io.StringWriter;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for arithmetic operators which are called in statetement-like fashion.
+ * @since 3.0
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ArithmeticStatementTest extends JexlTestCase {
+    private Asserter asserter;
+
+    @Before
+    @Override
+    public void setUp() {
+        asserter = new Asserter(JEXL);
+        asserter.setStrict(false);
+    }
+
+    /**
+     * Create the named test.
+     * @param name test name
+     */
+    public ArithmeticStatementTest() {
+        super("ArithmeticStatementTest");
+    }
+
+    public static class StatementArithmetic extends JexlArithmetic {
+        StatementArithmetic(boolean flag) {
+            super(flag);
+        }
+
+        public void raise() throws Exception {
+            throw new Exception("Do not execute");
+        }
+
+    }
+
+    @Test
+    public void testStatementArithmetic() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new StatementArithmetic(true)).create();
+        JexlScript expr0 = jexl.createScript("raise; return 2");
+
+        try {
+           Object value0 = expr0.execute(jc);
+           Assert.fail("should have cancelled");
+        } catch (Exception ex) {
+           // Ok
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index f521088..76c59ab 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -128,6 +128,16 @@
         asserter.assertExpression("right % left", new BigInteger("0"));
     }
 
+    @Test
+    public void testOverflows() throws Exception {
+        asserter.assertExpression("1 + 2147483647", Long.valueOf("2147483648"));
+        asserter.assertExpression("-2147483648 - 1", Long.valueOf("-2147483649"));
+        asserter.assertExpression("1 + 9223372036854775807", new BigInteger("9223372036854775808"));
+        asserter.assertExpression("-1 + (-9223372036854775808)", new BigInteger("-9223372036854775809"));
+        asserter.assertExpression("-9223372036854775808 - 1", new BigInteger("-9223372036854775809"));
+        asserter.assertExpression("-1 - 9223372036854775808", new BigInteger("-9223372036854775809"));
+    }
+
     /**
      * test some simple mathematical calculations
      */
@@ -158,6 +168,34 @@
      * test some simple mathematical calculations
      */
     @Test
+    public void testUnaryPlus() throws Exception {
+        asserter.setVariable("aByte", new Byte((byte) 1));
+        asserter.setVariable("aShort", new Short((short) 2));
+        asserter.setVariable("aChar", new Character('A'));
+        asserter.setVariable("anInteger", new Integer(3));
+        asserter.setVariable("aLong", new Long(4));
+        asserter.setVariable("aFloat", new Float(5.5));
+        asserter.setVariable("aDouble", new Double(6.6));
+        asserter.setVariable("aBigInteger", new BigInteger("7"));
+        asserter.setVariable("aBigDecimal", new BigDecimal("8.8"));
+
+        asserter.assertExpression("+3", new Integer("3"));
+        asserter.assertExpression("+3.0", new Double("3.0"));
+        asserter.assertExpression("+aByte", new Integer(1));
+        asserter.assertExpression("+aShort", new Integer(2));
+        asserter.assertExpression("+aChar", new Integer(65));
+        asserter.assertExpression("+anInteger", new Integer(3));
+        asserter.assertExpression("+aLong", new Long(4));
+        asserter.assertExpression("+aFloat", new Float(5.5));
+        asserter.assertExpression("+aDouble", new Double(6.6));
+        asserter.assertExpression("+aBigInteger", new BigInteger("7"));
+        asserter.assertExpression("+aBigDecimal", new BigDecimal("8.8"));
+    }
+
+    /**
+     * test some simple mathematical calculations
+     */
+    @Test
     public void testCalculations() throws Exception {
         asserter.setStrict(true, false);
         /*
diff --git a/src/test/java/org/apache/commons/jexl3/ArrayLiteralTest.java b/src/test/java/org/apache/commons/jexl3/ArrayLiteralTest.java
index f7ea3dd..b5a37ae 100644
--- a/src/test/java/org/apache/commons/jexl3/ArrayLiteralTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArrayLiteralTest.java
@@ -55,7 +55,7 @@
     }
 
     @Test
-    public void testLiteralWithElipsis() throws Exception {
+    public void testLiteralWithEllipsis() throws Exception {
         JexlExpression e = JEXL.createExpression("[ 'foo' , 'bar', ... ]");
         JexlContext jc = new MapContext();
 
diff --git a/src/test/java/org/apache/commons/jexl3/ArrayMultiDimensionalAccessTest.java b/src/test/java/org/apache/commons/jexl3/ArrayMultiDimensionalAccessTest.java
new file mode 100644
index 0000000..f8c5ea5
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ArrayMultiDimensionalAccessTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.jexl3.junit.Asserter;
+import org.junit.Before;
+import org.junit.Test;
+
+
+/**
+ * Tests for multidimesional array access operator [,]
+ *
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ArrayMultiDimensionalAccessTest extends JexlTestCase {
+
+    private Asserter asserter;
+
+    // Needs to be accessible by Foo.class
+    static final String[][] GET_METHOD_ARRAY2 =
+        new String[][] { {"One", "Two", "Three"},{"Four", "Five", "Six"} };
+
+    public ArrayMultiDimensionalAccessTest() {
+        super("ArrayMultiDimensionalAccessTest");
+    }
+
+    @Override
+    @Before
+    public void setUp() {
+        asserter = new Asserter(JEXL);
+    }
+
+    /**
+     * test some simple double array lookups
+     */
+    @Test
+    public void testDoubleArrays() throws Exception {
+        Object[][] foo = new Object[2][2];
+
+        foo[0][0] = "one";
+        foo[0][1] = "two";
+        asserter.setVariable("foo", foo);
+        asserter.assertExpression("foo[0][1] == foo[0,1]", Boolean.TRUE);
+        asserter.assertExpression("foo[0,1] = 'three'", "three");
+        asserter.assertExpression("foo[0,1]", "three");
+    }
+
+    @Test
+    public void testDoubleMaps() throws Exception {
+        Map<Object, Map<Object, Object>> foo = new HashMap<Object, Map<Object, Object>>();
+        Map<Object, Object> foo0 = new HashMap<Object, Object>();
+        foo.put(0, foo0);
+        foo0.put(0, "one");
+        foo0.put(1, "two");
+        foo0.put("3.0", "three");
+        asserter.setVariable("foo", foo);
+        asserter.assertExpression("foo[0,1]", "two");
+        asserter.assertExpression("foo[0,1] = 'three'", "three");
+        asserter.assertExpression("foo[0,1]", "three");
+        asserter.assertExpression("foo[0,'3.0']", "three");
+    }
+
+    @Test
+    public void testArrayArray() throws Exception {
+        Integer i42 = Integer.valueOf(42);
+        Integer i43 = Integer.valueOf(43);
+        String s42 = "fourty-two";
+        String s43 = "fourty-three";
+        Object[] foo = new Object[3];
+        foo[0] = foo;
+        foo[1] = i42;
+        foo[2] = s42;
+        asserter.setVariable("foo", foo);
+        asserter.setVariable("zero", Integer.valueOf(0));
+        asserter.setVariable("one", Integer.valueOf(1));
+        asserter.setVariable("two", Integer.valueOf(2));
+        for(int l = 0; l < 2; ++l) {
+            asserter.assertExpression("foo[0]", foo);
+            asserter.assertExpression("foo[0,0]", foo);
+            asserter.assertExpression("foo[1]", foo[1]);
+            asserter.assertExpression("foo[0,1]", foo[1]);
+            asserter.assertExpression("foo[0,1] = 43", i43);
+            asserter.assertExpression("foo[0,1]", i43);
+            asserter.assertExpression("foo[0,1] = 42", i42);
+            asserter.assertExpression("foo[0,1]", i42);
+            asserter.assertExpression("foo[0,0,1]", foo[1]);
+            asserter.assertExpression("foo[0,0,1] = 43", i43);
+            asserter.assertExpression("foo[0,0,1]", i43);
+            asserter.assertExpression("foo[0,0,1] = 42", i42);
+            asserter.assertExpression("foo[0,0,1]", i42);
+            asserter.assertExpression("foo[2]", foo[2]);
+            asserter.assertExpression("foo[0,2]", foo[2]);
+            asserter.assertExpression("foo[0,2] = 'fourty-three'", s43);
+            asserter.assertExpression("foo[0,2]", s43);
+            asserter.assertExpression("foo[0,2] = 'fourty-two'", s42);
+            asserter.assertExpression("foo[0,2]", s42);
+            asserter.assertExpression("foo[0,0,2]", foo[2]);
+            asserter.assertExpression("foo[0,0,2] = 'fourty-three'", s43);
+            asserter.assertExpression("foo[0,0,2]", s43);
+            asserter.assertExpression("foo[0,0,2] = 'fourty-two'", s42);
+            asserter.assertExpression("foo[0,0,2]", s42);
+
+            asserter.assertExpression("foo[zero]", foo);
+            asserter.assertExpression("foo[zero,zero]", foo);
+            asserter.assertExpression("foo[one]", foo[1]);
+            asserter.assertExpression("foo[zero,one]", foo[1]);
+            asserter.assertExpression("foo[zero,one] = 43", i43);
+            asserter.assertExpression("foo[zero,one]", i43);
+            asserter.assertExpression("foo[zero,one] = 42", i42);
+            asserter.assertExpression("foo[zero,one]", i42);
+            asserter.assertExpression("foo[zero,zero,one]", foo[1]);
+            asserter.assertExpression("foo[zero,zero,one] = 43", i43);
+            asserter.assertExpression("foo[zero,zero,one]", i43);
+            asserter.assertExpression("foo[zero,zero,one] = 42", i42);
+            asserter.assertExpression("foo[zero,zero,one]", i42);
+            asserter.assertExpression("foo[two]", foo[2]);
+            asserter.assertExpression("foo[zero,two]", foo[2]);
+            asserter.assertExpression("foo[zero,two] = 'fourty-three'", s43);
+            asserter.assertExpression("foo[zero,two]", s43);
+            asserter.assertExpression("foo[zero,two] = 'fourty-two'", s42);
+            asserter.assertExpression("foo[zero,two]", s42);
+            asserter.assertExpression("foo[zero,zero,two]", foo[2]);
+            asserter.assertExpression("foo[zero,zero,two] = 'fourty-three'", s43);
+            asserter.assertExpression("foo[zero,zero,two]", s43);
+            asserter.assertExpression("foo[zero,zero,two] = 'fourty-two'", s42);
+            asserter.assertExpression("foo[zero,zero,two]", s42);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/jexl3/AssertionsTest.java b/src/test/java/org/apache/commons/jexl3/AssertionsTest.java
new file mode 100644
index 0000000..280280e
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/AssertionsTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.util.Arrays;
+import java.util.List;
+
+import java.math.MathContext;
+import java.nio.charset.Charset;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for assert statement.
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class AssertionsTest extends JexlTestCase {
+
+    public AssertionsTest() {
+        super("AssertionsTest");
+    }
+
+    @Test
+    public void testInitiallyDisabledAssertions() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript s = JEXL.createScript("assert false");
+        Object o = s.execute(jc);
+        Assert.assertNull(o);
+    }
+
+    @Test
+    public void testEnabledAssertions() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlEngine jexl = new JexlBuilder().assertions(true).create();
+
+        JexlScript s = jexl.createScript("assert true");
+        Object o = s.execute(jc);
+        Assert.assertNull(o);
+
+        s = jexl.createScript("assert false");
+        try {
+             o = s.execute(jc);
+             Assert.fail("Should have failed");
+        } catch (AssertionError ex) {
+             // OK
+        }
+    }
+
+    @Test
+    public void testAssertionMessages() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlEngine jexl = new JexlBuilder().assertions(true).create();
+        JexlScript s = jexl.createScript("assert false : 'check'");
+        try {
+             Object o = s.execute(jc);
+             Assert.fail("Should have failed");
+        } catch (AssertionError ex) {
+             Assert.assertEquals("check", ex.getMessage());
+        }
+    }
+
+    public class AssertionsContext implements JexlContext, JexlEngine.Options {
+
+        /** The wrapped context. */
+        private final JexlContext wrapped;
+
+        private final boolean assertions;
+
+        /**
+         * Creates a new readonly context.
+         * @param context the wrapped context
+         * @param eopts the engine evaluation options
+         */
+        public AssertionsContext(JexlContext context, boolean assertions) {
+            wrapped = context;
+            this.assertions = assertions;
+        }
+
+        @Override
+        public Object get(String name) {
+            return wrapped.get(name);
+        }
+
+        @Override
+        public void set(String name, Object value) {
+            wrapped.set(name, value);
+        }
+
+        @Override
+        public boolean has(String name) {
+            return wrapped.has(name);
+        }
+
+        @Override
+        public Boolean isSilent() {
+            return false;
+        }
+
+        @Override
+        public Boolean isStrict() {
+            return true;
+        }
+
+        @Override
+        public Boolean isAssertions() {
+            return assertions;
+        }
+
+        @Override
+        public Boolean isCancellable() {
+            return true;
+        }
+
+        @Override
+        public Boolean isStrictArithmetic() {
+            return true;
+        }
+
+        @Override
+        public MathContext getArithmeticMathContext() {
+            return null;
+        }
+
+        @Override
+        public int getArithmeticMathScale() {
+            return -1;
+        }
+
+        @Override
+        public Charset getCharset() {
+            return Charset.defaultCharset();
+        }
+    }
+
+    @Test
+    public void testContextEnabledAssertions() throws Exception {
+        JexlContext jc = new AssertionsContext(new MapContext(), true);
+        JexlEngine jexl = new JexlBuilder().assertions(false).create();
+        JexlScript s = jexl.createScript("assert false");
+        try {
+             Object o = s.execute(jc);
+             Assert.fail("Should have failed");
+        } catch (AssertionError ex) {
+             // OK
+        }
+    }
+
+    @Test
+    public void testContextDisabledAssertions() throws Exception {
+        JexlContext jc = new AssertionsContext(new MapContext(), false);
+        JexlEngine jexl = new JexlBuilder().assertions(true).create();
+        JexlScript s = jexl.createScript("assert false");
+        Object o = s.execute(jc);
+        Assert.assertNull(o);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/ComprehensionTest.java b/src/test/java/org/apache/commons/jexl3/ComprehensionTest.java
new file mode 100644
index 0000000..dcc5048
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ComprehensionTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.Map;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for comprehensions operator.
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ComprehensionTest extends JexlTestCase {
+
+    public ComprehensionTest() {
+        super("ComprehensionTest");
+    }
+
+    @Test
+    public void testArrayComprehensionLiteral() throws Exception {
+        JexlContext jc = new MapContext();
+        Object o = JEXL.createScript("var x = [1, 2, 3]; var y = [...x]").execute(jc);
+        Assert.assertTrue(o instanceof int[]);
+        Assert.assertEquals(3, ((int[]) o).length);
+
+        o = JEXL.createScript("var x = null; var y = [1, 2, 3, ...x]").execute(jc);
+        Assert.assertEquals(3, ((int[]) o).length);
+    }
+
+    @Test
+    public void testSetComprehensionLiteral() throws Exception {
+        JexlContext jc = new MapContext();
+        Object o = JEXL.createScript("var x = [1, 2, 3]; var y = {...x}").execute(jc);
+        Assert.assertTrue(o instanceof Set);
+        Assert.assertEquals(3, ((Set) o).size());
+
+        o = JEXL.createScript("var x = null; var y = {1, 2, 3, ...x}").execute(jc);
+        Assert.assertEquals(3, ((Set) o).size());
+
+        o = JEXL.createScript("var x = [3, 4, 5]; var y = {1, 2, 3, ...x}").execute(jc);
+        Assert.assertEquals(5, ((Set) o).size());
+    }
+
+    @Test
+    public void testMapComprehensionLiteral() throws Exception {
+        JexlContext jc = new MapContext();
+        Object o = JEXL.createScript("var x = {1:1, 2:2, 3:3}; var y = {*:...x}").execute(jc);
+        Assert.assertTrue(o instanceof Map);
+        Assert.assertEquals(3, ((Map) o).size());
+
+        o = JEXL.createScript("var x = null; var y = {1:1, 2:2, 3:3, *:...x}").execute(jc);
+        Assert.assertEquals(3, ((Map) o).size());
+
+        o = JEXL.createScript("var x = [3, 4, 5]; var y = {1:1, 2:2, 3:3, *:...x}").execute(jc);
+        Assert.assertEquals(5, ((Map) o).size());
+    }
+
+    @Test
+    public void testArgumentComprehension() throws Exception {
+        JexlContext jc = new MapContext();
+        Object o = JEXL.createScript("var x = (a, b, c) -> {return a + b + c}; var y = [1, 2, 3]; x(...y)").execute(jc);
+        Assert.assertEquals(6, o);
+    }
+
+    @Test
+    public void testIteratorEmpty() throws Exception {
+        JexlContext jc = new MapContext();
+        Object o = JEXL.createScript("var x = [1, 2, 3]; not empty ...x").execute(jc);
+        Assert.assertEquals(Boolean.TRUE, o);
+
+        o = JEXL.createScript("var x = []; empty ...x").execute(jc);
+        Assert.assertEquals(Boolean.TRUE, o);
+    }
+
+    @Test
+    public void testGeneratorIterator() throws Exception {
+        JexlContext jc = new MapContext();
+        Object o = JEXL.createScript("var x = ...(0 : x -> {x < 10 ? x + 2 : null}); return [...x]").execute(jc);
+        Assert.assertEquals(6, ((int[]) o).length);
+
+        o = JEXL.createScript("var x = ...(0 : (i,x) -> {i < 10 ? x + 2 : null}); return [...x]").execute(jc);
+        Assert.assertEquals(10, ((int[]) o).length);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/ConstructorTest.java b/src/test/java/org/apache/commons/jexl3/ConstructorTest.java
new file mode 100644
index 0000000..02c6100
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ConstructorTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test cases for the qualified new syntax.
+ *
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ConstructorTest extends JexlTestCase {
+
+    public ConstructorTest() {
+        super("ConstructorTest");
+    }
+
+    @Test
+    public void testSimpleName() throws Exception {
+        JexlScript e = JEXL.createScript("new String()");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o instanceof String);
+    }
+
+    @Test
+    public void testQualifiedName() throws Exception {
+        JexlScript e = JEXL.createScript("new java.util.concurrent.atomic.AtomicLong()");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o instanceof AtomicLong);
+    }
+
+    @Test
+    public void testSimpleNameParams() throws Exception {
+        JexlScript e = JEXL.createScript("new String('abc'); ");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", "abc", o);
+    }
+
+    @Test
+    public void testQualifiedNameParams() throws Exception {
+        JexlScript e = JEXL.createScript("var x = new java.util.concurrent.atomic.AtomicLong(42); x.get()");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 42L, o);
+    }
+
+    @Test
+    public void testArray() throws Exception {
+        JexlScript e = JEXL.createScript("new String[5]");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o instanceof String[]);
+        Assert.assertEquals("Result is not as expected", 5, ((String[])o).length);
+    }
+
+    @Test
+    public void testPrimitiveArray() throws Exception {
+        JexlScript e = JEXL.createScript("new int[5]");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o instanceof int[]);
+        Assert.assertEquals("Result is not as expected", 5, ((int[])o).length);
+    }
+
+    @Test
+    public void testQualifiedArray() throws Exception {
+        JexlScript e = JEXL.createScript("new java.util.concurrent.atomic.AtomicLong[5]");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o instanceof AtomicLong[]);
+        Assert.assertEquals("Result is not as expected", 5, ((AtomicLong[])o).length);
+    }
+
+    @Test
+    public void testMultidimensionalArray() throws Exception {
+        JexlScript e = JEXL.createScript("new String[6][5]");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o instanceof Object[]);
+        Assert.assertEquals("Result is not as expected", 6, ((Object[])o).length);
+    }
+
+    @Test
+    public void testMultidimensionalPrimitiveArray() throws Exception {
+        JexlScript e = JEXL.createScript("new int[6][5]");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o instanceof Object[]);
+        Assert.assertEquals("Result is not as expected", 6, ((Object[])o).length);
+    }
+
+    @Test
+    public void testInitializedArray() throws Exception {
+        JexlScript e = JEXL.createScript("new String[] {'abc','def'}");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o instanceof String[]);
+        Assert.assertEquals("Result is not as expected", 2, ((String[])o).length);
+    }
+    
+}
diff --git a/src/test/java/org/apache/commons/jexl3/DoWhileTest.java b/src/test/java/org/apache/commons/jexl3/DoWhileTest.java
index b712821..02a0dce 100644
--- a/src/test/java/org/apache/commons/jexl3/DoWhileTest.java
+++ b/src/test/java/org/apache/commons/jexl3/DoWhileTest.java
@@ -40,7 +40,7 @@
         e = JEXL.createScript("do {} while (false); 23");
         o = e.execute(jc);
         Assert.assertEquals(23, o);
-        
+
     }
 
     @Test
@@ -52,12 +52,12 @@
         Object o = e.execute(jc);
         Assert.assertEquals(10, o);
         Assert.assertEquals(10, jc.get("x"));
-        
+
         e = JEXL.createScript("var x = 0; do x += 1; while (x < 23)");
         o = e.execute(jc);
         Assert.assertEquals(23, o);
-        
-        
+
+
         jc.set("x", 1);
         e = JEXL.createScript("do x += 1; while (x < 23); return 42;");
         o = e.execute(jc);
diff --git a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
index b565eff..b245745 100644
--- a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
@@ -233,12 +233,12 @@
 
     @Test
     public void test206() throws Exception {
-        String src = "null.1 = 2; return 42";
+        String src = "(null).1 = 2; return 42";
         doTest206(src, false, false);
         doTest206(src, false, true);
         doTest206(src, true, false);
         doTest206(src, true, true);
-        src = "x = null.1; return 42";
+        src = "x = (null).1; return 42";
         doTest206(src, false, false);
         doTest206(src, false, true);
         doTest206(src, true, false);
diff --git a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
index 3a64362..1d5789a 100644
--- a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
@@ -21,8 +21,8 @@
 import org.junit.Test;
 
 /**
- * Tests for blocks
- * @since 1.1
+ * Tests for features
+ * @since 3.0
  */
 @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
 public class FeaturesTest extends JexlTestCase {
@@ -31,7 +31,7 @@
      * Create the test
      */
     public FeaturesTest() {
-        super("BlockTest");
+        super("FeaturesTest");
     }
 
     /**
@@ -261,7 +261,7 @@
         JexlFeatures f = new JexlFeatures().pragma(false);
         String[] scripts = new String[]{
             "#pragma foo 42",
-            "@two var x = 3; #pragma foo 'bar'"
+            "#pragma foo 'bar' @two var x = 3; "
         };
         checkFeature(f, scripts);
     }
diff --git a/src/test/java/org/apache/commons/jexl3/ForEachIndexedTest.java b/src/test/java/org/apache/commons/jexl3/ForEachIndexedTest.java
new file mode 100644
index 0000000..e9da28c
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ForEachIndexedTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.StringTokenizer;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for the foreach statement
+ * @since 1.1
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ForEachIndexedTest extends JexlTestCase {
+
+    /** create a named test */
+    public ForEachIndexedTest() {
+        super("ForEachIndexedTest");
+    }
+
+    @Test
+    public void testForEachWithEmptyStatement() throws Exception {
+        JexlScript e = JEXL.createScript("for(index, item : list) ;");
+        JexlContext jc = new MapContext();
+        jc.set("list", Collections.emptyList());
+
+        Object o = e.execute(jc);
+        Assert.assertNull("Result is not null", o);
+    }
+
+    @Test
+    public void testForEachWithEmptyList() throws Exception {
+        JexlScript e = JEXL.createScript("for(index, item : list) 1+1");
+        JexlContext jc = new MapContext();
+        jc.set("list", Collections.emptyList());
+
+        Object o = e.execute(jc);
+        Assert.assertNull("Result is not null", o);
+    }
+
+    @Test
+    public void testForEachWithArray() throws Exception {
+        JexlScript e = JEXL.createScript("for(i, item : list) item");
+        JexlContext jc = new MapContext();
+        jc.set("list", new Object[]{"Hello", "World"});
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "World", o);
+    }
+
+    @Test
+    public void testForEachWithCollection() throws Exception {
+        JexlScript e = JEXL.createScript("for(var i, item : list) item");
+        JexlContext jc = new MapContext();
+        jc.set("list", Arrays.asList(new Object[]{"Hello", "World"}));
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "World", o);
+    }
+
+    @Test
+    public void testForEachWithEnumeration() throws Exception {
+        JexlScript e = JEXL.createScript("for(var i, item : list) item");
+        JexlContext jc = new MapContext();
+        jc.set("list", new StringTokenizer("Hello,World", ","));
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "World", o);
+    }
+
+    @Test
+    public void testForEachWithIterator() throws Exception {
+        JexlScript e = JEXL.createScript("for(var i, item : list) item");
+        JexlContext jc = new MapContext();
+        jc.set("list", Arrays.asList(new Object[]{"Hello", "World"}).iterator());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "World", o);
+    }
+
+    @Test
+    public void testForEachWithMap() throws Exception {
+        JexlScript e = JEXL.createScript("for(key, item : list) item");
+        JexlContext jc = new MapContext();
+        Map<?, ?> map = System.getProperties();
+        String lastProperty = (String) new ArrayList<Object>(map.values()).get(System.getProperties().size() - 1);
+        jc.set("list", map);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", lastProperty, o);
+    }
+
+    @Test
+    public void testForEachWithBlock() throws Exception {
+        JexlScript exs0 = JEXL.createScript("for(var i, k : list) { x = x + k; }");
+        JexlContext jc = new MapContext();
+        jc.set("list", new Object[]{2, 3});
+            jc.set("x", new Integer(1));
+        Object o = exs0.execute(jc);
+            Assert.assertEquals("Result is wrong", new Integer(6), o);
+            Assert.assertEquals("x is wrong", new Integer(6), jc.get("x"));
+        }
+
+    @Test
+    public void testForEachWithListExpression() throws Exception {
+        JexlScript e = JEXL.createScript("for(var key, value : list) key");
+        JexlContext jc = new MapContext();
+        Map<?, ?> map = System.getProperties();
+        String lastKey = (String) new ArrayList<Object>(map.keySet()).get(System.getProperties().size() - 1);
+        jc.set("list", map);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", lastKey, o);
+    }
+
+    @Test
+    public void testForEachWithProperty() throws Exception {
+        JexlScript e = JEXL.createScript("for(var i, item : list.cheeseList) item");
+        JexlContext jc = new MapContext();
+        jc.set("list", new Foo());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "brie", o);
+    }
+
+    @Test
+    public void testForEachWithIteratorMethod() throws Exception {
+        JexlScript e = JEXL.createScript("for(var i, item : list.cheezy) item");
+        JexlContext jc = new MapContext();
+        jc.set("list", new Foo());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "brie", o);
+    }
+
+    @Test
+    public void testForEachBreakMethod() throws Exception {
+        JexlScript e = JEXL.createScript(
+                "var rr = -1; for(var i, item : [1, 2, 3 ,4 ,5, 6]) { if (item == 3) { rr = item; break; }} rr"
+        );
+        JexlContext jc = new MapContext();
+        jc.set("list", new Foo());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", 3, o);
+    }
+
+    @Test
+    public void testForEachContinueMethod() throws Exception {
+        JexlScript e = JEXL.createScript(
+                "var rr = 0; for(var i, item : [1, 2, 3 ,4 ,5, 6]) { if (item <= 3) continue; rr = rr + item;}"
+        );
+        JexlContext jc = new MapContext();
+        jc.set("list", new Foo());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", 15, o);
+    }
+
+    @Test
+    public void testForEachRemoveMethod() throws Exception {
+        JexlScript e = JEXL.createScript(
+                "var set = {1, 2, 3 ,4 ,5, 6}; for(var i, item : set) { if (item <= 3) remove} size(set)"
+        );
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", 3, o);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/ForEachTest.java b/src/test/java/org/apache/commons/jexl3/ForEachTest.java
index ab4790a..0b20665 100644
--- a/src/test/java/org/apache/commons/jexl3/ForEachTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ForEachTest.java
@@ -105,7 +105,7 @@
 
     @Test
     public void testForEachWithBlock() throws Exception {
-        JexlScript exs0 = JEXL.createScript("for(var in : list) { x = x + in; }");
+        JexlScript exs0 = JEXL.createScript("for(var i : list) { x = x + i; }");
         JexlContext jc = new MapContext();
         jc.set("list", new Object[]{2, 3});
             jc.set("x", new Integer(1));
@@ -166,6 +166,16 @@
     }
 
     @Test
+    public void testForEachRemoveMethod() throws Exception {
+        JexlScript e = JEXL.createScript(
+                "var set = {1, 2, 3 ,4 ,5, 6}; for(var item : set) { if (item <= 3) remove} size(set)"
+        );
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", 3, o);
+    }
+
+    @Test
     public void testForEachContinueBroken() throws Exception {
         try {
             JexlScript e = JEXL.createScript("var rr = 0; continue;");
@@ -177,6 +187,17 @@
     }
 
     @Test
+    public void testForEachRemoveBroken() throws Exception {
+        try {
+            JexlScript e = JEXL.createScript("var rr = 0; remove;");
+            Assert.fail("remove is out of loop!");
+        } catch (JexlException.Parsing xparse) {
+            String str = xparse.detailedMessage();
+            Assert.assertTrue(str.contains("remove"));
+        }
+    }
+
+    @Test
     public void testForEachBreakBroken() throws Exception {
         try {
             JexlScript e = JEXL.createScript("if (true) { break; }");
diff --git a/src/test/java/org/apache/commons/jexl3/ForTest.java b/src/test/java/org/apache/commons/jexl3/ForTest.java
new file mode 100644
index 0000000..d48aca9
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ForTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.StringTokenizer;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for the for statement
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ForTest extends JexlTestCase {
+
+    /** create a named test */
+    public ForTest() {
+        super("ForTest");
+    }
+
+    @Test
+    public void testForStatement() throws Exception {
+        JexlScript e = JEXL.createScript("for(;;) break");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertNull("Result is not null", o);
+    }
+
+    @Test
+    public void testForInitialization() throws Exception {
+        JexlScript e = JEXL.createScript("for(var i = 0;;) break; i");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 0, o);
+
+        e = JEXL.createScript("var i = 0; for(i = 1;;) break; i");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 1, o);
+    }
+
+    @Test
+    public void testForTermination() throws Exception {
+        JexlScript e = JEXL.createScript("for(var i = 0; i < 10;) i = i + 1; i");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 10, o);
+
+        e = JEXL.createScript("var i = 0; for(;i < 10;) i = i + 1; i");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 10, o);
+    }
+
+    @Test
+    public void testForIncrement() throws Exception {
+        JexlScript e = JEXL.createScript("for(var i = 0; i < 10; i = i + 1); i");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 10, o);
+    }
+
+    @Test
+    public void testForBreak() throws Exception {
+        JexlScript e = JEXL.createScript("for(var i = 0; i < 10; i = i + 1) if (i > 5) break; i");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 6, o);
+    }
+
+    @Test
+    public void testForContinue() throws Exception {
+        JexlScript e = JEXL.createScript("var x = 0; for(var i = 0; i < 10; i = i + 1) {if (i < 5) continue; x = x + i}; x");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 5+6+7+8+9, o);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/IdentityTest.java b/src/test/java/org/apache/commons/jexl3/IdentityTest.java
new file mode 100644
index 0000000..c380315
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/IdentityTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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 the identity operators.
+ *
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class IdentityTest extends JexlTestCase {
+    public IdentityTest() {
+        super("IdentityTest");
+    }
+
+    @Test
+    public void testSimpleIdentical() throws Exception {
+        JexlScript e = JEXL.createScript("var x = '123'; var y = x; x === y");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+    }
+
+    @Test
+    public void testSimpleNotIdentical() throws Exception {
+        JexlScript e = JEXL.createScript("var x = '123'; var y = '123'; x !== y");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+    }
+
+    @Test
+    public void testNullIdentical() throws Exception {
+        JexlScript e = JEXL.createScript("var x = null; var y = null; x === y");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+    }
+
+    @Test
+    public void testNullNotIdentical() throws Exception {
+        JexlScript e = JEXL.createScript("var x = 123; var y = null; x !== y");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/IfTest.java b/src/test/java/org/apache/commons/jexl3/IfTest.java
index 83f0d2f..579c4cd 100644
--- a/src/test/java/org/apache/commons/jexl3/IfTest.java
+++ b/src/test/java/org/apache/commons/jexl3/IfTest.java
@@ -262,6 +262,65 @@
 
     /**
      * Ternary operator condition undefined or null evaluates to false
+     * independantly of engine flags.
+     * @throws Exception
+     */
+    @Test
+    public void testTernaryOptional() throws Exception {
+        JexlEngine jexl = JEXL;
+
+        JexlEvalContext jc = new JexlEvalContext();
+        JexlExpression e = jexl.createExpression("x.y.z = foo ?'bar'");
+        Object o;
+
+        // undefined foo
+        for (int l = 0; l < 4; ++l) {
+            jc.setStrict((l & 1) == 0);
+            jc.setSilent((l & 2) != 0);
+            o = e.evaluate(jc);
+            Assert.assertNull(o);
+            o = jc.get("x.y.z");
+            Assert.assertNull(o);
+        }
+
+        jc.set("foo", null);
+
+        for (int l = 0; l < 4; ++l) {
+            jc.setStrict((l & 1) == 0);
+            jc.setSilent((l & 2) != 0);
+            o = e.evaluate(jc);
+            Assert.assertNull(o);
+            o = jc.get("x.y.z");
+            Assert.assertNull(o);
+        }
+
+        jc.set("foo", Boolean.FALSE);
+
+        for (int l = 0; l < 4; ++l) {
+            jc.setStrict((l & 1) == 0);
+            jc.setSilent((l & 2) != 0);
+            o = e.evaluate(jc);
+            Assert.assertNull(o);
+            o = jc.get("x.y.z");
+            Assert.assertNull(o);
+        }
+
+        jc.set("foo", Boolean.TRUE);
+
+        for (int l = 0; l < 4; ++l) {
+            jc.setStrict((l & 1) == 0);
+            jc.setSilent((l & 2) != 0);
+            o = e.evaluate(jc);
+            Assert.assertEquals("Should be bar", "bar", o);
+            o = jc.get("x.y.z");
+            Assert.assertEquals("Should be bar", "bar", o);
+        }
+
+        debuggerCheck(jexl);
+    }
+
+    /**
+     * Ternary operator condition undefined or null evaluates to false
      * independently of engine flags; same for null coalescing operator.
      * @throws Exception
      */
diff --git a/src/test/java/org/apache/commons/jexl3/ImmutableTest.java b/src/test/java/org/apache/commons/jexl3/ImmutableTest.java
new file mode 100644
index 0000000..1b77aec
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ImmutableTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.Map;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for immutable array/set/map literals.
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ImmutableTest extends JexlTestCase {
+
+    public ImmutableTest() {
+        super("ImmutableTest");
+    }
+
+    @Test
+    public void testImmutableArrayLiteral() throws Exception {
+        JexlContext jc = new MapContext();
+        Object o = JEXL.createScript("var x = #[1, 2, 3]").execute(jc);
+        Assert.assertTrue(o instanceof List<?>);
+        Assert.assertEquals(3, ((List<?>) o).size());
+
+        try {
+           o = JEXL.createScript("var x = #[1, 2, 3]; x.remove(1)").execute(jc);
+           Assert.fail("should have failed");
+        } catch(JexlException xjexl) {
+            //
+        }
+    }
+
+    @Test
+    public void testImmutableSetLiteral() throws Exception {
+        JexlContext jc = new MapContext();
+        Object o = JEXL.createScript("var x = #{1, 2, 3}").execute(jc);
+        Assert.assertTrue(o instanceof Set);
+        Assert.assertEquals(3, ((Set) o).size());
+
+        try {
+           o = JEXL.createScript("var x = #{1, 2, 3}; x.remove(1)").execute(jc);
+           Assert.fail("should have failed");
+        } catch(JexlException xjexl) {
+            //
+        }
+    }
+
+    @Test
+    public void testImmutableMapLiteral() throws Exception {
+        JexlContext jc = new MapContext();
+        Object o = JEXL.createScript("var x = #{1:1, 2:2, 3:3}").execute(jc);
+        Assert.assertTrue(o instanceof Map);
+        Assert.assertEquals(3, ((Map) o).size());
+
+        try {
+           o = JEXL.createScript("var x = #{1:1, 2:2, 3:3}; x.remove(1)").execute(jc);
+           Assert.fail("should have failed");
+        } catch(JexlException xjexl) {
+            //
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/IncrementDecrementTest.java b/src/test/java/org/apache/commons/jexl3/IncrementDecrementTest.java
new file mode 100644
index 0000000..db2b33a
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/IncrementDecrementTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Test cases for increment/decrement assignment.
+ *
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class IncrementDecrementTest extends JexlTestCase {
+
+    public IncrementDecrementTest() {
+        super("IncrementDecrementTest", new JexlBuilder().cache(512).arithmetic(new AtomicArithmetic(false)).create());
+    }
+
+    public static class AtomicArithmetic extends JexlArithmetic {
+        AtomicArithmetic(boolean flag) {
+            super(flag);
+        }
+
+        public Object increment(AtomicInteger val) {
+            val.incrementAndGet();
+            return JexlOperator.ASSIGN;
+        }
+
+        public Object decrement(AtomicInteger val) {
+            val.decrementAndGet();
+            return JexlOperator.ASSIGN;
+        }
+    }
+
+    @Test
+    public void testIncrement() throws Exception {
+        JexlScript inc = JEXL.createScript("var x = 1; ++x");
+        JexlContext jc = new MapContext();
+        Object o = inc.execute(jc);
+        Assert.assertEquals("Result is not 2", new Integer(2), o);
+    }
+
+    @Test
+    public void testIncrementPost() throws Exception {
+        JexlScript inc = JEXL.createScript("var x = 1; ++x; x");
+        JexlContext jc = new MapContext();
+        Object o = inc.execute(jc);
+        Assert.assertEquals("Result is not 2", new Integer(2), o);
+    }
+
+    @Test
+    public void testIncrementOverride() throws Exception {
+        JexlScript inc = JEXL.createScript("++x");
+        JexlContext jc = new MapContext();
+        AtomicInteger value = new AtomicInteger(1);
+        jc.set("x", value);
+        Object o = inc.execute(jc);
+        Assert.assertEquals("Result is not 2", value, o);
+    }
+
+    @Test
+    public void testDecrement() throws Exception {
+        JexlScript dec = JEXL.createScript("var x = 43; --x");
+        JexlContext jc = new MapContext();
+        Object o = dec.execute(jc);
+        Assert.assertEquals("Result is not 42", new Integer(42), o);
+    }
+
+    @Test
+    public void testDecrementPost() throws Exception {
+        JexlScript dec = JEXL.createScript("var x = 43; --x; x");
+        JexlContext jc = new MapContext();
+        Object o = dec.execute(jc);
+        Assert.assertEquals("Result is not 42", new Integer(42), o);
+    }
+
+    @Test
+    public void testDecrementOverride() throws Exception {
+        JexlScript inc = JEXL.createScript("--x");
+        JexlContext jc = new MapContext();
+        AtomicInteger value = new AtomicInteger(43);
+        jc.set("x", value);
+        Object o = inc.execute(jc);
+        Assert.assertEquals("Result is not 42", value, o);
+    }
+
+    @Test
+    public void testIncrementPostfix() throws Exception {
+        JexlScript inc = JEXL.createScript("x = 1; x++");
+        JexlContext jc = new MapContext();
+        Object o = inc.execute(jc);
+        Assert.assertEquals("Variable is not 2", new Integer(2), jc.get("x"));
+        Assert.assertEquals("Result is not 1", new Integer(1), o);
+    }
+
+    @Test
+    public void testDecrementPostfix() throws Exception {
+        JexlScript dec = JEXL.createScript("x = 43; x--");
+        JexlContext jc = new MapContext();
+        Object o = dec.execute(jc);
+        Assert.assertEquals("Variable is not 42", new Integer(42), jc.get("x"));
+        Assert.assertEquals("Result is not 43", new Integer(43), o);
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/jexl3/IndirectTest.java b/src/test/java/org/apache/commons/jexl3/IndirectTest.java
new file mode 100644
index 0000000..f9624fa
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/IndirectTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.Assert;
+import org.junit.Test;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tests dereferencing operator.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class IndirectTest extends JexlTestCase {
+
+    public IndirectTest() {
+        super("IndirectTest");
+    }
+
+    @Test
+    public void testValue() throws Exception {
+        JexlScript e = JEXL.createScript("*x");
+        JexlContext jc = new MapContext();
+        jc.set("x", new AtomicInteger(42));
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    public class DuckTypedRef {
+        protected int x = 42;
+
+        public int get() {
+            return x;
+        }
+
+        public void set(Integer value) {
+            x = value;
+        }
+    }
+
+    @Test
+    public void testDuckTypedReference() throws Exception {
+        JexlScript e = JEXL.createScript("*x");
+        JexlContext jc = new MapContext();
+        jc.set("x", new DuckTypedRef());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    public class OverloadedRef {
+        protected int x = 42;
+
+        public int value() {
+            return x;
+        }
+
+        public void setValue(int i) {
+            x = i;
+        }
+    }
+
+    public static class IndirectArithmetic extends JexlArithmetic {
+        IndirectArithmetic(boolean flag) {
+            super(flag);
+        }
+
+        public Object indirect(OverloadedRef f) {
+            return f.value();
+        }
+
+        public void indirectAssign(OverloadedRef f, Integer value) {
+            f.setValue(value);
+        }
+        
+        public Object selfAdd(Collection<Object> c, Object value) {
+            if (value != null) {
+                if (value instanceof Collection<?>) {
+                    c.addAll((Collection<?>)value);
+                } else {
+                    c.add(value);
+                }
+            }
+            return JexlOperator.ASSIGN;
+        }
+    }
+
+    @Test
+    public void testOverloadedReference() throws Exception {
+        JexlEngine jexl = new JexlBuilder().arithmetic(new IndirectArithmetic(true)).create();
+        JexlScript e = jexl.createScript("*x");
+        JexlContext jc = new MapContext();
+        jc.set("x", new OverloadedRef());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    @Test
+    public void testStrictNullDereference() throws Exception {
+        JexlEngine jexl = new JexlBuilder().strict(true).create();
+        JexlScript e = jexl.createScript("*x");
+        JexlContext jc = new MapContext();
+        try {
+            Object o = e.execute(jc);
+            Assert.fail("Should have failed");
+        } catch (Exception ex) {
+            ///
+        }
+        jexl = new JexlBuilder().strict(false).create();
+        e = jexl.createScript("*x");
+        Object o = e.execute(jc);
+        Assert.assertNull(o);
+    }
+
+    @Test
+    public void testDereferencedAssign() throws Exception {
+        JexlEngine jexl = new JexlBuilder().arithmetic(new IndirectArithmetic(true)).create();
+        JexlScript e = jexl.createScript("*x = 42");
+        JexlContext jc = new MapContext();
+        AtomicInteger x = new AtomicInteger(0);
+        jc.set("x", x);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+        Assert.assertEquals("Result is not expected", 42, x.get());
+
+        e = jexl.createScript("*x = 41");
+        jc = new MapContext();
+        OverloadedRef xxf = new OverloadedRef();
+        jc.set("x", xxf);
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 41, o);
+        Assert.assertEquals("Result is not expected", 41, xxf.value());
+
+        e = jexl.createScript("*x = 41");
+        jc = new MapContext();
+        DuckTypedRef xxd = new DuckTypedRef();
+        jc.set("x", xxd);
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 41, o);
+        Assert.assertEquals("Result is not expected", 41, xxd.get());
+
+        e = jexl.createScript("*x += 42");
+        jc = new MapContext();
+        x = new AtomicInteger(0);
+        jc.set("x", x);
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+        Assert.assertEquals("Result is not expected", 42, x.get());
+
+        e = jexl.createScript("*x += 42");
+        jc = new MapContext();
+        AtomicReference xx = new AtomicReference();
+        ArrayList xxv = new ArrayList();
+        xx.set(xxv);
+        jc.set("x", xx);
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", o, xxv);
+        Assert.assertEquals("Result is not expected", 1, xxv.size());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/InlinePropertyTest.java b/src/test/java/org/apache/commons/jexl3/InlinePropertyTest.java
new file mode 100644
index 0000000..84deecf
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/InlinePropertyTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.util.Map;
+import java.util.HashMap;
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for blocks
+ * @since 1.1
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class InlinePropertyTest extends JexlTestCase {
+
+    /**
+     * Create the test
+     */
+    public InlinePropertyTest() {
+        super("InlinePropertyTest");
+    }
+
+    public class Address {
+
+        protected String postalCode;
+        protected String city;
+
+        protected Map<String, Object> tags;
+        protected List<String> lines;
+        protected List<AbstractMap.SimpleEntry> keys;
+
+        public Address() {
+           tags = new HashMap<String, Object> ();
+
+           lines = new ArrayList<String> ();
+           keys = new ArrayList<AbstractMap.SimpleEntry> ();
+
+           lines.add("Test");
+
+           keys.add(new AbstractMap.SimpleEntry(1, "Test"));
+           keys.add(new AbstractMap.SimpleEntry(2, "Test 2"));
+        }
+
+        public String getPostalCode() {
+           return postalCode;
+        }
+
+        public void setPostalCode(String value) {
+           postalCode = value;
+        }
+
+        public String getCity() {
+           return city;
+        }
+
+        public void setCity(String value) {
+           city = value;
+        }
+
+        public Map<String, Object> getTags() {
+           return tags;
+        }
+
+        public List<AbstractMap.SimpleEntry> getKeys() {
+           return keys;
+        }
+
+        public List<String> getLines() {
+           return lines;
+        }
+
+    }
+
+    @Test
+    public void inlinePropertySimple() throws Exception {
+        JexlScript e = JEXL.createScript("addr { PostalCode : '123456'}; addr.PostalCode");
+        JexlContext jc = new MapContext();
+        jc.set("addr", new Address());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "123456", o);
+    }
+
+    @Test
+    public void inlinePropertySimpleString() throws Exception {
+        JexlScript e = JEXL.createScript("addr { 'postalCode' : '123456'}; addr.postalCode");
+        JexlContext jc = new MapContext();
+        jc.set("addr", new Address());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "123456", o);
+    }
+
+    @Test
+    public void inlinePropertySimpleJxlt() throws Exception {
+        JexlScript e = JEXL.createScript("var name = 'postalCode'; addr { `${name}` : '123456'}; addr.postalCode");
+        JexlContext jc = new MapContext();
+        jc.set("addr", new Address());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "123456", o);
+    }
+
+    @Test
+    public void inlinePropertyNestedProp() throws Exception {
+        JexlScript e = JEXL.createScript("addr { tags { PostalCode : '123456'}}; addr.tags.PostalCode");
+        JexlContext jc = new MapContext();
+        jc.set("addr", new Address());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "123456", o);
+    }
+
+    @Test
+    public void inlinePropertyNestedArray() throws Exception {
+        JexlScript e = JEXL.createScript("var i = 0; addr { keys[i] { value : '123456'}}; addr.keys[0].value");
+        JexlContext jc = new MapContext();
+        jc.set("addr", new Address());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "123456", o);
+    }
+
+    @Test
+    public void inlinePropertyNestedArray2() throws Exception {
+        JexlScript e = JEXL.createScript("var i = 0; addr { keys { [i] { value : '123456'}}}; addr.keys[0].value");
+        JexlContext jc = new MapContext();
+        jc.set("addr", new Address());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "123456", o);
+    }
+
+    @Test
+    public void inlinePropertyArray() throws Exception {
+        JexlScript e = JEXL.createScript("var i = 0; addr.lines{[i] : '123456'}; addr.lines[0]");
+        JexlContext jc = new MapContext();
+        jc.set("addr", new Address());
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", "123456", o);
+    }
+
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/InstanceofTest.java b/src/test/java/org/apache/commons/jexl3/InstanceofTest.java
new file mode 100644
index 0000000..398aeef
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/InstanceofTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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 the instanceof operator.
+ *
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class InstanceofTest extends JexlTestCase {
+    public InstanceofTest() {
+        super("InstanceofTest");
+    }
+
+    @Test
+    public void testSimpleName() throws Exception {
+        JexlScript e = JEXL.createScript("var x = '123'; x instanceof String");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+    }
+
+    @Test
+    public void testArray() throws Exception {
+        JexlScript e = JEXL.createScript("var x = ['123','456']; x instanceof String[]");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+    }
+
+    @Test
+    public void testPrimitiveArray() throws Exception {
+        JexlScript e = JEXL.createScript("var x = [123,456]; x instanceof int[]");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+
+        e = JEXL.createScript("var x = [123,456]; not (x instanceof int)");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+    }
+
+    @Test
+    public void testQualifiedName() throws Exception {
+        JexlScript e = JEXL.createScript("var x = new('java.util.concurrent.atomic.AtomicLong'); x instanceof java.util.concurrent.atomic.AtomicLong");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+    }
+
+    @Test
+    public void testQualifiedArray() throws Exception {
+        JexlScript e = JEXL.createScript("var a = new('java.util.concurrent.atomic.AtomicLong'); var x = [a,a,a]; x instanceof java.util.concurrent.atomic.AtomicLong[]");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+    }
+
+    @Test
+    public void testPrimitive() throws Exception {
+        JexlScript e = JEXL.createScript("var x = 123; x instanceof int");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is true", Boolean.FALSE, o);
+    }
+
+    @Test
+    public void testNull() throws Exception {
+        JexlScript e = JEXL.createScript("var x = null; x instanceof Object");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is true", Boolean.FALSE, o);
+    }
+
+    @Test
+    public void testDefaultImports() throws Exception {
+        JexlScript e = JEXL.createScript("var x = {1,2,3}; x instanceof Set");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+
+        e = JEXL.createScript("var x = 100H; x instanceof BigInteger");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+
+        e = JEXL.createScript("var x = 100.1B; x instanceof BigDecimal");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+    }
+
+    @Test
+    public void testUntypedArray() throws Exception {
+        JexlScript e = JEXL.createScript("var x = [123,'456']; x instanceof []");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+
+        e = JEXL.createScript("var x = 100H; x instanceof []");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is true", Boolean.FALSE, o);
+    }
+
+    @Test
+    public void testMultidimensionalArray() throws Exception {
+        JexlScript e = JEXL.createScript("x instanceof []");
+        JexlContext jc = new MapContext();
+        jc.set("x", new int[5][6]);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+
+        e = JEXL.createScript("x instanceof [][]");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+
+        e = JEXL.createScript("x instanceof int[][]");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not true", Boolean.TRUE, o);
+
+        e = JEXL.createScript("x instanceof long[][]");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is true", Boolean.FALSE, o);
+
+        e = JEXL.createScript("x instanceof [][][]");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is true", Boolean.FALSE, o);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/Issues200Test.java b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
index 8d520aa..5b2d2a4 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues200Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
@@ -385,7 +385,7 @@
         result = script.execute(ctx);
         Assert.assertEquals(10, result);
     }
-      
+
     @Test
     public void test230() throws Exception {
         JexlEngine jexl = new JexlBuilder().cache(4).create();
@@ -514,7 +514,7 @@
             Iterator266 i266 = queue.getFirst();
             if (i266 != null) {
                 i266.remove();
-                throw new JexlException.Continue(null);
+                throw new JexlException.Continue(null, null);
             } else {
                 throw new NoSuchElementException();
             }
@@ -568,8 +568,8 @@
         result = script.execute(ctxt);
         Assert.assertTrue(result instanceof JexlScript);
     }
-    
-    
+
+
     @Test
     public void test274() throws Exception {
         JexlEngine jexl = new JexlBuilder().strict(true).safe(true).stackOverflow(5).create();
@@ -596,8 +596,8 @@
             String sxs = xstack.toString();
             Assert.assertTrue(sxs.contains("jvm"));
         }
-    } 
-               
+    }
+
     @Test
     public void test278() throws Exception {
         String[] srcs = new String[]{
@@ -616,7 +616,7 @@
         ctxt.set("union", "42");
         Object value;
         JexlScript jc;
-        for(int i = 0; i < srcs.length; ++i) { 
+        for(int i = 0; i < srcs.length; ++i) {
             String src = srcs[i];
             try {
                 jc = jexl.createScript(src);
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTest.java b/src/test/java/org/apache/commons/jexl3/JexlTest.java
index 8db318b..7ad4e89 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTest.java
@@ -83,6 +83,17 @@
     }
 
     @Test
+    public void testThis() throws Exception {
+        JexlContext jc = new MapContext();
+        jc.set("a", Boolean.TRUE);
+        jc.set("b", Boolean.FALSE);
+
+        assertExpression(jc, "this", jc);
+        assertExpression(jc, "this.a", Boolean.TRUE);
+        assertExpression(jc, "this['b']", Boolean.FALSE);
+    }
+
+    @Test
     public void testStringLit() throws Exception {
         /*
          *  tests a simple property expression
diff --git a/src/test/java/org/apache/commons/jexl3/LabeledStatementsTest.java b/src/test/java/org/apache/commons/jexl3/LabeledStatementsTest.java
new file mode 100644
index 0000000..50f203b
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/LabeledStatementsTest.java
@@ -0,0 +1,242 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Assert;
+import org.junit.Test;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tests labeled statements.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class LabeledStatementsTest extends JexlTestCase {
+
+    public LabeledStatementsTest() {
+        super("LabeledStatements");
+    }
+
+    @Test
+    public void testBlock() throws Exception {
+        JexlScript e = JEXL.createScript("egg : {x = 1; break egg; x = 2}");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 1, jc.get("x"));
+
+        e = JEXL.createScript("foo : { bar : {x = 1; break foo; x = 2}; x = 3}");
+        jc = new MapContext();
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 1, jc.get("x"));
+    }
+
+    @Test
+    public void testBadBlock() throws Exception {
+        try {
+            JEXL.createScript("egg : {break eggs}");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : {x = function() {break egg}}");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : {break}");
+            Assert.fail("Unlabelled break inside block should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : {continue egg}");
+            Assert.fail("Unsupported continue on labeled block");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : {remove egg}");
+            Assert.fail("Unsupported remove on labeled block");
+        } catch (Exception ex) {
+        }
+    }
+
+    @Test
+    public void testWhile() throws Exception {
+        JexlScript e = JEXL.createScript("x = 0; foo : while (x < 7) bar : while(x < 10) {x = x + 1; if (x >= 5) break foo}");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 5, jc.get("x"));
+
+        e = JEXL.createScript("x = 0; foo : while (x < 7) {x = x + 1; if (x >= 5) break}");
+        jc = new MapContext();
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 5, jc.get("x"));
+    }
+
+    @Test
+    public void testBadWhile() throws Exception {
+        try {
+            JEXL.createScript("egg : while(true) break eggs");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : while(true) x = function() {break egg}");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : while(true) remove egg");
+            Assert.fail("Unsupported remove on labeled while");
+        } catch (Exception ex) {
+        }
+    }
+
+    @Test
+    public void testDoWhile() throws Exception {
+        JexlScript e = JEXL.createScript("x = 0; foo : do { bar : do {x = x + 1; if (x >= 5) break foo} while(x < 10)} while (x < 7)");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 5, jc.get("x"));
+
+        e = JEXL.createScript("x = 0; foo : do { x = x + 1; if (x >= 5) break} while (x < 7)");
+        jc = new MapContext();
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 5, jc.get("x"));
+    }
+
+    @Test
+    public void testBadDoWhile() throws Exception {
+        try {
+            JEXL.createScript("egg : do { break eggs } while(true)");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : do { x = function() {break egg}} while(true)");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : do { remove egg } while(true)");
+            Assert.fail("Unsupported remove on labeled do-while");
+        } catch (Exception ex) {
+        }
+    }
+
+    @Test
+    public void testSwitch() throws Exception {
+        JexlScript e = JEXL.createScript("x = 0; foo : switch(true) { default : { bar : switch (false) { default : break foo}}; x = 1}");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 0, jc.get("x"));
+
+        e = JEXL.createScript("x = 0; foo : switch(true) { default : break; x = 1}");
+        jc = new MapContext();
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 0, jc.get("x"));
+    }
+
+    @Test
+    public void testBadSwitch() throws Exception {
+        try {
+            JEXL.createScript("egg : switch(true) {default : break eggs}");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : switch(true) {default : x = function() {break egg}}");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : switch(true) {default : continue egg}");
+            Assert.fail("Unsupported continue on labeled switch");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : switch(true) {default : remove egg}");
+            Assert.fail("Unsupported remove on labeled switch");
+        } catch (Exception ex) {
+        }
+    }
+
+    @Test
+    public void testFor() throws Exception {
+        JexlScript e = JEXL.createScript("x = 0; foo : for (; x < 7; ) bar : for (; x < 10; ) {x = x + 1; if (x >= 5) break foo}");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 5, jc.get("x"));
+
+        e = JEXL.createScript("x = 0; foo : for (; x < 7; ) {x = x + 1; if (x >= 5) break}");
+        jc = new MapContext();
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 5, jc.get("x"));
+    }
+
+    @Test
+    public void testBadFor() throws Exception {
+        try {
+            JEXL.createScript("egg : for(;;) break eggs");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : for(;;) x = function() {break egg}");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : for(;;) remove egg");
+            Assert.fail("Unsupported remove on labeled for");
+        } catch (Exception ex) {
+        }
+    }
+
+    @Test
+    public void testForeach() throws Exception {
+        JexlScript e = JEXL.createScript("x = 0; foo : for (var i : 1..5) bar : for (var y : 0..10) {x = x + 1; if (x >= 5) break foo}");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 5, jc.get("x"));
+
+        e = JEXL.createScript("x = 0; foo : for (var i : 1..5) {x = x + 1; if (x >= 5) break}");
+        jc = new MapContext();
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 5, jc.get("x"));
+    }
+
+    @Test
+    public void testBadForeach() throws Exception {
+        try {
+            JEXL.createScript("egg : for(var i : 1..5) break eggs");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+        try {
+            JEXL.createScript("egg : for(var i : 1..5) x = function() {break egg}");
+            Assert.fail("Undeclared break label should not be allowed");
+        } catch (Exception ex) {
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/LambdaExpressionTest.java b/src/test/java/org/apache/commons/jexl3/LambdaExpressionTest.java
new file mode 100644
index 0000000..73a8e9d
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/LambdaExpressionTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.apache.commons.jexl3.internal.Engine;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests function/lambda/closure features.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class LambdaExpressionTest extends JexlTestCase {
+
+    public LambdaExpressionTest() {
+        super("LambdaExpressionTest");
+    }
+
+    @Test
+    public void testScriptContext() throws Exception {
+        JexlEngine jexl = new Engine();
+        JexlScript s = jexl.createScript("function(x) { x + x }");
+        Assert.assertEquals(42, s.execute(null, 21));
+        JexlScript s42 = jexl.createScript("s(21)");
+        JexlEvalContext ctxt = new JexlEvalContext();
+        ctxt.set("s", s);
+        Object result = s42.execute(ctxt);
+        Assert.assertEquals(42, result);
+        result = s42.execute(ctxt);
+        Assert.assertEquals(42, result);
+        s42 = jexl.createScript("x => x + x");
+        result = s42.execute(ctxt, 21);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
+    public void testLambda() throws Exception {
+        JexlEngine jexl = new Engine();
+        String strs = "var s = x => x + x; s(21)";
+        JexlScript s42 = jexl.createScript(strs);
+        Object result = s42.execute(null);
+        Assert.assertEquals(42, result);
+        strs = "var s = (x, y) => x + y; s(15, 27)";
+        s42 = jexl.createScript(strs);
+        result = s42.execute(null);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
+    public void testLambdaClosure() throws Exception {
+        JexlEngine jexl = new Engine();
+        String strs = "var t = 20; var s = (x, y) => x + y + t; s(15, 7)";
+        JexlScript s42 = jexl.createScript(strs);
+        Object result = s42.execute(null);
+        Assert.assertEquals(42, result);
+        strs = "var t = 20; var s = (x, y) => x + y + t; t = 54; s(15, 7)";
+        s42 = jexl.createScript(strs);
+        result = s42.execute(null);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
+    public void testLambdaLambda() throws Exception {
+        JexlEngine jexl = new Engine();
+
+        String strs = "( (x, y) => ( (xx, yy) => xx + yy)(x, y))(15, 27)";
+        JexlScript s42 = jexl.createScript(strs);
+        Object result = s42.execute(null);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
+    public void testNestLambda() throws Exception {
+        JexlEngine jexl = new Engine();
+        String strs = "( (x) => (y) => x + y)(15)(27)";
+        JexlScript s42 = jexl.createScript(strs);
+        Object result = s42.execute(null);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
+    public void testRecurse() throws Exception {
+        JexlEngine jexl = new Engine();
+        JexlContext jc = new MapContext();
+        try {
+            JexlScript script = jexl.createScript("var fact = x => (x <= 1) ? 1 : x * fact(x - 1); fact(5)");
+            int result = (Integer) script.execute(jc);
+            Assert.assertEquals(120, result);
+        } catch (JexlException xany) {
+            String msg = xany.toString();
+            throw xany;
+        }
+    }
+
+    @Test
+    public void testRecurse2() throws Exception {
+        JexlEngine jexl = new Engine();
+        JexlContext jc = new MapContext();
+        // adding some hoisted vars to get it confused
+        try {
+            JexlScript script = jexl.createScript(
+                    "var y = 1; var z = 1; "
+                    +"var fact = (x) => (x <= y) ? z : x * fact(x - 1); fact(6)");
+            int result = (Integer) script.execute(jc);
+            Assert.assertEquals(720, result);
+        } catch (JexlException xany) {
+            String msg = xany.toString();
+            throw xany;
+        }
+    }
+
+    @Test
+    public void testIdentity() throws Exception {
+        JexlEngine jexl = new Engine();
+        JexlScript script;
+        Object result;
+
+        script = jexl.createScript("(x) => x");
+        Assert.assertArrayEquals(new String[]{"x"}, script.getParameters());
+        result = script.execute(null, 42);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
+    public void testCurry1() throws Exception {
+        JexlEngine jexl = new Engine();
+        JexlScript script;
+        Object result;
+
+        JexlScript base = jexl.createScript("(x, y, z)=>x + y + z");
+        script = base.curry(5);
+        script = script.curry(15);
+        script = script.curry(22);
+        result = script.execute(null);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
+    public void testCurry2() throws Exception {
+        JexlEngine jexl = new Engine();
+        JexlScript script;
+        Object result;
+
+        JexlScript base = jexl.createScript("(x, y, z)=>x + y + z");
+        script = base.curry(5, 15);
+        script = script.curry(22);
+        result = script.execute(null);
+        Assert.assertEquals(42, result);
+    }
+}
diff --git a/src/test/java/org/apache/commons/jexl3/LambdaTest.java b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
index 930377c..8ab2255 100644
--- a/src/test/java/org/apache/commons/jexl3/LambdaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
@@ -46,7 +46,7 @@
         JexlEngine jexl = createEngine();
         JexlScript s = jexl.createScript("function(x) { x + x }");
         String fsstr = s.getParsedText(0);
-        Assert.assertEquals("(x)->{ x + x; }", fsstr);
+        Assert.assertEquals("x->{ x + x; }", fsstr);
         Assert.assertEquals(42, s.execute(null, 21));
         JexlScript s42 = jexl.createScript("s(21)");
         JexlEvalContext ctxt = new JexlEvalContext();
@@ -61,6 +61,15 @@
     }
 
     @Test
+    public void testParameterlessFunction() throws Exception {
+        JexlEngine jexl = createEngine();
+        String strs = "var s = function { 21 + 21 }; s()";
+        JexlScript s42 = jexl.createScript(strs);
+        Object result = s42.execute(null);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
     public void testLambda() throws Exception {
         JexlEngine jexl = createEngine();
         String strs = "var s = function(x) { x + x }; s(21)";
diff --git a/src/test/java/org/apache/commons/jexl3/MethodTest.java b/src/test/java/org/apache/commons/jexl3/MethodTest.java
index 9849fcf..8ee8266 100644
--- a/src/test/java/org/apache/commons/jexl3/MethodTest.java
+++ b/src/test/java/org/apache/commons/jexl3/MethodTest.java
@@ -131,6 +131,15 @@
     }
 
     @Test
+    public void testCallRemoveMethod() throws Exception {
+
+        JexlContext context = new MapContext();
+        JexlScript rm = JEXL.createScript("var x = {1, 2, 3}; x.remove(3); size(x)");
+        Object o = rm.execute(context);
+        Assert.assertEquals("Result is not 2", new Integer(2), o);
+    }
+
+    @Test
     public void testCallVarArgMethod() throws Exception {
         VarArgs test = new VarArgs();
         asserter.setVariable("test", test);
@@ -139,7 +148,6 @@
         asserter.assertExpression("test.callInts(1,2,3,4,5)", test.callInts(1, 2, 3, 4, 5));
         asserter.assertExpression("test.concat(['1', '2', '3'])", test.concat(new String[]{"1", "2", "3"}));
         asserter.assertExpression("test.concat('1', '2', '3')", test.concat("1", "2", "3"));
-
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/jexl3/MultiAssignTest.java b/src/test/java/org/apache/commons/jexl3/MultiAssignTest.java
new file mode 100644
index 0000000..84df1e7
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/MultiAssignTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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 multiple assignment.
+ *
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class MultiAssignTest extends JexlTestCase {
+
+    public static class Quux {
+        String str;
+        int value;
+        public Quux(String str, int value) {
+            this.str = str;
+            this.value = value;
+        }
+        public String getStr() {
+            return str;
+        }
+
+        public void setStr(String str) {
+            this.str = str;
+        }
+
+        public void setValue(int v) {
+            value = v;
+        }
+        public int getValue() {
+            return value;
+        }
+    }
+
+    public MultiAssignTest() {
+        super("MultiAssignTest", new JexlBuilder().cache(512).strict(true).silent(false).create());
+    }
+
+    @Test
+    public void testArray() throws Exception {
+        JexlScript assign = JEXL.createScript("(x,y) = [40,2,6]");
+        JexlContext jc = new MapContext();
+        jc.set("x", 10);
+        jc.set("y", 20);
+        Object o = assign.execute(jc);
+        Assert.assertEquals("Result is not 40", new Integer(40), jc.get("x"));
+        Assert.assertEquals("Result is not 2", new Integer(2), jc.get("y"));
+    }
+
+    @Test
+    public void testMap() throws Exception {
+        JexlScript assign = JEXL.createScript("(x,y) = {'z':22,'x':40,'y':2}");
+        JexlContext jc = new MapContext();
+        jc.set("x", 10);
+        jc.set("y", 20);
+        Object o = assign.execute(jc);
+        Assert.assertEquals("Result is not 40", new Integer(40), jc.get("x"));
+        Assert.assertEquals("Result is not 2", new Integer(2), jc.get("y"));
+    }
+
+    @Test
+    public void testObject() throws Exception {
+        JexlScript assign = JEXL.createScript("(str,value) = quux");
+        JexlContext jc = new MapContext();
+        jc.set("x", 10);
+        jc.set("y", 20);
+        jc.set("quux", new Quux("bar",42));
+        Object o = assign.execute(jc);
+        Assert.assertEquals("Result is not bar", "bar", jc.get("str"));
+        Assert.assertEquals("Result is not 42", new Integer(42), jc.get("value"));
+    }
+
+    @Test
+    public void testNull() throws Exception {
+        JexlScript assign = JEXL.createScript("(x,y) = null");
+        JexlContext jc = new MapContext();
+        jc.set("x", 10);
+        jc.set("y", 20);
+        Object o = assign.execute(jc);
+        Assert.assertEquals("Result is not null", null, jc.get("x"));
+        Assert.assertEquals("Result is not null", null, jc.get("y"));
+    }
+
+    @Test
+    public void testUnderflow() throws Exception {
+        JexlScript assign = JEXL.createScript("(x,y,z) = [40,2]");
+        JexlContext jc = new MapContext();
+        jc.set("x", 10);
+        jc.set("y", 20);
+        jc.set("z", 30);
+        Object o = assign.execute(jc);
+        Assert.assertEquals("Result is not null", null, jc.get("z"));
+    }
+
+    @Test
+    public void testOverflow() throws Exception {
+        JexlScript assign = JEXL.createScript("(x,y) = [40,2,1]");
+        JexlContext jc = new MapContext();
+        jc.set("x", 10);
+        jc.set("y", 20);
+        Object o = assign.execute(jc);
+        Assert.assertEquals("Result is not null", new Integer(2), o);
+    }
+
+    @Test
+    public void testVar() throws Exception {
+        JexlScript assign = JEXL.createScript("var (x,y) = [40,2,6]; y");
+        JexlContext jc = new MapContext();
+        jc.set("x", 10);
+        jc.set("y", 20);
+        Object o = assign.execute(jc);
+        Assert.assertEquals("Result is not 2", new Integer(2), o);
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/jexl3/PointerTest.java b/src/test/java/org/apache/commons/jexl3/PointerTest.java
new file mode 100644
index 0000000..ba47a04
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/PointerTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.Assert;
+import org.junit.Test;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tests pointer operator.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class PointerTest extends JexlTestCase {
+
+    public PointerTest() {
+        super("PointerTest");
+    }
+
+    @Test
+    public void testVarPointerGet() throws Exception {
+        JexlScript e = JEXL.createScript("var x = 42; var y = &x; *y");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    @Test
+    public void testVarPointerSet() throws Exception {
+        JexlScript e = JEXL.createScript("var x = 1; var y = &x; *y = 42; x");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    @Test
+    public void testVarPointerSideEffects() throws Exception {
+        JexlScript e = JEXL.createScript("var x = 1; var y = &x; *y += 41; x");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    @Test
+    public void testContextVarPointerGet() throws Exception {
+        JexlScript e = JEXL.createScript("var y = &x; *y");
+        JexlContext jc = new MapContext();
+        jc.set("x",42);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    @Test
+    public void testContextVarPointerSet() throws Exception {
+        JexlScript e = JEXL.createScript("var y = &x; *y = 42");
+        JexlContext jc = new MapContext();
+        jc.set("x",1);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, jc.get("x"));
+    }
+
+    @Test
+    public void testContextVarPointerSideEffects() throws Exception {
+        JexlScript e = JEXL.createScript("var y = &x; *y += 41");
+        JexlContext jc = new MapContext();
+        jc.set("x",1);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, jc.get("x"));
+    }
+
+    @Test
+    public void testAntishContextVarPointerGet() throws Exception {
+        JexlScript e = JEXL.createScript("var y = &x.z; *y");
+        JexlContext jc = new MapContext();
+        jc.set("x.z",42);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    @Test
+    public void testAntishContextVarPointerSet() throws Exception {
+        JexlScript e = JEXL.createScript("var y = &x.z; *y = 42");
+        JexlContext jc = new MapContext();
+        jc.set("x.z",1);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, jc.get("x.z"));
+    }
+
+    @Test
+    public void testAntishContextVarPointerSideEffects() throws Exception {
+        JexlScript e = JEXL.createScript("var y = &x.z; *y += 41");
+        JexlContext jc = new MapContext();
+        jc.set("x.z",1);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, jc.get("x.z"));
+    }
+
+    @Test
+    public void testBeanPointerGet() throws Exception {
+        JexlScript e = JEXL.createScript("var x = {'a':2,'b':42}; var y = &x.b; *y");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    @Test
+    public void testBeanPointerSet() throws Exception {
+        JexlScript e = JEXL.createScript("var x = {'a':2,'b':1}; var y = &x.b; *y = 42; x.b");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+
+        e = JEXL.createScript("var x = {'a':2,'b':42}; var y = &x.b; x = {'a':2,'b':0}; *y");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    @Test
+    public void testArrayPointerGet() throws Exception {
+        JexlScript e = JEXL.createScript("var x = [1,2,3,4,42]; var y = &x[4]; *y");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+
+        e = JEXL.createScript("var x = [1,2,3,4,42]; var i = 4; var y = &x[i]; i = 0; *y");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+
+        e = JEXL.createScript("var x = [1,2,3,4,42]; var i = 4; var y = &x[i]; x = [1,2,3,4,5]; *y");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    @Test
+    public void testArrayPointerSet() throws Exception {
+        JexlScript e = JEXL.createScript("var x = [1,2,3,4,5]; var y = &x[4]; *y = 42; x[4]");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+
+        e = JEXL.createScript("var x = [1,2,3,4,5]; var i = 4; var y = &x[i]; i = 0; *y = 42; x[4]");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+    @Test
+    public void testBadPointer() throws Exception {
+        try {
+            JEXL.createScript("var y = &null");
+            Assert.fail("Non left values should not be allowed");
+        } catch (Exception ex) {
+            // OK
+        }
+        try {
+            JEXL.createScript("var y = &(2+2)");
+            Assert.fail("Non left values should not be allowed");
+        } catch (Exception ex) {
+            // OK
+        }
+    }
+
+    @Test
+    public void testOuterPointerSet() throws Exception {
+        JexlScript e = JEXL.createScript("var x = 1; var f = (y) -> { *y = 42}; f(&x); x");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42, o);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/PragmaTest.java b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
index 3eff579..ecb8cfc 100644
--- a/src/test/java/org/apache/commons/jexl3/PragmaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
@@ -47,6 +47,7 @@
             Assert.assertEquals("truth", pragmas.get("the.very.hard"));
         } catch (JexlException xjexl) {
             String s = xjexl.toString();
+            Assert.fail("Should not fail with: " + s);
         }
     }
 
@@ -63,6 +64,7 @@
             Assert.assertEquals("truth", pragmas.get("the.very.hard"));
         } catch (JexlException xjexl) {
             String s = xjexl.toString();
+            Assert.fail("Should not fail with: " + s);
         }
     }
 
diff --git a/src/test/java/org/apache/commons/jexl3/ProjectionTest.java b/src/test/java/org/apache/commons/jexl3/ProjectionTest.java
new file mode 100644
index 0000000..856a98f
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ProjectionTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+import java.util.ArrayList;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for projections
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ProjectionTest extends JexlTestCase {
+
+    protected Map<String, String> testMap;
+    protected List<String> test;
+
+
+    public ProjectionTest() {
+        super("ProjectionTest");
+
+        testMap = new HashMap<String, String>();
+        testMap.put("foo", "bar");
+        testMap.put("eat", "food");
+        testMap.put("drink", "water");
+
+        test = new ArrayList<String>();
+        test.add("apple");
+        test.add("banana");
+        test.add("kiwi");
+    }
+
+    @Test
+    public void testMapProjection() throws Exception {
+        JexlScript e = JEXL.createScript("var s = {}; for (var i : ...m.[key]) s.add(i); s");
+        JexlContext jc = new MapContext();
+        jc.set("m", testMap);
+
+        Object o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("foo"));
+        Assert.assertEquals(Boolean.FALSE, ((Set)o).contains("bar"));
+
+        e = JEXL.createScript("var s = {}; for (var i : ...m.[value]) s.add(i); s");
+        o = e.execute(jc);
+        Assert.assertEquals(Boolean.FALSE, ((Set)o).contains("foo"));
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("bar"));
+    }
+
+    @Test
+    public void testMapProjection2() throws Exception {
+        JexlScript e = JEXL.createScript("var s = {:}; for (var i : ...m.[key,value]) s.put(i[0],i[1]); s");
+        JexlContext jc = new MapContext();
+        jc.set("m", testMap);
+
+        Object o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Map)o).containsKey("foo"));
+        Assert.assertEquals(Boolean.TRUE, ((Map)o).containsValue("food"));
+
+        e = JEXL.createScript("var s = {:}; for (var i : ...m.{value:key}) s.put(i.key,i.value); s");
+        o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Map)o).containsKey("bar"));
+        Assert.assertEquals(Boolean.TRUE, ((Map)o).containsValue("eat"));
+    }
+
+    @Test
+    public void testMapProjection3() throws Exception {
+        JexlScript e = JEXL.createScript("var s = {:}; for (var i : ...m.[key.length(),key,value]) s.put(i[1],i[2]); s");
+        JexlContext jc = new MapContext();
+        jc.set("m", testMap);
+
+        Object o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Map)o).containsKey("foo"));
+        Assert.assertEquals(Boolean.TRUE, ((Map)o).containsValue("food"));
+    }
+
+    @Test
+    public void testLambdaMapProjection() throws Exception {
+        JexlScript e = JEXL.createScript("var s = {}; for (var i : ...m.[(index, entry) -> {entry.value}]) s.add(i); s");
+        JexlContext jc = new MapContext();
+        jc.set("m", testMap);
+
+        Object o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("bar"));
+        Assert.assertEquals(Boolean.FALSE, ((Set)o).contains("eat"));
+    }
+
+    @Test
+    public void testLambdaMapProjection2() throws Exception {
+        JexlScript e = JEXL.createScript("var s = {}; for (var i : ...m.[entry -> {entry.value}]) s.add(i); s");
+        JexlContext jc = new MapContext();
+        jc.set("m", testMap);
+
+        Object o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("bar"));
+        Assert.assertEquals(Boolean.FALSE, ((Set)o).contains("eat"));
+    }
+
+    @Test
+    public void testListProjection() throws Exception {
+        JexlScript e = JEXL.createScript("var s = {}; for (var i : ...fruits.[value -> {value.length()}]) s.add(i); s");
+        JexlContext jc = new MapContext();
+        jc.set("fruits", test);
+
+        Object o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains(4));
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains(6));
+    }
+
+    @Test
+    public void testIndexedListProjection() throws Exception {
+        JexlScript e = JEXL.createScript("var s = {}; for (var i : ...fruits.[(index, value) -> {index}]) s.add(i); s");
+        JexlContext jc = new MapContext();
+        jc.set("fruits", test);
+
+        Object o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains(0));
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains(1));
+
+        e = JEXL.createScript("var s = {}; for (var i : ...fruits.[(index, value) -> {value}]) s.add(i); s");
+        o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("banana"));
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("kiwi"));
+
+        e = JEXL.createScript("var s = {}; for (var i : ...fruits.[(index, value, dummy) -> {empty dummy ? value : index}]) s.add(i); s");
+        o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("banana"));
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("apple"));
+
+        e = JEXL.createScript("var s = {}; for (var i : ...fruits.[(index, value) -> {index},(index, value) -> {value}]) s.add(i[1]); s");
+        o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("banana"));
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("kiwi"));
+    }
+
+    @Test
+    public void testListSelection() throws Exception {
+        JexlScript e = JEXL.createScript("var s = {}; for (var i : ...fruits.(value -> {value.length() >= 5})) s.add(i); s");
+        JexlContext jc = new MapContext();
+        jc.set("fruits", test);
+
+        Object o = e.execute(jc);
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains("banana"));
+        Assert.assertEquals(Boolean.FALSE, ((Set)o).contains("kiwi"));
+    }
+
+    @Test
+    public void testListSelectionProjection() throws Exception {
+        JexlScript e = JEXL.createScript("var s = {}; for (var i : ...fruits.(value -> {value.length() >= 5}).[value -> {value.length()}]) s.add(i); s");
+        JexlContext jc = new MapContext();
+        jc.set("fruits", test);
+
+        Object o = e.execute(jc);
+        Assert.assertEquals(Boolean.FALSE, ((Set)o).contains(4));
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains(6));
+    }
+
+    @Test
+    public void testListProjectionSelection() throws Exception {
+        JexlScript e = JEXL.createScript("var s = {}; for (var i : ...fruits.[value->{value.length()}].(len->{len >= 5})) s.add(i); s");
+        JexlContext jc = new MapContext();
+        jc.set("fruits", test);
+
+        Object o = e.execute(jc);
+        Assert.assertEquals(Boolean.FALSE, ((Set)o).contains(4));
+        Assert.assertEquals(Boolean.TRUE, ((Set)o).contains(6));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/ShiftOperatorsTest.java b/src/test/java/org/apache/commons/jexl3/ShiftOperatorsTest.java
new file mode 100644
index 0000000..02b31a2
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ShiftOperatorsTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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.BigInteger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tests shift operators.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ShiftOperatorsTest extends JexlTestCase {
+
+    public ShiftOperatorsTest() {
+        super("ShiftOperatorsTest");
+    }
+
+    @Test
+    public void testLeftShiftIntValue() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("1 << 2");
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 1 << 2, o);
+
+        e = JEXL.createScript("1 << -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 1 << -2, o);
+
+        e = JEXL.createScript("-1 << 2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -1 << 2, o);
+
+        e = JEXL.createScript("-1 << -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -1 << -2, o);
+    }
+
+    @Test
+    public void testRightShiftIntValue() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("42 >> 2");
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42 >> 2, o);
+
+        e = JEXL.createScript("42 >> -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42 >> -2, o);
+
+        e = JEXL.createScript("-42 >> 2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -42 >> 2, o);
+
+        e = JEXL.createScript("-42 >> -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -42 >> -2, o);
+    }
+
+    @Test
+    public void testRightShiftUnsignedIntValue() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("42 >>> 2");
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42 >>> 2, o);
+
+        e = JEXL.createScript("42 >>> -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 42 >>> -2, o);
+
+        e = JEXL.createScript("-42 >>> 2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -42 >>> 2, o);
+
+        e = JEXL.createScript("-42 >>> -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -42 >>> -2, o);
+    }
+
+    @Test
+    public void testLeftShiftLongValue() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("2147483648 << 2");
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 2147483648L << 2, o);
+
+        e = JEXL.createScript("2147483648 << -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 2147483648L << -2, o);
+
+        e = JEXL.createScript("-2147483649 << 2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -2147483649L << 2, o);
+
+        e = JEXL.createScript("-2147483649 << -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -2147483649L << -2, o);
+    }
+
+    @Test
+    public void testRightShiftLongValue() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("8589934592 >> 2");
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 8589934592L >> 2, o);
+
+        e = JEXL.createScript("8589934592 >> -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 8589934592L >> -2, o);
+
+        e = JEXL.createScript("-8589934596 >> 2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -8589934596L >> 2, o);
+
+        e = JEXL.createScript("-8589934596 >> -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -8589934596L >> -2, o);
+    }
+
+    @Test
+    public void testRightShiftUnsignedLongValue() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("8589934592 >>> 2");
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 8589934592L >>> 2, o);
+
+        e = JEXL.createScript("8589934592 >>> -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 8589934592L >>> -2, o);
+
+        e = JEXL.createScript("-8589934596 >>> 2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -8589934596L >>> 2, o);
+
+        e = JEXL.createScript("-8589934596 >>> -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", -8589934596L >>> -2, o);
+    }
+
+    @Test
+    public void testLeftShiftBigValue() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("9223372036854775808 << 2");
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", new BigInteger("9223372036854775808").shiftLeft(2), o);
+
+        e = JEXL.createScript("9223372036854775808 << -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", new BigInteger("9223372036854775808").shiftLeft(-2), o);
+
+        e = JEXL.createScript("-9223372036854775809 << 2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", new BigInteger("-9223372036854775809").shiftLeft(2), o);
+
+        e = JEXL.createScript("-9223372036854775809 << -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", new BigInteger("-9223372036854775809").shiftLeft(-2), o);
+    }
+
+    @Test
+    public void testRightShiftBigValue() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("9223372036854775808 >> 2");
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", new BigInteger("9223372036854775808").shiftRight(2), o);
+
+        e = JEXL.createScript("9223372036854775808 >> -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", new BigInteger("9223372036854775808").shiftRight(-2), o);
+
+        e = JEXL.createScript("-9223372036854775809 >> 2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", new BigInteger("-9223372036854775809").shiftRight(2), o);
+
+        e = JEXL.createScript("-9223372036854775809 >> -2");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", new BigInteger("-9223372036854775809").shiftRight(-2), o);
+    }
+
+    public static class ShiftArithmetic extends JexlArithmetic {
+        ShiftArithmetic(boolean flag) {
+            super(flag);
+        }
+       
+        public Object leftShift(StringBuilder c, String value) {
+            c.append(value);
+            return c;
+        }
+
+        public Object rightShift(String value, StringBuilder c) {
+            c.append(value);
+            return c;
+        }
+
+        public Object rightShiftUnsigned(String value, StringBuilder c) {
+            c.append(value.toLowerCase());
+            return c;
+        }
+    }
+
+    @Test
+    public void testOverloadedShift() throws Exception {
+        JexlEngine jexl = new JexlBuilder().arithmetic(new ShiftArithmetic(true)).create();
+        JexlScript e = jexl.createScript("x << 'Left'");
+        JexlContext jc = new MapContext();
+        StringBuilder c = new StringBuilder("1");
+        jc.set("x", c);
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", "1Left", c.toString());
+
+        e = jexl.createScript("'Right' >> x");
+        jc = new MapContext();
+        c = new StringBuilder("1");
+        jc.set("x", c);
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", "1Right", c.toString());
+
+        e = jexl.createScript("'Right' >>> x");
+        jc = new MapContext();
+        c = new StringBuilder("1");
+        jc.set("x", c);
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", "1right", c.toString());
+    }
+
+    @Test
+    public void testPrecedence() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("40 + 2 << 1 + 1");
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 40 + 2 << 1 + 1, o);
+
+        e = JEXL.createScript("40 + (2 << 1) + 1");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", 40 + (2 << 1) + 1, o);
+
+        e = JEXL.createScript("(40 + 2) << (1 + 1)");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not expected", (40 + 2) << (1 + 1), o);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/ShiftSideEffectTest.java b/src/test/java/org/apache/commons/jexl3/ShiftSideEffectTest.java
new file mode 100644
index 0000000..c95aff7
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ShiftSideEffectTest.java
@@ -0,0 +1,321 @@
+/*
+ * 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.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import java.util.logging.Level;
+import org.apache.commons.jexl3.junit.Asserter;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+
+/**
+ * Tests for shift self assign operators (<<=,>>=,>>>=)
+ *
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ShiftSideEffectTest extends JexlTestCase {
+
+    private Asserter asserter;
+
+    public ShiftSideEffectTest() {
+        super("ShiftSideEffectTest");
+    }
+
+    @Override
+    @Before
+    public void setUp() {
+        asserter = new Asserter(JEXL);
+    }
+
+    @Test
+    public void testSideEffectVar() throws Exception {
+        Map<String,Object> context = asserter.getVariables();
+        Integer i41 = Integer.valueOf(4141);
+        Object foo = i41;
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo <<= 2", i41 << 2);
+        Assert.assertEquals(context.get("foo"), i41 << 2);
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo >>= 2", i41 >> 2);
+        Assert.assertEquals(context.get("foo"), i41 >> 2);
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo >>>= 2", i41 >>> 2);
+        Assert.assertEquals(context.get("foo"), i41 >>> 2);
+    }
+
+    @Test
+    public void testSideEffectVarDots() throws Exception {
+        Map<String,Object> context = asserter.getVariables();
+        Integer i41 = Integer.valueOf(4141);
+        Object foo = i41;
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux <<= 2", i41 << 2);
+        Assert.assertEquals(context.get("foo.bar.quux"), i41 << 2);
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux >>= 2", i41 >> 2);
+        Assert.assertEquals(context.get("foo.bar.quux"), i41 >> 2);
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux >>>= 2", i41 >>> 2);
+        Assert.assertEquals(context.get("foo.bar.quux"), i41 >>> 2);
+    }
+
+    @Test
+    public void testSideEffectArray() throws Exception {
+        Integer i41 = Integer.valueOf(4141);
+        Integer i42 = Integer.valueOf(42);
+        Integer i43 = Integer.valueOf(43);
+        String s42 = "fourty-two";
+        String s43 = "fourty-three";
+        Object[] foo = new Object[3];
+        foo[1] = i42;
+        foo[2] = i43;
+        asserter.setVariable("foo", foo);
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] <<= 2", i41 << 2);
+        Assert.assertEquals(foo[0], i41 << 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] >>= 2", i41 >> 2);
+        Assert.assertEquals(foo[0], i41 >> 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] >>>= 2", i41 >>> 2);
+        Assert.assertEquals(foo[0], i41 >>> 2);
+    }
+
+    @Test
+    public void testSideEffectDotArray() throws Exception {
+        Integer i41 = Integer.valueOf(4141);
+        Integer i42 = Integer.valueOf(42);
+        Integer i43 = Integer.valueOf(43);
+        String s42 = "fourty-two";
+        String s43 = "fourty-three";
+        Object[] foo = new Object[3];
+        foo[1] = i42;
+        foo[2] = i43;
+        asserter.setVariable("foo", foo);
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 <<= 2", i41 << 2);
+        Assert.assertEquals(foo[0], i41 << 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 >>= 2", i41 >> 2);
+        Assert.assertEquals(foo[0], i41 >> 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 >>>= 2", i41 >>> 2);
+        Assert.assertEquals(foo[0], i41 >>> 2);
+    }
+
+    @Test
+    public void testSideEffectAntishArray() throws Exception {
+        Integer i41 = Integer.valueOf(4141);
+        Integer i42 = Integer.valueOf(42);
+        Integer i43 = Integer.valueOf(43);
+        Object[] foo = new Object[3];
+        foo[1] = i42;
+        foo[2] = i43;
+        asserter.setVariable("foo.bar", foo);
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] <<= 2", i41 << 2);
+        Assert.assertEquals(foo[0], i41 << 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] >>= 2", i41 >> 2);
+        Assert.assertEquals(foo[0], i41 >> 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] >>>= 2", i41 >>> 2);
+        Assert.assertEquals(foo[0], i41 >>> 2);
+    }
+
+    public static class Foo {
+        int value;
+        Foo(int v) {
+            value = v;
+        }
+
+        @Override
+        public String toString() {
+            return Integer.toString(value);
+        }
+
+        public void setValue(long v) {
+            value = (int) v;
+        }
+        public int getValue() {
+            return value;
+        }
+
+        public void setBar(int x, long v) {
+            value = (int) v + x;
+        }
+
+        public int getBar(int x) {
+            return value + x;
+        }
+    }
+
+    @Test
+    public void testSideEffectBean() throws Exception {
+        Integer i41 = Integer.valueOf(4141);
+        Foo foo = new Foo(0);
+        asserter.setVariable("foo", foo);
+        foo.value = i41;
+        asserter.assertExpression("foo.value <<= 2", i41 << 2);
+        Assert.assertEquals(foo.value, i41 << 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.value >>= 2", i41 >> 2);
+        Assert.assertEquals(foo.value, i41 >> 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.value >>>= 2", i41 >>> 2);
+        Assert.assertEquals(foo.value, i41 >>> 2);
+    }
+
+    @Test
+    public void testSideEffectBeanContainer() throws Exception {
+        Integer i41 = Integer.valueOf(4141);
+        Foo foo = new Foo(0);
+        asserter.setVariable("foo", foo);
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] <<= 2", i41 << 2);
+        Assert.assertEquals(foo.value, i41 << 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] >>= 2", i41 >> 2);
+        Assert.assertEquals(foo.value, i41 >> 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] >>>= 2", i41 >>> 2);
+        Assert.assertEquals(foo.value, i41 >>> 2);
+    }
+
+    @Test
+    public void testArithmeticSelf() throws Exception {
+        JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new SelfArithmetic(false)).create();
+        JexlContext jc = null;
+        runSelfOverload(jexl, jc);
+        runSelfOverload(jexl, jc);
+    }
+
+    @Test
+    public void testArithmeticSelfNoCache() throws Exception {
+        JexlEngine jexl = new JexlBuilder().cache(0).arithmetic(new SelfArithmetic(false)).create();
+        JexlContext jc = null;
+        runSelfOverload(jexl, jc);
+    }
+
+    protected void runSelfOverload(JexlEngine jexl, JexlContext jc) {
+        JexlScript script;
+        Object result;
+        script = jexl.createScript("(x, y)->{ x <<= y }");
+        result = script.execute(jc, 3115, 15);
+        Assert.assertEquals(3115 << 15,  result);
+        Var v0 = new Var(3115);
+        result = script.execute(jc, v0, new Var(15));
+        Assert.assertEquals(result, v0);
+        Assert.assertEquals(3115 << 15,  v0.value);
+
+        script = jexl.createScript("(x, y)->{ x >>= y}");
+        result = script.execute(jc, 3115, 2);
+        Assert.assertEquals(3115 >> 2,  result);
+        Var v1 = new Var(3115);
+        result = script.execute(jc, v1, new Var(2));
+        Assert.assertEquals(result, v1);
+        Assert.assertEquals(3115 >> 2,  ((Var) result).value);
+
+        script = jexl.createScript("(x, y)->{ x >>>= y }");
+        result = script.execute(jc, 3115, 2);
+        Assert.assertEquals(3115 >>> 2,  result);
+        Var v2 = new Var(3115);
+        result = script.execute(jc, v2, new Var(2));
+        Assert.assertEquals(result, v2);
+        Assert.assertEquals(3115 >>> 2,  v2.value);
+    }
+
+    public static class Var {
+        int value;
+
+        Var(int v) {
+            value = v;
+        }
+
+        @Override
+        public String toString() {
+            return Integer.toString(value);
+        }
+    }
+
+    // an arithmetic that performs side effects
+    public static class SelfArithmetic extends JexlArithmetic {
+        public SelfArithmetic(boolean strict) {
+            super(strict);
+        }
+
+        public Object propertyGet(Var var, String property) {
+            return "value".equals(property)? var.value : JexlEngine.TRY_FAILED;
+        }
+
+        public Object propertySet(Var var, String property, int v) {
+            return "value".equals(property)? var.value = v : JexlEngine.TRY_FAILED;
+        }
+
+        public Object arrayGet(Var var, String property) {
+            return "VALUE".equals(property)? var.value : JexlEngine.TRY_FAILED;
+        }
+
+        public Object arraySet(Var var, String property, int v) {
+            return "VALUE".equals(property)? var.value = v : JexlEngine.TRY_FAILED;
+        }
+    
+        public Var leftShift(Var lhs, Var rhs) {
+            return new Var(lhs.value << rhs.value);
+        }
+
+        public JexlOperator selfLeftShift(Var lhs, Var rhs) {
+            lhs.value <<= rhs.value;
+            return JexlOperator.ASSIGN;
+        }
+
+        public Var rightShift(Var lhs, Var rhs) {
+            return new Var(lhs.value >> rhs.value);
+        }
+
+        public JexlOperator selfRightShift(Var lhs, Var rhs) {
+            lhs.value >>= rhs.value;
+            return JexlOperator.ASSIGN;
+        }
+
+        public Var rightShiftUnsigned(Var lhs, Var rhs) {
+            return new Var(lhs.value >>> rhs.value);
+        }
+
+        public JexlOperator selfRightShiftUnsigned(Var lhs, Var rhs) {
+            lhs.value >>>= rhs.value;
+            return JexlOperator.ASSIGN;
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/jexl3/SwitchTest.java b/src/test/java/org/apache/commons/jexl3/SwitchTest.java
new file mode 100644
index 0000000..0f67d25
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/SwitchTest.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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Assert;
+import org.junit.Test;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tests switch statement.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class SwitchTest extends JexlTestCase {
+
+    public SwitchTest() {
+        super("SwitchTest");
+    }
+
+    @Test
+    public void testSyntax() throws Exception {
+        JexlScript e = JEXL.createScript("switch (1) {case 1 : return 42; case 2: return 0}");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 42, o);
+
+        e = JEXL.createScript("switch (1) {case 1 : return 42; case 2: return 0; default: return -1}");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 42, o);
+
+        e = JEXL.createScript("switch (1) {default: return 42}");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 42, o);
+
+        e = JEXL.createScript("switch (1) {case 2: return 0; default: return 42}");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 42, o);
+
+        e = JEXL.createScript("switch (1) {default: return -1; case 2: return 0; case 1 : return 42}");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 42, o);
+
+        e = JEXL.createScript("switch (1) {default: return -1; case 2: case 1 : return 42}");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 42, o);
+    }
+
+    @Test
+    public void testBadSyntax() throws Exception {
+
+        try {
+           JEXL.createScript("switch (1) {}");
+           Assert.fail("Should have at least one case");
+        } catch (Exception ex) {
+          //
+        }
+
+        try {
+           JEXL.createScript("switch (1) {default: default: return}");
+           Assert.fail("Should not have multiple default sections");
+        } catch (Exception ex) {
+          //
+        }
+    }
+
+    @Test
+    public void testFallThrough() throws Exception {
+        JexlScript e = JEXL.createScript("x = 0; switch (1) {case 1 : x = x + 1; case 2: x = x + 2}; x");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 3, o);
+
+        e = JEXL.createScript("x = 0; switch (1) {case 1 : x = x + 1; default: x = x + 2}; x");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 3, o);
+
+        e = JEXL.createScript("x = 0; switch (99) {default: x = x + 1; case 1 : x = x + 2}; x");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 3, o);
+    }
+
+    @Test
+    public void testBreak() throws Exception {
+        JexlScript e = JEXL.createScript("x = 0; switch (1) {case 1 : x = x + 1; break; case 2: x = x + 2}; x");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 1, o);
+
+        e = JEXL.createScript("x = 0; switch (99) {default: x = x + 1; break; case 1 : x = x + 2}; x");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 1, o);
+
+        e = JEXL.createScript("x = 0; while(x < 5) switch (99) {default: x = x + 1; break; case 1 : x = x + 2}; x");
+        o = e.execute(jc);
+        Assert.assertEquals("Result is not as expected", 5, o);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java b/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java
index cf98bce..6cd7a39 100644
--- a/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java
+++ b/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java
@@ -44,10 +44,10 @@
     @Test
     public void testSynchronizer() throws Exception {
         Map<String, Object> ns = new TreeMap<String, Object>();
-        ns.put("synchronized", SynchronizedContext.class);
+        ns.put("sync", SynchronizedContext.class);
         JexlContext jc = new MapContext();
         JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
-        JexlScript js0 = jexl.createScript("synchronized:call(x, (y)->{y.size()})", "x");
+        JexlScript js0 = jexl.createScript("sync:call(x, (y)->{y.size()})", "x");
         Object size = js0.execute(jc, "foobar");
         Assert.assertEquals(6, size);
     }
diff --git a/src/test/java/org/apache/commons/jexl3/SynchronizedStatementTest.java b/src/test/java/org/apache/commons/jexl3/SynchronizedStatementTest.java
new file mode 100644
index 0000000..85fb12a
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/SynchronizedStatementTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * Tests synchronized statement.
+ * @since 3.2
+ */
+@SuppressWarnings({"UnnecessaryBoxing"})
+public class SynchronizedStatementTest extends JexlTestCase {
+
+    public SynchronizedStatementTest() {
+        super("SynchronizedStatementTest");
+    }
+
+    @Test
+    public void testStatement() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlScript js = jexl.createScript("var result = 0; var x = {1,2,3}; synchronized(x) {for (var i : x) result += i}");
+        Object size = js.execute(jc);
+        Assert.assertEquals(6, size);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/ThrowTest.java b/src/test/java/org/apache/commons/jexl3/ThrowTest.java
new file mode 100644
index 0000000..2fe27af
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/ThrowTest.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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Assert;
+import org.junit.Test;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tests throw statement.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class ThrowTest extends JexlTestCase {
+
+    public ThrowTest() {
+        super("ThrowTest");
+    }
+
+    @Test
+    public void testMessage() throws Exception {
+        JexlScript e = JEXL.createScript("throw 'Error'");
+        JexlContext jc = new MapContext();
+        try {
+            Object o = e.execute(jc);
+        } catch (Exception ex) {
+           Assert.assertEquals("Result is not last evaluated expression", "Error", ex.getMessage());
+        }
+    }
+
+    @Test
+    public void testException() throws Exception {
+        JexlScript e = JEXL.createScript("throw new ('java.lang.IllegalStateException', 'Error')");
+        JexlContext jc = new MapContext();
+        try {
+            Object o = e.execute(jc);
+        } catch (Exception ex) {
+           Assert.assertTrue(ex instanceof IllegalStateException);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/TryTest.java b/src/test/java/org/apache/commons/jexl3/TryTest.java
new file mode 100644
index 0000000..19466cd
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/TryTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Assert;
+import org.junit.Test;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tests try/catch/finally statement.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class TryTest extends JexlTestCase {
+
+    public TryTest() {
+        super("TryTest");
+    }
+
+    @Test
+    public void testLastValue() throws Exception {
+        JexlScript e = JEXL.createScript("try {x = 1} finally {x = 2}");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", 1, o);
+    }
+
+    @Test
+    public void testFinallyAlwaysCalled() throws Exception {
+        JexlScript e = JEXL.createScript("try {x = 1} finally {x = 42}");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertEquals(42, jc.get("x"));
+
+        jc = new MapContext();
+        e = JEXL.createScript("try {42/0} finally {x = 42}");
+        try {
+            o = e.execute(jc);
+        } catch (Exception ex) {
+            Assert.assertEquals(42, jc.get("x"));
+        }
+
+        jc = new MapContext();
+        e = JEXL.createScript("try {42/0} catch (var e) {} finally {x = 42}");
+        o = e.execute(jc);
+        Assert.assertEquals(42, jc.get("x"));
+    }
+
+    @Test
+    public void testCatch() throws Exception {
+        JexlScript e = JEXL.createScript("try {42/0} catch (e) {}");
+        JexlContext jc = new MapContext();
+        Object o = e.execute(jc);
+        Assert.assertTrue(jc.get("e") instanceof Exception);
+
+        e = JEXL.createScript("try {42/0} catch (var e) {x = e}");
+        jc = new MapContext();
+        o = e.execute(jc);
+        Assert.assertTrue(jc.get("x") instanceof Exception);
+
+        e = JEXL.createScript("try {return 42} catch (var e) {return 0}");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+    }
+
+    @Test
+    public void testReturn() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("try {return 1} finally {return 42}");
+        Object o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        e = JEXL.createScript("try {42/0} catch(var e) {return 42}");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        e = JEXL.createScript("try {42/0} catch(var e) {return 2} finally { return 42}");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+    }
+
+    @Test
+    public void testBreakInsideTry() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("for (var i : 42..43) try {break} finally {}; i");
+        Object o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        e = JEXL.createScript("for (var i : 42..43) try {} finally {break}; i");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        e = JEXL.createScript("for (var i : 42..43) try {break} catch(var e) {}; i");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        e = JEXL.createScript("for (var i : 42..43) try {42/0} catch(var e) {break}; i");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+    }
+
+    @Test
+    public void testContinueInsideTry() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlScript e = JEXL.createScript("var i = 0; while (true) { i+=1; try {if (i < 42) continue else break} finally {}}; i");
+        Object o = e.execute(jc);
+        Assert.assertEquals(42, o);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/TryWithResourcesTest.java b/src/test/java/org/apache/commons/jexl3/TryWithResourcesTest.java
new file mode 100644
index 0000000..114c23d
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/TryWithResourcesTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.io.StringReader;
+import org.junit.Assert;
+import org.junit.Test;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tests try-with-resources statement.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class TryWithResourcesTest extends JexlTestCase {
+
+    public TryWithResourcesTest() {
+        super("TryWithResourcesTest");
+    }
+
+    @Test
+    public void testLastValue() throws Exception {
+        JexlScript e = JEXL.createScript("try (r) {}");
+        JexlContext jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        Object o = e.execute(jc);
+        Assert.assertEquals("Result is not last evaluated expression", null, o);
+    }
+
+    @Test
+    public void testClosed() throws Exception {
+        JexlEngine jexl = new JexlBuilder().strict(true).silent(false).create();
+        JexlScript e = jexl.createScript("try (r) {}; r.read(); return 42");
+        JexlContext jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        Object o = null;
+        try {
+            o = e.execute(jc);
+            Assert.fail("should have thrown");
+        } catch (JexlException xjexl) {
+            Assert.assertNull(o);
+        }
+    }
+
+    public static class BadStream implements AutoCloseable {
+
+        public BadStream() {
+        }
+
+        public BadStream(boolean fail) throws Exception {
+            if (fail) 
+                throw new Exception("Should not be created");
+        }
+
+        @Override
+        public void close() throws Exception {
+            throw new Exception("Should be ignored");
+        }
+    }
+
+    @Test
+    public void testFinallyAlwaysCalled() throws Exception {
+
+        JexlContext jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+
+        JexlScript e = JEXL.createScript("try (r) {x = 1} finally {x = 42}");
+        Object o = e.execute(jc);
+        Assert.assertEquals(42, jc.get("x"));
+
+        jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+
+        e = JEXL.createScript("try (r) {42/0} finally {x = 42}");
+        try {
+            o = e.execute(jc);
+        } catch (Exception ex) {
+            Assert.assertEquals(42, jc.get("x"));
+        }
+
+        jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+
+        e = JEXL.createScript("try (r) {42/0} catch (var e) {} finally {x = 42}");
+        o = e.execute(jc);
+        Assert.assertEquals(42, jc.get("x"));
+
+        jc = new MapContext();
+        jc.set("r", new BadStream());
+
+        e = JEXL.createScript("try (r) {} finally {x = 42}");
+        try {
+            o = e.execute(jc);
+            Assert.fail("should have thrown");
+        } catch (Exception ex) {
+            Assert.assertNull(o);
+            Assert.assertEquals(42, jc.get("x"));
+        }
+
+        e = JEXL.createScript("try (var r = 42/0) {} finally {x = 42}");
+        try {
+            o = e.execute(jc);
+            Assert.fail("should have thrown");
+        } catch (Exception ex) {
+            Assert.assertNull(o);
+            Assert.assertEquals(42, jc.get("x"));
+        }
+    }
+
+    @Test
+    public void testCatch() throws Exception {
+        JexlContext jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+
+        JexlScript e = JEXL.createScript("try (r) {42/0} catch (e) {}");
+        jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        Object o = e.execute(jc);
+        Assert.assertTrue(jc.get("e") instanceof Exception);
+
+        e = JEXL.createScript("try (r) {42/0} catch (var e) {x = e}");
+        jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        o = e.execute(jc);
+        Assert.assertTrue(jc.get("x") instanceof Exception);
+
+        jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        e = JEXL.createScript("try (r) {return 42} catch (var e) {return 0}");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        e = JEXL.createScript("try (r) {return 42} catch(e) {}");
+        jc = new MapContext();
+        jc.set("r", new BadStream());
+        o = e.execute(jc);
+        Assert.assertNull(jc.get("e"));
+
+        e = JEXL.createScript("try (r = 42/0) {} catch (var e) {x = e}");
+        jc = new MapContext();
+        o = e.execute(jc);
+        Assert.assertTrue(jc.get("x") instanceof Exception);
+    }
+
+    @Test
+    public void testReturn() throws Exception {
+        JexlContext jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        JexlScript e = JEXL.createScript("try (r) {return 1} finally {return 42}");
+        Object o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        e = JEXL.createScript("try (r) {42/0} catch(var e) {return 42}");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        e = JEXL.createScript("try (r) {42/0} catch(var e) {return 2} finally { return 42}");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+    }
+
+    @Test
+    public void testBreakInsideTry() throws Exception {
+        JexlContext jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        JexlScript e = JEXL.createScript("for (var i : 42..43) try (r) {break} finally {}; i");
+        Object o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        e = JEXL.createScript("for (var i : 42..43) try (r) {} finally {break}; i");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        e = JEXL.createScript("for (var i : 42..43) try (r) {break} catch(var e) {}; i");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+
+        jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        e = JEXL.createScript("for (var i : 42..43) try (r) {42/0} catch(var e) {break}; i");
+        o = e.execute(jc);
+        Assert.assertEquals(42, o);
+    }
+
+    @Test
+    public void testContinueInsideTry() throws Exception {
+        JexlContext jc = new MapContext();
+        jc.set("r", new StringReader("foo"));
+        JexlScript e = JEXL.createScript("var i = 0; while (true) { i+=1; try (r) {if (i < 42) continue else break} finally {}}; i");
+        Object o = e.execute(jc);
+        Assert.assertEquals(42, o);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/VarargTest.java b/src/test/java/org/apache/commons/jexl3/VarargTest.java
new file mode 100644
index 0000000..c9e6591
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/VarargTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.apache.commons.jexl3.internal.Engine;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests function/lambda/closure features.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class VarargTest extends JexlTestCase {
+
+    public VarargTest() {
+        super("VarargTest");
+    }
+
+    @Test
+    public void testScriptArguments() throws Exception {
+        JexlEngine jexl = new Engine();
+        JexlScript s = jexl.createScript("function(x...) {x[0]}");
+        Assert.assertEquals(42, s.execute(null, 42));
+        s = jexl.createScript("function(y, x...) {x[0]}");
+        Assert.assertEquals(42, s.execute(null, 21, 42));
+        s = jexl.createScript("function(x...) {size(x)}");
+        Assert.assertEquals(0, s.execute(null));
+        Assert.assertEquals(2, s.execute(null, 21, 42));
+    }
+
+    @Test
+    public void testCurry1() throws Exception {
+        JexlEngine jexl = new Engine();
+        JexlScript script;
+        Object result;
+
+        JexlScript base = jexl.createScript("(x, y...)->{ x + y[0] + y[1]}");
+        script = base.curry(5);
+        result = script.execute(null, 15, 22);
+        Assert.assertEquals(42, result);
+        script = script.curry(15);
+        result = script.execute(null, 22);
+        Assert.assertEquals(42, result);
+        script = script.curry(22);
+        result = script.execute(null);
+        Assert.assertEquals(42, result);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/WhileTest.java b/src/test/java/org/apache/commons/jexl3/WhileTest.java
index 61909c7..69138be 100644
--- a/src/test/java/org/apache/commons/jexl3/WhileTest.java
+++ b/src/test/java/org/apache/commons/jexl3/WhileTest.java
@@ -61,4 +61,16 @@
         Assert.assertEquals("x is wrong", new Integer(10), jc.get("x"));
         Assert.assertEquals("y is wrong", new Integer(512), jc.get("y"));
     }
+
+    @Test
+    public void testWhileRemoveBroken() throws Exception {
+        try {
+            JexlScript e = JEXL.createScript("while (x < 10) remove;");
+            Assert.fail("remove is out of loop!");
+        } catch (JexlException.Parsing xparse) {
+            String str = xparse.detailedMessage();
+            Assert.assertTrue(str.contains("remove"));
+        }
+    }
+
 }
diff --git a/src/test/java/org/apache/commons/jexl3/junit/AsserterTest.java b/src/test/java/org/apache/commons/jexl3/junit/AsserterTest.java
index 4801521..4dcefdc 100644
--- a/src/test/java/org/apache/commons/jexl3/junit/AsserterTest.java
+++ b/src/test/java/org/apache/commons/jexl3/junit/AsserterTest.java
@@ -36,10 +36,10 @@
     @Test
     public void testThis() throws Exception {
         Asserter asserter = new Asserter(JEXL);
-        asserter.setVariable("this", new Foo());
-        asserter.assertExpression("this.repeat('abc')", "Repeat : abc");
+        asserter.setVariable("foo", new Foo());
+        asserter.assertExpression("foo.repeat('abc')", "Repeat : abc");
         try {
-            asserter.assertExpression("this.count", "Wrong Value");
+            asserter.assertExpression("foo.count", "Wrong Value");
             Assert.fail("This method should have thrown an assertion exception");
         }
         catch (AssertionError e) {
diff --git a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
index ed6c4e0..cbcb907 100644
--- a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
+++ b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
@@ -93,9 +93,9 @@
         Assert.assertEquals(newValue,engine.get("value"));
         Assert.assertEquals(engine.getContext(),engine.get(JexlScriptEngine.CONTEXT_KEY));
         // Check behaviour of JEXL object
-        Assert.assertEquals(engine.getContext().getReader(),engine.eval("JEXL.in"));
-        Assert.assertEquals(engine.getContext().getWriter(),engine.eval("JEXL.out"));
-        Assert.assertEquals(engine.getContext().getErrorWriter(),engine.eval("JEXL.err"));
+        Assert.assertEquals(engine.getContext().getReader(),engine.eval("JEXL['in']"));
+        Assert.assertEquals(engine.getContext().getWriter(),engine.eval("JEXL['out']"));
+        Assert.assertEquals(engine.getContext().getErrorWriter(),engine.eval("JEXL['err']"));
         Assert.assertEquals(System.class,engine.eval("JEXL.System"));
     }
 
@@ -147,9 +147,9 @@
         Assert.assertNotNull("Manager should not be null", manager);
         ScriptEngine engine = manager.getEngineByName("JEXL");
         Assert.assertNotNull("Engine should not be null (JEXL)", engine);
-        engine.eval("this.is.a.test=null");
-        Assert.assertNull(engine.get("this.is.a.test"));
-        Assert.assertEquals(Boolean.TRUE, engine.eval("empty(this.is.a.test)"));
+        engine.eval("a.test.variable=null");
+        Assert.assertNull(engine.get("a.test.variable"));
+        Assert.assertEquals(Boolean.TRUE, engine.eval("empty(a.test.variable)"));
         final Object mymap = engine.eval("testmap={ 'key1' : 'value1', 'key2' : 'value2' }");
         Assert.assertTrue(mymap instanceof Map<?, ?>);
         Assert.assertEquals(2,((Map<?, ?>)mymap).size());