You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2024/02/16 21:12:05 UTC

(commons-jexl) branch JEXL-418 created (now 01310b5e)

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

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


      at 01310b5e JEXL-418: initial drop;

This branch includes the following new commits:

     new 01310b5e JEXL-418: initial drop;

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



(commons-jexl) 01/01: JEXL-418: initial drop;

Posted by he...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 01310b5ef80288bcfe5e8269c7ccda388a5b0042
Author: Henri Biestro <hb...@cloudera.com>
AuthorDate: Fri Feb 16 22:10:10 2024 +0100

    JEXL-418: initial drop;
---
 .../apache/commons/jexl3/internal/Debugger.java    | 129 ++++------
 .../apache/commons/jexl3/internal/Interpreter.java | 262 ++++++++++++++-------
 .../commons/jexl3/internal/ScriptVisitor.java      |  93 +-------
 .../commons/jexl3/parser/ASTTryStatement.java      |  49 ++++
 .../commons/jexl3/parser/FeatureController.java    |   8 +
 .../org/apache/commons/jexl3/parser/Parser.jjt     |  36 ++-
 .../apache/commons/jexl3/parser/ParserVisitor.java |   2 +
 .../apache/commons/jexl3/TryCatchFinallyTest.java  |  58 +++++
 8 files changed, 369 insertions(+), 268 deletions(-)

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 6353a57f..4cb224c7 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
@@ -25,95 +25,7 @@ import org.apache.commons.jexl3.JexlExpression;
 import org.apache.commons.jexl3.JexlFeatures;
 import org.apache.commons.jexl3.JexlInfo;
 import org.apache.commons.jexl3.JexlScript;
-import org.apache.commons.jexl3.parser.ASTAddNode;
-import org.apache.commons.jexl3.parser.ASTAndNode;
-import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
-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.ASTArrayLiteral;
-import org.apache.commons.jexl3.parser.ASTAssignment;
-import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
-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.ASTConstructorNode;
-import org.apache.commons.jexl3.parser.ASTContinue;
-import org.apache.commons.jexl3.parser.ASTDecrementGetNode;
-import org.apache.commons.jexl3.parser.ASTDefineVars;
-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.ASTEmptyFunction;
-import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
-import org.apache.commons.jexl3.parser.ASTFalseNode;
-import org.apache.commons.jexl3.parser.ASTForeachStatement;
-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.ASTGetDecrementNode;
-import org.apache.commons.jexl3.parser.ASTGetIncrementNode;
-import org.apache.commons.jexl3.parser.ASTIdentifier;
-import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
-import org.apache.commons.jexl3.parser.ASTIfStatement;
-import org.apache.commons.jexl3.parser.ASTIncrementGetNode;
-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.ASTMapLiteral;
-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.ASTNENode;
-import org.apache.commons.jexl3.parser.ASTNEWNode;
-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.ASTNullpNode;
-import org.apache.commons.jexl3.parser.ASTNumberLiteral;
-import org.apache.commons.jexl3.parser.ASTOrNode;
-import org.apache.commons.jexl3.parser.ASTQualifiedIdentifier;
-import org.apache.commons.jexl3.parser.ASTRangeNode;
-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.ASTReturnStatement;
-import org.apache.commons.jexl3.parser.ASTSWNode;
-import org.apache.commons.jexl3.parser.ASTSetAddNode;
-import org.apache.commons.jexl3.parser.ASTSetAndNode;
-import org.apache.commons.jexl3.parser.ASTSetDivNode;
-import org.apache.commons.jexl3.parser.ASTSetLiteral;
-import org.apache.commons.jexl3.parser.ASTSetModNode;
-import org.apache.commons.jexl3.parser.ASTSetMultNode;
-import org.apache.commons.jexl3.parser.ASTSetOrNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftLeftNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightUnsignedNode;
-import org.apache.commons.jexl3.parser.ASTSetSubNode;
-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.ASTStringLiteral;
-import org.apache.commons.jexl3.parser.ASTSubNode;
-import org.apache.commons.jexl3.parser.ASTTernaryNode;
-import org.apache.commons.jexl3.parser.ASTTrueNode;
-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.ParserVisitor;
-import org.apache.commons.jexl3.parser.StringParser;
+import org.apache.commons.jexl3.parser.*;
 
 /**
  * Helps pinpoint the cause of problems in expressions that fail during evaluation.
@@ -381,6 +293,7 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
                 || child instanceof ASTBlock
                 || child instanceof ASTIfStatement
                 || child instanceof ASTForeachStatement
+                || child instanceof ASTTryStatement
                 || child instanceof ASTWhileStatement
                 || child instanceof ASTDoWhileStatement
                 || child instanceof ASTAnnotation;
@@ -785,6 +698,44 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
         return data;
     }
 
+    @Override
+    protected Object visit(final ASTTryStatement node, final Object data) {
+        final int form = node.getTryForm();
+        builder.append("try");
+        int nc = 0;
+        // try with-resource named
+        if ((form & 1) != 0) {
+            builder.append('(');
+           accept(node.jjtGetChild(nc++), data);
+           if ((form & 2) != 0) {
+               builder.append(" = ");
+               accept(node.jjtGetChild(nc++), data);
+           }
+           builder.append(')');
+        } else if ((form & 2) != 0) {
+            // try with-resource un-named
+            builder.append('(');
+            accept(node.jjtGetChild(nc++), data);
+            builder.append(')');
+        }
+        // try-body
+        if ((form & 4) != 0) {
+            accept(node.jjtGetChild(nc++), data);
+        }
+        // catch-body
+        if ((form & 8) != 0) {
+            builder.append(" catch(");
+            accept(node.jjtGetChild(nc++), data);
+            builder.append(')');
+        }
+        // finally-body
+        if ((form & 16) != 0) {
+            builder.append(" finally ");
+            accept(node.jjtGetChild(nc++), data);
+        }
+        return data;
+    }
+
     @Override
     protected Object visit(final ASTGENode node, final Object data) {
         return infixChildren(node, " >= ", false, data);
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 4c690273..c2553001 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -33,94 +33,7 @@ import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.JxltEngine;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
-import org.apache.commons.jexl3.parser.ASTAddNode;
-import org.apache.commons.jexl3.parser.ASTAndNode;
-import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
-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.ASTArrayLiteral;
-import org.apache.commons.jexl3.parser.ASTAssignment;
-import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
-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.ASTConstructorNode;
-import org.apache.commons.jexl3.parser.ASTContinue;
-import org.apache.commons.jexl3.parser.ASTDecrementGetNode;
-import org.apache.commons.jexl3.parser.ASTDefineVars;
-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.ASTEmptyFunction;
-import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
-import org.apache.commons.jexl3.parser.ASTFalseNode;
-import org.apache.commons.jexl3.parser.ASTForeachStatement;
-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.ASTGetDecrementNode;
-import org.apache.commons.jexl3.parser.ASTGetIncrementNode;
-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.ASTIfStatement;
-import org.apache.commons.jexl3.parser.ASTIncrementGetNode;
-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.ASTMapLiteral;
-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.ASTNENode;
-import org.apache.commons.jexl3.parser.ASTNEWNode;
-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.ASTNullpNode;
-import org.apache.commons.jexl3.parser.ASTNumberLiteral;
-import org.apache.commons.jexl3.parser.ASTOrNode;
-import org.apache.commons.jexl3.parser.ASTQualifiedIdentifier;
-import org.apache.commons.jexl3.parser.ASTRangeNode;
-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.ASTReturnStatement;
-import org.apache.commons.jexl3.parser.ASTSWNode;
-import org.apache.commons.jexl3.parser.ASTSetAddNode;
-import org.apache.commons.jexl3.parser.ASTSetAndNode;
-import org.apache.commons.jexl3.parser.ASTSetDivNode;
-import org.apache.commons.jexl3.parser.ASTSetLiteral;
-import org.apache.commons.jexl3.parser.ASTSetModNode;
-import org.apache.commons.jexl3.parser.ASTSetMultNode;
-import org.apache.commons.jexl3.parser.ASTSetOrNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftLeftNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightUnsignedNode;
-import org.apache.commons.jexl3.parser.ASTSetSubNode;
-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.ASTStringLiteral;
-import org.apache.commons.jexl3.parser.ASTSubNode;
-import org.apache.commons.jexl3.parser.ASTTernaryNode;
-import org.apache.commons.jexl3.parser.ASTTrueNode;
-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.*;
 
 /**
  * An interpreter of JEXL syntax.
@@ -700,6 +613,179 @@ public class Interpreter extends InterpreterBase {
         throw new JexlException.Break(node);
     }
 
+    @Override
+    protected Object visit(final ASTTryStatement node, final Object data) {
+        int form = node.getTryForm();
+        int nc = 0;
+        JexlNode tryVar = (form & 1) != 0 ? node.jjtGetChild(nc++) : null;
+        JexlNode tryExpression = (form & 2) != 0 ? node.jjtGetChild(nc++) : null;
+        JexlNode tryBody = (form & 4) != 0 ? node.jjtGetChild(nc++) : null;
+        JexlNode catchVar = (form & 8) != 0 ? node.jjtGetChild(nc++) : null;
+        JexlNode catchBody = (form & 8) != 0 ? node.jjtGetChild(nc++) : null;
+        JexlNode finallyBody = (form & 16) != 0 ? node.jjtGetChild(nc) : null;
+        JexlException caught = null;
+        JexlException flowControl = null;
+        Object result = null;
+        try {
+            // evaluate the try
+            result = evalTry(tryVar, tryExpression, tryBody, data);
+        } catch(JexlException.Return | JexlException.Cancel xflow) {
+            // flow control exceptions do not trigger the catch clause
+            flowControl = xflow;
+        } catch(JexlException xany) {
+            caught = xany;
+            result = null;
+        }
+        // if we caught an exception and have a catch body, evaluate it
+        JexlException thrownByCatch = null;
+        if (caught != null && catchBody != null) {
+            try {
+                // evaluate the catch
+                evalCatch(catchVar, catchBody, caught,  data);
+            } catch(JexlException.Break | JexlException.Continue |
+                    JexlException.Return | JexlException.Cancel alterFlow) {
+                flowControl = alterFlow;
+            } catch (JexlException exception) {
+                // catching an exception thrown from catch body; can be a (re)throw
+                thrownByCatch = exception;
+            }
+        }
+        // if we have a 'finally' block, no matter what, evaluate it: its control flow will
+        // take precedence over what the 'catch' block might have thrown.
+        if (finallyBody != null) {
+            try {
+                finallyBody.jjtAccept(this, data);
+            } catch (JexlException.Break | JexlException.Continue  | JexlException.Return flowException) {
+                // potentially swallow previous, even return but not cancel
+                if (!(flowControl instanceof JexlException.Cancel)) {
+                    flowControl = flowException;
+                }
+            } catch (JexlException.Cancel cancelException) {
+                // cancel swallows everything
+                flowControl = cancelException;
+            } catch (JexlException exception) {
+                // catching an exception thrown in finally body
+                if (jexl.logger.isDebugEnabled()) {
+                    jexl.logger.debug("exception thrown in finally", thrownByCatch);
+                }
+                // swallow the caught one
+                thrownByCatch = exception;
+            }
+        }
+        if (flowControl != null) {
+            if (thrownByCatch != null && jexl.logger.isDebugEnabled()) {
+                jexl.logger.debug("finally swallowed exception thrown by catch", thrownByCatch);
+            }
+            throw flowControl;
+        }
+        if (thrownByCatch != null) {
+            throw thrownByCatch;
+        }
+        return result;
+    }
+
+    /**
+     * Evaluate the try/try-with resource a try/catch/finally.
+     *
+     * @param tryVar the try-with-resource variable (if any)
+     * @param tryExpression the try-with-resource expression
+     * @param tryBody the try body
+     * @param data the data
+     * @return the result of body evaluation
+     */
+    private Object evalTry(JexlNode tryVar, JexlNode tryExpression, JexlNode tryBody, final Object data) {
+        boolean lexical = false;
+        try {
+            final ASTIdentifier tryVariable;
+            final int symbol;
+            Object tryResult = null;
+            /* Capture try variable if any. */
+            if (tryVar instanceof ASTReference) {
+                tryVariable = (ASTIdentifier) tryVar.jjtGetChild(0);
+                symbol = tryVariable.getSymbol();
+                lexical = tryVariable.isLexical() || options.isLexical();
+                if (lexical) {
+                    // create lexical frame
+                    final LexicalFrame locals = new LexicalFrame(frame, block);
+                    // it may be a local previously declared
+                    final boolean trySymbol = symbol >= 0 && tryVariable instanceof ASTVar;
+                    if (trySymbol && !defineVariable((ASTVar) tryVariable, locals)) {
+                        return redefinedVariable(tryVar.jjtGetParent(), tryVariable.getName());
+                    }
+                    block = locals;
+                }
+            } else {
+                tryVariable = null;
+                symbol = -1;
+                lexical = false;
+            }
+            // evaluate try expression if any
+            if (tryExpression != null) {
+                tryResult = tryExpression.jjtAccept(this, data);
+                // assign try variable if declared
+                if (tryVariable != null) {
+                    if (symbol < 0) {
+                        setContextVariable(tryVar.jjtGetParent(), tryVariable.getName(), tryResult);
+                    } else {
+                        frame.set(symbol, tryResult);
+                    }
+                }
+            }
+            // evaluate the body
+            return tryBody.jjtAccept(this, data);
+        } finally {
+            // restore lexical frame
+            if (lexical) {
+                block = block.pop();
+            }
+        }
+    }
+
+    /**
+     * Evaluate the catch a try/catch/finally.
+     *
+     * @param catchVar the variable containing the exception
+     * @param catchBody the body
+     * @param caught the caught exception
+     * @param data the data
+     * @return the result of body evaluation
+     */
+    private Object evalCatch(JexlNode catchVar, JexlNode catchBody, JexlException caught, Object data) {
+        final int symbol;
+        final ASTIdentifier catchVariable;
+        boolean lexical = false;
+        try {
+            // declare catch variable and assign with caught exception
+            if (catchVar instanceof ASTReference) {
+                catchVariable = (ASTIdentifier) catchVar.jjtGetChild(0);
+                symbol = catchVariable.getSymbol();
+                lexical = catchVariable.isLexical() || options.isLexical();
+                if (lexical) {
+                    // create lexical frame
+                    final LexicalFrame locals = new LexicalFrame(frame, block);
+                    // it may be a local previously declared
+                    final boolean trySymbol = symbol >= 0 && catchVariable instanceof ASTVar;
+                    if (trySymbol && !defineVariable((ASTVar) catchVariable, locals)) {
+                        return redefinedVariable(catchVar.jjtGetParent(), catchVariable.getName());
+                    }
+                    block = locals;
+                }
+                if (symbol < 0) {
+                    setContextVariable(catchVar.jjtGetParent(), catchVariable.getName(), caught);
+                } else {
+                    frame.set(symbol, caught);
+                }
+            }
+            // evaluate body
+            return catchBody.jjtAccept(this, data);
+        } finally {
+            // restore lexical frame
+            if (lexical) {
+                block = block.pop();
+            }
+        }
+    }
+
     @Override
     protected Object visit(final ASTForeachStatement node, final Object data) {
         return node.getLoopForm() == 0 ? forIterator(node, data) : forLoop(node, data);
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 f9f225a3..6bd0cf77 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
@@ -18,93 +18,7 @@ package org.apache.commons.jexl3.internal;
 
 import org.apache.commons.jexl3.JexlExpression;
 import org.apache.commons.jexl3.JexlScript;
-import org.apache.commons.jexl3.parser.ASTAddNode;
-import org.apache.commons.jexl3.parser.ASTAndNode;
-import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
-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.ASTArrayLiteral;
-import org.apache.commons.jexl3.parser.ASTAssignment;
-import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
-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.ASTConstructorNode;
-import org.apache.commons.jexl3.parser.ASTContinue;
-import org.apache.commons.jexl3.parser.ASTDecrementGetNode;
-import org.apache.commons.jexl3.parser.ASTDefineVars;
-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.ASTEmptyFunction;
-import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
-import org.apache.commons.jexl3.parser.ASTFalseNode;
-import org.apache.commons.jexl3.parser.ASTForeachStatement;
-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.ASTGetDecrementNode;
-import org.apache.commons.jexl3.parser.ASTGetIncrementNode;
-import org.apache.commons.jexl3.parser.ASTIdentifier;
-import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
-import org.apache.commons.jexl3.parser.ASTIfStatement;
-import org.apache.commons.jexl3.parser.ASTIncrementGetNode;
-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.ASTMapLiteral;
-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.ASTNENode;
-import org.apache.commons.jexl3.parser.ASTNEWNode;
-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.ASTNullpNode;
-import org.apache.commons.jexl3.parser.ASTNumberLiteral;
-import org.apache.commons.jexl3.parser.ASTOrNode;
-import org.apache.commons.jexl3.parser.ASTQualifiedIdentifier;
-import org.apache.commons.jexl3.parser.ASTRangeNode;
-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.ASTReturnStatement;
-import org.apache.commons.jexl3.parser.ASTSWNode;
-import org.apache.commons.jexl3.parser.ASTSetAddNode;
-import org.apache.commons.jexl3.parser.ASTSetAndNode;
-import org.apache.commons.jexl3.parser.ASTSetDivNode;
-import org.apache.commons.jexl3.parser.ASTSetLiteral;
-import org.apache.commons.jexl3.parser.ASTSetModNode;
-import org.apache.commons.jexl3.parser.ASTSetMultNode;
-import org.apache.commons.jexl3.parser.ASTSetOrNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftLeftNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightUnsignedNode;
-import org.apache.commons.jexl3.parser.ASTSetSubNode;
-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.ASTStringLiteral;
-import org.apache.commons.jexl3.parser.ASTSubNode;
-import org.apache.commons.jexl3.parser.ASTTernaryNode;
-import org.apache.commons.jexl3.parser.ASTTrueNode;
-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.ParserVisitor;
+import org.apache.commons.jexl3.parser.*;
 
 /**
  * Concrete visitor base, used for feature and operator controllers.
@@ -187,6 +101,11 @@ public class ScriptVisitor extends ParserVisitor {
         return visitNode(node, data);
     }
 
+    @Override
+    protected Object visit(final ASTTryStatement node, final Object data) {
+        return visitNode(node, data);
+    }
+
     @Override
     protected Object visit(final ASTReturnStatement node, final Object data) {
         return visitNode(node, data);
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTTryStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTTryStatement.java
new file mode 100644
index 00000000..dde63c65
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTTryStatement.java
@@ -0,0 +1,49 @@
+/*
+ * 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 try/catch/finally statement.
+ */
+public class ASTTryStatement extends JexlLexicalNode {
+    private static final long serialVersionUID = 1L;
+    /** try {}=0; try(expr()) &=2; try(let x = expr()); &=1 . */
+    /** catch() &= 4, finally &= 8. */
+    private int tryForm;
+
+    void setTryForm(final int form) {
+        tryForm = form;
+    }
+
+    public int getTryForm() {
+        return tryForm;
+    }
+
+    public ASTTryStatement(final int id) {
+        super(id);
+    }
+
+    public ASTTryStatement(final Parser p, final int id) {
+        super(p, id);
+    }
+
+    @Override
+    public Object jjtAccept(final ParserVisitor visitor, final 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 73d1c3b1..2dfa2b0a 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/FeatureController.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/FeatureController.java
@@ -131,6 +131,14 @@ public class FeatureController extends ScriptVisitor {
         return data;
     }
 
+    @Override
+    protected Object visit(final ASTTryStatement node, final Object data) {
+//        if (!features.supportsLoops()) {
+//            throwFeatureException(JexlFeatures.LOOP, node);
+//        }
+        return data;
+    }
+
     @Override
     protected Object visit(final ASTConstructorNode node, final Object data) {
         if (!features.supportsNewInstance()) {
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 527f1e30..7dbcebaa 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -29,8 +29,8 @@ options
    TRACK_TOKENS=true;
    CACHE_TOKENS=true;
    ERROR_REPORTING=false;
-  // DEBUG_PARSER=true;
-   //DEBUG_TOKEN_MANAGER=true;
+//   DEBUG_PARSER=true;
+//   DEBUG_TOKEN_MANAGER=true;
 }
 
 PARSER_BEGIN(Parser)
@@ -219,6 +219,11 @@ TOKEN_MGR_DECLS : {
     | < NULL : "null" >
     | < TRUE : "true" >
     | < FALSE : "false" >
+    | < THROW : "throw" >
+    | < TRY : "try" >
+    | < CATCH : "catch" >
+    | < FINALLY : "finally" >
+
 }
 
 /***************************************
@@ -379,6 +384,8 @@ void StatementNoVar() #void : {}
     | LOOKAHEAD(<RETURN>) ReturnStatement()
     | LOOKAHEAD(<CONTINUE>) Continue()
     | LOOKAHEAD(<BREAK>) Break()
+    | LOOKAHEAD(<THROW>) ThrowStatement()
+    | LOOKAHEAD(<TRY>) TryStatement()
     | LOOKAHEAD(Expression()) ExpressionStatement()
     | Block()
     | LOOKAHEAD(<VAR>, {!getFeatures().isLexical()} ) Var()
@@ -407,6 +414,22 @@ void IfStatement() : {}
     ( LOOKAHEAD(1) <ELSE>  (LOOKAHEAD(1) Block() | StatementNoVar()) )?
 }
 
+void TryStatement() : {
+ int tryForm = 0;}
+{
+    {
+        pushUnit(jjtThis);
+    }
+    <TRY> (<LPAREN> (LOOKAHEAD(3) InlineVar() <assign> { tryForm |= 1; })? Expression() <RPAREN> { tryForm |= 2; })?
+     ((LOOKAHEAD(1) Block() | StatementNoVar())  { tryForm |= 4; })
+     (LOOKAHEAD(1) <CATCH> <LPAREN> InlineVar() <RPAREN>  (LOOKAHEAD(1) Block() | StatementNoVar()) { tryForm |= 8; })?
+     (LOOKAHEAD(1) <FINALLY>  (LOOKAHEAD(1) Block() | StatementNoVar()) { tryForm |= 16; })?
+    {
+        jjtThis.setTryForm(tryForm);
+        popUnit(jjtThis);
+    }
+}
+
 void WhileStatement() : {}
 {
     <WHILE> <LPAREN> Expression() <RPAREN>  { loopCount += 1; }  (LOOKAHEAD(1) Block() | StatementNoVar()) { loopCount -= 1; }
@@ -422,6 +445,11 @@ void ReturnStatement() : {}
     <RETURN> (LOOKAHEAD(2) ExpressionStatement() )?
 }
 
+void ThrowStatement() : {}
+{
+    <THROW> (LOOKAHEAD(2) ExpressionStatement() )?
+}
+
 void Continue() #Continue : {
     Token t;
 }
@@ -445,7 +473,7 @@ void ForeachStatement() : {
     }
     <FOR> <LPAREN>
     (
-        LOOKAHEAD(3) ForEachVar() <COLON> Expression() { loopForm = 0; }
+        LOOKAHEAD(3) InlineVar() <COLON> Expression() { loopForm = 0; }
     |
         ((LOOKAHEAD(1) Var() | Expression()) { loopForm = 1; })? <SEMICOL>
         (Expression() { loopForm |= 2; })? <SEMICOL>
@@ -463,7 +491,7 @@ void ForeachStatement() : {
     }
 }
 
-void ForEachVar() #Reference : {}
+void InlineVar() #Reference : {}
 {
     <VAR> DeclareVar(false, false)
 |
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 66479e23..1851180a 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
@@ -56,6 +56,8 @@ public abstract class ParserVisitor {
 
     protected abstract Object visit(ASTForeachStatement node, Object data);
 
+    protected abstract Object visit(ASTTryStatement node, Object data);
+
     protected abstract Object visit(ASTReturnStatement node, Object data);
 
     protected abstract Object visit(ASTAssignment node, Object data);
diff --git a/src/test/java/org/apache/commons/jexl3/TryCatchFinallyTest.java b/src/test/java/org/apache/commons/jexl3/TryCatchFinallyTest.java
new file mode 100644
index 00000000..38535f64
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/TryCatchFinallyTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+public class TryCatchFinallyTest extends JexlTestCase {
+  public TryCatchFinallyTest() {
+    super(TryCatchFinallyTest.class.getSimpleName());
+  }
+
+  @Test
+  public void testForm0x14() {
+    String src = "try { 42; } finally { 169; }";
+    JexlScript script = JEXL.createScript(src);
+    Assert.assertNotNull(script);
+    Object result = script.execute(null);
+    Assert.assertEquals(42, result);
+  }
+  @Test
+  public void testForm0x17() {
+    String src = "try(let x = 42) { x; } finally { 169; }";
+    JexlScript script = JEXL.createScript(src);
+    Assert.assertNotNull(script);
+    Object result = script.execute(null);
+    Assert.assertEquals(42, result);
+  }
+
+
+  @Test
+  public void testEdgeTry() {
+    int i = 0;
+    while(i++ < 5) {
+      System.out.println("i: " + i);
+      try {
+        throw new JexlException.Continue(null);
+      } finally {
+        continue;
+      }
+    }
+    System.out.println("iii: " + i);
+  }
+}