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

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


The following commit(s) were added to refs/heads/JEXL-369 by this push:
     new 8549308a JEXL-369: let/const disallow any redeclaration of a variable 
within lexical scope; added let/const for parameters as well; refactored badly 
named internal fields (frame vs scope);
8549308a is described below

commit 8549308ac2704b775f4d77fa63bfacdb10f6ee9f
Author: henrib <hen...@apache.org>
AuthorDate: Sun May 8 16:34:54 2022 +0200

    JEXL-369: let/const disallow any redeclaration of a variable within lexical 
scope;
    added let/const for parameters as well;
    refactored badly named internal fields (frame vs scope);
---
 RELEASE-NOTES.txt                                  |   1 +
 .../org/apache/commons/jexl3/internal/Frame.java   |   7 -
 .../org/apache/commons/jexl3/internal/Scope.java   |  96 ++++++------
 .../apache/commons/jexl3/parser/JexlParser.java    | 167 +++++++++++----------
 .../org/apache/commons/jexl3/parser/Parser.jjt     |  30 ++--
 .../java/org/apache/commons/jexl3/LexicalTest.java | 143 +++++++++++++-----
 6 files changed, 261 insertions(+), 183 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index a1d9b7c2..9b3bb2b3 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -38,6 +38,7 @@ allow fine-tuning the scripting integration into any project.
 
 New Features in 3.3:
 ====================
+* JEXL-369:     Add 'let' and 'const' variable declarations
 * JEXL-365:     Lambda expressions
 * JEXL-363:     Allow retrieving captured variables in script
 * JEXL-360:     Add missing bitshift operators ( >>, >>>, <<)
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Frame.java 
b/src/main/java/org/apache/commons/jexl3/internal/Frame.java
index fe2b1718..6fc58d97 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Frame.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Frame.java
@@ -58,13 +58,6 @@ public final class Frame {
         return scope;
     }
 
-    boolean isLexical(int symbol) {
-        return scope != null && scope.isLexical(symbol);
-    }
-    boolean isConstant(int symbol) {
-        return scope != null && scope.isConstant(symbol);
-    }
-
     @Override
     public int hashCode() {
         return Arrays.deepHashCode(this.stack);
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 59dddb9f..77b045fc 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
@@ -18,14 +18,13 @@ package org.apache.commons.jexl3.internal;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.BitSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
 /**
  * A script scope, stores the declaration of parameters and local variables as 
symbols.
- * <p>This also acts as the functional scope and variable definition store.
+ * <p>This also acts as the functional scope and variable definition store.</p>
  * @since 3.0
  */
 public final class Scope {
@@ -67,32 +66,15 @@ public final class Scope {
      * The map of local captured variables to parent scope variables, ie 
closure.
      */
     private Map<Integer, Integer> capturedVariables = null;
-
+    /**
+     * Const symbols.
+     */
     private LexicalScope constVariables = null;
+    /**
+     * Let symbols.
+     */
     private LexicalScope lexicalVariables = null;
 
-    void addLexical(int s) {
-        if (lexicalVariables == null) {
-            lexicalVariables = new LexicalScope();
-        }
-        lexicalVariables.addSymbol(s);
-    }
-
-    public boolean isLexical(int s) {
-        return lexicalVariables != null && s >= 0 && 
lexicalVariables.hasSymbol(s);
-    }
-
-    void addConstant(int s) {
-        if (constVariables == null) {
-            constVariables = new LexicalScope();
-        }
-        constVariables.addSymbol(s);
-    }
-
-    public boolean isConstant(int s) {
-        return constVariables != null && s >= 0 && constVariables.hasSymbol(s);
-    }
-
     /**
      * The empty string array.
      */
@@ -170,17 +152,53 @@ public final class Scope {
                 register = namedVariables.size();
                 namedVariables.put(name, register);
                 capturedVariables.put(register, pr);
-                if (parent.isLexical(pr)) {
-                    this.addLexical(register);
-                    if (parent.isConstant(pr)) {
-                        this.addConstant(register);
-                    }
-                }
             }
         }
         return register;
     }
 
+    /**
+     * Marks a symbol as a lexical, declared through let or const.
+     * @param s the symbol
+     * @return true if the symbol was not already present in the lexical set
+     */
+    public boolean addLexical(int s) {
+        if (lexicalVariables == null) {
+            lexicalVariables = new LexicalScope();
+        }
+        return lexicalVariables.addSymbol(s);
+    }
+
+    /**
+     * Checks whether a symbol is declared through a let or const.
+     * @param s the symbol
+     * @return true if symbol was declared through let or const
+     */
+    public boolean isLexical(int s) {
+        return lexicalVariables != null && s >= 0 && 
lexicalVariables.hasSymbol(s);
+    }
+
+    /**
+     * Marks a symbol as a const.
+     * @param s the symbol
+     * @return true if the symbol was not already present in the constant set
+     */
+    public boolean addConstant(int s) {
+        if (constVariables == null) {
+            constVariables = new LexicalScope();
+        }
+        return constVariables.addSymbol(s);
+    }
+
+    /**
+     * Checks whether a symbol is declared through a const.
+     * @param s the symbol
+     * @return true if symbol was declared through const
+     */
+    public boolean isConstant(int s) {
+        return constVariables != null && s >= 0 && constVariables.hasSymbol(s);
+    }
+
     /**
      * Checks whether a given symbol is captured.
      * @param symbol the symbol number
@@ -240,22 +258,6 @@ public final class Scope {
                     capturedVariables.put(register, pr);
                 }
             }
-            if (lexical) {
-                addLexical(register);
-                if (constant) {
-                    addConstant(register);
-                }
-            }
-        } else {
-            // belt and suspenders
-            if (lexical) {
-                if (!isLexical(register)) {
-                    throw new IllegalStateException("cant redefine lexical 
variable");
-                }
-                if (constant && !isConstant(register)) {
-                    throw new IllegalStateException("cant redefine const 
variable");
-                }
-            }
         }
         return register;
     }
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 274fbbf6..709695f2 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -58,11 +58,11 @@ public abstract class JexlParser extends StringParser {
      * <p>Each parameter is associated to a register and is materialized
      * as an offset in the registers array used during evaluation.</p>
      */
-    protected Scope frame = null;
+    protected Scope scope = null;
     /**
      * When parsing inner functions/lambda, need to stack the scope (sic).
      */
-    protected final Deque<Scope> frames = new ArrayDeque<>();
+    protected final Deque<Scope> scopes = new ArrayDeque<>();
     /**
      * The list of pragma declarations.
      */
@@ -72,7 +72,7 @@ public abstract class JexlParser extends StringParser {
      */
     protected Set<String> namespaces = null;
     /**
-     * The number of imbricated loops.
+     * The number of nested loops.
      */
     protected int loopCount = 0;
     /**
@@ -112,6 +112,9 @@ public abstract class JexlParser extends StringParser {
          */
         int getSymbolCount();
 
+        /**
+         * @return the set of symbols identifiers declared in this unit
+         */
         LexicalScope getLexicalScope();
     }
 
@@ -122,8 +125,8 @@ public abstract class JexlParser extends StringParser {
     protected void cleanup(final JexlFeatures features) {
         info = null;
         source = null;
-        frame = null;
-        frames.clear();
+        scope = null;
+        scopes.clear();
         pragmas = null;
         namespaces = null;
         loopCounts.clear();
@@ -203,29 +206,29 @@ public abstract class JexlParser extends StringParser {
      * @return the named register map
      */
     protected Scope getFrame() {
-        return frame;
+        return scope;
     }
 
     /**
-     * Create a new local variable frame and push it as current scope.
+     * Create a new local variable scope and push it as current.
      */
-    protected void pushFrame() {
-        if (frame != null) {
-            frames.push(frame);
+    protected void pushScope() {
+        if (scope != null) {
+            scopes.push(scope);
         }
-        frame = new Scope(frame, (String[]) null);
+        scope = new Scope(scope, (String[]) null);
         loopCounts.push(loopCount);
         loopCount = 0;
     }
 
     /**
-     * Pops back to previous local variable frame.
+     * Pops back to previous local variable scope.
      */
-    protected void popFrame() {
-        if (!frames.isEmpty()) {
-            frame = frames.pop();
+    protected void popScope() {
+        if (!scopes.isEmpty()) {
+            scope = scopes.pop();
         } else {
-            frame = null;
+            scope = null;
         }
         if (!loopCounts.isEmpty()) {
             loopCount = loopCounts.pop();
@@ -296,7 +299,7 @@ public abstract class JexlParser extends StringParser {
      * @return true if a variable with that name was declared
      */
     protected boolean isVariable(String name) {
-        return frame != null && frame.getSymbol(name) != null;
+        return scope != null && scope.getSymbol(name) != null;
     }
 
     /**
@@ -306,13 +309,13 @@ public abstract class JexlParser extends StringParser {
      * @return the image
      */
     protected String checkVariable(final ASTIdentifier identifier, final 
String name) {
-        if (frame != null) {
-            final Integer symbol = frame.getSymbol(name);
+        if (scope != null) {
+            final Integer symbol = scope.getSymbol(name);
             if (symbol != null) {
-                identifier.setLexical(frame.isLexical(symbol));
-                identifier.setConstant(frame.isConstant(symbol));
+                identifier.setLexical(scope.isLexical(symbol));
+                identifier.setConstant(scope.isConstant(symbol));
                 boolean declared = true;
-                if (frame.isCapturedSymbol(symbol)) {
+                if (scope.isCapturedSymbol(symbol)) {
                     // captured are declared in all cases
                     identifier.setCaptured(true);
                 } else {
@@ -335,7 +338,7 @@ public abstract class JexlParser extends StringParser {
                     identifier.setShaded(true);
                     if (identifier.isLexical() || 
getFeatures().isLexicalShade()) {
                         // can not reuse a local as a global
-                        throw new JexlException(identifier, name + ": variable 
is not defined");
+                        throw new JexlException.Parsing(info, name + ": 
variable is not defined").clean();
                     }
                 }
             }
@@ -382,6 +385,8 @@ public abstract class JexlParser extends StringParser {
      * Declares a local variable.
      * <p> This method creates an new entry in the symbol map. </p>
      * @param variable the identifier used to declare
+     * @param lexical whether the symbol is lexical
+     * @param constant whether the symbol is constant
      * @param token      the variable name toekn
      */
     protected void declareVariable(final ASTVar variable, final Token token, 
boolean lexical, boolean constant) {
@@ -389,23 +394,62 @@ public abstract class JexlParser extends StringParser {
         if (!allowVariable(name)) {
             throwFeatureException(JexlFeatures.LOCAL_VAR, token);
         }
-        if (frame == null) {
-            frame = new Scope(null, (String[]) null);
+        if (scope == null) {
+            scope = new Scope(null, (String[]) null);
         }
-        final int symbol = frame.declareVariable(name, lexical, constant);
+        final int symbol = scope.declareVariable(name, lexical, constant);
         variable.setSymbol(symbol, name);
-        if (frame.isCapturedSymbol(symbol)) {
+        variable.setLexical(lexical);
+        variable.setConstant(constant);
+        if (scope.isCapturedSymbol(symbol)) {
             variable.setCaptured(true);
         }
-        // lexical feature error
+        // if not the first time we declare this symbol...
         if (!declareSymbol(symbol)) {
-            if (lexical || getFeatures().isLexical()) {
-                throw new JexlException(variable, name + ": variable is 
already declared");
+            if (lexical || scope.isLexical(symbol) || 
getFeatures().isLexical()) {
+                throw new JexlException.Parsing(variable.jexlInfo(), name + ": 
variable is already declared").clean();
             }
+            // not lexical, redefined nevertheless
             variable.setRedefined(true);
+        } else {
+            if (lexical) {
+                scope.addLexical(symbol);
+                if (constant) {
+                    scope.addConstant(symbol);
+                }
+            }
+        }
+    }
+
+    /**
+     * Declares a local parameter.
+     * <p> This method creates an new entry in the symbol map. </p>
+     * @param token the parameter name token
+     * @param lexical whether the parameter is lexical or not
+     * @param constant whether the parameter is constant or not
+     */
+    protected void declareParameter(final Token token, boolean lexical, 
boolean constant) {
+        final String identifier =  token.image;
+        if (!allowVariable(identifier)) {
+            throwFeatureException(JexlFeatures.LOCAL_VAR, token);
+        }
+        if (scope == null) {
+            scope = new Scope(null, (String[]) null);
+        }
+        final int symbol = scope.declareParameter(identifier);
+        // not sure how declaring a parameter could fail...
+        // lexical feature error
+        if (!block.declareSymbol(symbol)) {
+            if (lexical || getFeatures().isLexical()) {
+                final JexlInfo xinfo = info.at(token.beginLine, 
token.beginColumn);
+                throw new JexlException.Parsing(xinfo, identifier + ": 
parameter is already declared").clean();
+            }
+        } else if (lexical) {
+            scope.addLexical(symbol);
+            if (constant) {
+                scope.addConstant(symbol);
+            }
         }
-        variable.setLexical(lexical);
-        variable.setConstant(constant);
     }
 
     /**
@@ -430,7 +474,7 @@ public abstract class JexlParser extends StringParser {
         if (ns != null && key.startsWith(PRAGMA_JEXLNS)) {
             // jexl.namespace.***
             final String nsname = key.substring(PRAGMA_JEXLNS.length());
-            if (nsname != null && !nsname.isEmpty()) {
+            if (!nsname.isEmpty()) {
                 if (namespaces == null) {
                     namespaces = new HashSet<>();
                 }
@@ -466,28 +510,6 @@ public abstract class JexlParser extends StringParser {
         return false;
     }
 
-    /**
-     * Declares a local parameter.
-     * <p> This method creates an new entry in the symbol map. </p>
-     * @param token the parameter name toekn
-     */
-    protected void declareParameter(final Token token) {
-        final String identifier =  token.image;
-        if (!allowVariable(identifier)) {
-            throwFeatureException(JexlFeatures.LOCAL_VAR, token);
-        }
-        if (frame == null) {
-            frame = new Scope(null, (String[]) null);
-        }
-        final int symbol = frame.declareParameter(identifier);
-        // not sure how declaring a parameter could fail...
-        // lexical feature error
-        if (!block.declareSymbol(symbol) && getFeatures().isLexical()) {
-            final JexlInfo xinfo = info.at(token.beginLine, token.beginColumn);
-            throw new JexlException(xinfo,  identifier + ": variable is 
already declared", null);
-        }
-    }
-
     /**
      * Default implementation does nothing but is overridden by generated code.
      * @param top whether the identifier is beginning an l/r value
@@ -555,20 +577,22 @@ public abstract class JexlParser extends StringParser {
             }
             final ASTJexlScript script = (ASTJexlScript) node;
             // reaccess in case local variables have been declared
-            if (script.getScope() != frame) {
-                script.setScope(frame);
+            if (script.getScope() != scope) {
+                script.setScope(scope);
             }
-            popFrame();
+            popScope();
         } else if (ASSIGN_NODES.contains(node.getClass())) {
             final JexlNode lv = node.jjtGetChild(0);
             if (!lv.isLeftValue()) {
-                throwParsingException(JexlException.Assignment.class, null);
+                throw new JexlException.Assignment(lv.jexlInfo(), 
null).clean();
             }
             if (lv instanceof ASTIdentifier) {
                 ASTIdentifier var = (ASTIdentifier) lv;
-                int symbol = var.getSymbol();
-                if (symbol >= 0 && frame.isConstant(symbol)) {
-                    throwParsingException(JexlException.Assignment.class, 
null);
+                if (!(var instanceof ASTVar)) { // if not a declaration...
+                    int symbol = var.getSymbol();
+                    if (symbol >= 0 && scope.isConstant(symbol)) {
+                        throw new JexlException.Assignment(var.jexlInfo(), 
var.getName()).clean();
+                    }
                 }
             }
         }
@@ -613,7 +637,7 @@ public abstract class JexlParser extends StringParser {
         if (token == null) {
             token = this.getToken(0);
             if (token == null) {
-                throw new JexlException.Parsing(null, 
JexlFeatures.stringify(feature));
+                throw new JexlException.Parsing(null, 
JexlFeatures.stringify(feature)).clean();
             }
         }
         final JexlInfo xinfo = info.at(token.beginLine, token.beginColumn);
@@ -621,13 +645,11 @@ public abstract class JexlParser extends StringParser {
     }
 
     /**
-     * Creates a parsing exception.
-     * @param xclazz the class of exception
+     * Throws a parsing exception.
      * @param parsed the token to report
-     * @param <T> the parsing exception subclass
      * @throws JexlException.Parsing in all cases
      */
-    protected <T extends JexlException.Parsing> void 
throwParsingException(final Class<T> xclazz, final Token parsed) {
+    protected void throwParsingException(final Token parsed) {
         JexlInfo xinfo  = null;
         String msg = "unrecoverable state";
         JexlException.Parsing xparse = null;
@@ -638,17 +660,8 @@ public abstract class JexlParser extends StringParser {
         if (token != null) {
             xinfo = info.at(token.beginLine, token.beginColumn);
             msg = token.image;
-            if (xclazz != null) {
-                try {
-                    final Constructor<T> ctor = 
xclazz.getConstructor(JexlInfo.class, String.class);
-                    xparse = (JexlException.Parsing) ctor.newInstance(xinfo, 
msg).clean();
-                } catch (final Exception xany) {
-                    // ignore, very unlikely but then again..
-                }
-            }
         }
-        // unlikely but safe
-        throw xparse != null ? xparse : new JexlException.Parsing(xinfo, 
msg).clean();
+        throw new JexlException.Parsing(xinfo, msg).clean();
     }
 
     /**
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 baf31d30..04c22a79 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -47,7 +47,7 @@ import org.apache.commons.jexl3.internal.Scope;
 
 public final class Parser extends JexlParser
 {
-    public ASTJexlScript parse(JexlInfo jexlInfo, JexlFeatures jexlFeatures, 
String jexlSrc, Scope scope) {
+    public ASTJexlScript parse(JexlInfo jexlInfo, JexlFeatures jexlFeatures, 
String jexlSrc, Scope jexlScope) {
         JexlFeatures previous = getFeatures();
         try {
             setFeatures(jexlFeatures);
@@ -59,9 +59,9 @@ public final class Parser extends JexlParser
             info = jexlInfo != null? jexlInfo : new JexlInfo();
             source = jexlSrc;
             pragmas = null;
-            frame = scope;
+            this.scope = jexlScope;
             ReInit(jexlSrc);
-            ASTJexlScript script = jexlFeatures.supportsScript()? 
JexlScript(scope) : JexlExpression(scope);
+            ASTJexlScript script = jexlFeatures.supportsScript()? 
JexlScript(jexlScope) : JexlExpression(jexlScope);
             script.jjtSetValue(info.detach());
             script.setFeatures(jexlFeatures);
             script.setPragmas(pragmas != null
@@ -402,14 +402,14 @@ void Continue() #Continue : {
     Token t;
 }
 {
-    t=<CONTINUE> { if (loopCount == 0) { throwParsingException(null, t); } }
+    t=<CONTINUE> { if (loopCount == 0) { throwParsingException(t); } }
 }
 
 void Break() #Break : {
     Token t;
 }
 {
-    t=<BREAK> { if (loopCount == 0) { throwParsingException(null, t); } }
+    t=<BREAK> { if (loopCount == 0) { throwParsingException(t); } }
 }
 
 void ForeachStatement() : {}
@@ -852,27 +852,35 @@ void Parameter() #void :
     Token t;
 }
 {
-    t=<IDENTIFIER> { declareParameter(t); }
+    (<VAR>)? t=<IDENTIFIER> { declareParameter(t, false, false); }
+    |
+    <LET> t=<IDENTIFIER> { declareParameter(t, true, false); }
+    |
+    <CONST> t=<IDENTIFIER> { declareParameter(t, true, true); }
 }
 
 void Parameters() #void : {}
 {
-     <LPAREN> [(<VAR>)? Parameter() (<COMMA> (<VAR>)? Parameter())* ] <RPAREN>
+     <LPAREN> [Parameter() (<COMMA> Parameter())* ] <RPAREN>
 }
 
+void ParametersLookahead() #void : {}
+{
+     <LPAREN> [(<VAR>|<LET>|<CONST>)? <IDENTIFIER> (<COMMA> 
((<VAR>|<LET>|<CONST>)? <IDENTIFIER>))*] <RPAREN>
+}
 
 void LambdaLookahead() #void : {}
 {
-  <FUNCTION> Parameters()
+  <FUNCTION> ParametersLookahead()
   |
-  Parameters() <LAMBDA>
+  ParametersLookahead() <LAMBDA>
   |
-  Parameter() <LAMBDA>
+  <IDENTIFIER> <LAMBDA>
 }
 
 void Lambda() #JexlLambda :
 {
-   pushFrame();
+   pushScope();
 }
 {
   { pushUnit(jjtThis); } <FUNCTION> Parameters() ( LOOKAHEAD(3) Block() | 
Expression()) { popUnit(jjtThis); }
diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java 
b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
index 5f589b00..bb354c05 100644
--- a/src/test/java/org/apache/commons/jexl3/LexicalTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
@@ -19,6 +19,7 @@ package org.apache.commons.jexl3;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -34,16 +35,16 @@ import org.junit.Test;
 public class LexicalTest {
 
     @Test
-    public void testLexical0a() throws Exception {
+    public void testLexical0a() {
         runLexical0(false);
     }
 
     @Test
-    public void testLexical0b() throws Exception {
+    public void testLexical0b() {
         runLexical0(true);
     }
 
-    void runLexical0(final boolean feature) throws Exception {
+    void runLexical0(final boolean feature) {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(feature);
         final JexlEngine jexl = new 
JexlBuilder().strict(true).features(f).create();
@@ -123,16 +124,16 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical1a() throws Exception {
+    public void testLexical1a() {
         runLexical1(false);
     }
 
     @Test
-    public void testLexical1b() throws Exception {
+    public void testLexical1b() {
         runLexical1(true);
     }
 
-    void runLexical1(final boolean shade) throws Exception {
+    void runLexical1(final boolean shade) {
         final JexlEngine jexl = new JexlBuilder().strict(true).create();
         final JexlEvalContext ctxt = new JexlEvalContext();
         Object result;
@@ -202,7 +203,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical1() throws Exception {
+    public void testLexical1() {
         final JexlEngine jexl = new JexlBuilder().strict(true).create();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -234,16 +235,16 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical2a() throws Exception {
+    public void testLexical2a() {
         runLexical2(true);
     }
 
     @Test
-    public void testLexical2b() throws Exception {
+    public void testLexical2b() {
         runLexical2(false);
     }
 
-    protected void runLexical2(final boolean lexical) throws Exception {
+    protected void runLexical2(final boolean lexical) {
         final JexlEngine jexl = new 
JexlBuilder().strict(true).lexical(lexical).create();
         final JexlContext ctxt = new MapContext();
         final JexlScript script = jexl.createScript("{var x = 42}; {var x; 
return x; }");
@@ -256,7 +257,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical3() throws Exception {
+    public void testLexical3() {
         final String str = "var s = {}; for (var i : [1]) s.add(i); s";
         final JexlEngine jexl = new 
JexlBuilder().strict(true).lexical(true).create();
         JexlScript e = jexl.createScript(str);
@@ -270,7 +271,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical4() throws Exception {
+    public void testLexical4() {
         final JexlEngine Jexl = new 
JexlBuilder().silent(false).strict(true).lexical(true).create();
         final JxltEngine Jxlt = Jexl.createJxltEngine();
         final JexlContext ctxt = new MapContext();
@@ -296,7 +297,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical5() throws Exception {
+    public void testLexical5() {
         final JexlEngine jexl = new 
JexlBuilder().strict(true).lexical(true).create();
         final JexlContext ctxt = new DebugContext();
         JexlScript script;
@@ -312,7 +313,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical6a() throws Exception {
+    public void testLexical6a() {
         final String str = "i = 0; { var i = 32; }; i";
         final JexlEngine jexl = new 
JexlBuilder().strict(true).lexical(true).create();
         final JexlScript e = jexl.createScript(str);
@@ -322,7 +323,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical6b() throws Exception {
+    public void testLexical6b() {
         final String str = "i = 0; { var i = 32; }; i";
         final JexlEngine jexl = new 
JexlBuilder().strict(true).lexical(true).lexicalShade(true).create();
         final JexlScript e = jexl.createScript(str);
@@ -336,7 +337,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical6c() throws Exception {
+    public void testLexical6c() {
         final String str = "i = 0; for (var i : [42]) i; i";
         final JexlEngine jexl = new 
JexlBuilder().strict(true).lexical(true).lexicalShade(false).create();
         final JexlScript e = jexl.createScript(str);
@@ -346,7 +347,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical6d() throws Exception {
+    public void testLexical6d() {
         final String str = "i = 0; for (var i : [42]) i; i";
         final JexlEngine jexl = new 
JexlBuilder().strict(true).lexical(true).lexicalShade(true).create();
         final JexlScript e = jexl.createScript(str);
@@ -360,7 +361,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testPragmaOptions() throws Exception {
+    public void testPragmaOptions() {
         // same as 6d but using a pragma
         final String str = "#pragma jexl.options '+strict +lexical 
+lexicalShade -safe'\n"
                 + "i = 0; for (var i : [42]) i; i";
@@ -376,7 +377,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testPragmaNoop() throws Exception {
+    public void testPragmaNoop() {
         // unknow pragma
         final String str = "#pragma jexl.options 'no effect'\ni = -42; for 
(var i : [42]) i; i";
         final JexlEngine jexl = new 
JexlBuilder().lexical(false).strict(true).create();
@@ -388,7 +389,7 @@ public class LexicalTest {
 
 
     @Test
-    public void testScopeFrame() throws Exception {
+    public void testScopeFrame() {
         final LexicalScope scope = new LexicalScope();
         for(int i = 0; i < 128; i += 2) {
             Assert.assertTrue(scope.addSymbol(i));
@@ -401,7 +402,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testContextualOptions0() throws Exception {
+    public void testContextualOptions0() {
         final JexlFeatures f= new JexlFeatures();
         final JexlEngine jexl = new 
JexlBuilder().features(f).strict(true).create();
         final JexlEvalContext ctxt = new JexlEvalContext();
@@ -447,7 +448,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testContextualOptions1() throws Exception {
+    public void testContextualOptions1() {
         final JexlFeatures f = new JexlFeatures();
         final JexlEngine jexl = new 
JexlBuilder().features(f).strict(true).create();
         final JexlEvalContext ctxt = new TestContext();
@@ -474,7 +475,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testParameter0() throws Exception {
+    public void testParameter0() {
         final String str = "function(u) {}";
         final JexlEngine jexl = new JexlBuilder().create();
         JexlScript e = jexl.createScript(str);
@@ -484,7 +485,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testParameter1() throws Exception {
+    public void testParameter1() {
         final JexlEngine jexl = new 
JexlBuilder().strict(true).lexical(true).create();
         final JexlContext jc = new MapContext();
         final String strs = "var s = function(x) { for (var i : 1..3) {if (i > 
2) return x}}; s(42)";
@@ -494,7 +495,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testInnerAccess0() throws Exception {
+    public void testInnerAccess0() {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         final JexlEngine jexl = new 
JexlBuilder().strict(true).features(f).create();
@@ -506,14 +507,14 @@ public class LexicalTest {
     }
 
     @Test
-    public void testInnerAccess1a() throws Exception {
+    public void testInnerAccess1a() {
         final JexlEngine jexl = new 
JexlBuilder().strict(true).lexical(true).create();
         final JexlScript script = jexl.createScript("var x = 32; (()->{ 
for(var x : null) { var c = 0; {return x; }} })();");
         Assert.assertNotNull(script);
     }
 
     @Test
-    public void testInnerAccess1b() throws Exception {
+    public void testInnerAccess1b() {
         final JexlEngine jexl = new JexlBuilder().strict(true).create();
         final JexlScript script = jexl.createScript("let x = 32; (()->{ 
for(let x : null) { let c = 0; { return x; } } } )(); ");
         Assert.assertNotNull(script);
@@ -523,7 +524,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testForVariable0a() throws Exception {
+    public void testForVariable0a() {
         final JexlEngine jexl = new JexlBuilder().strict(true).create();
         try {
             final JexlScript script = jexl.createScript("for(let x : 1..3) { 
let c = 0}; return x");
@@ -534,7 +535,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testForVariable0b() throws Exception {
+    public void testForVariable0b() {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         f.lexicalShade(true);
@@ -548,7 +549,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testForVariable1a() throws Exception {
+    public void testForVariable1a() {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         f.lexicalShade(true);
@@ -563,7 +564,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testForVariable1b() throws Exception {
+    public void testForVariable1b() {
         final JexlEngine jexl = new JexlBuilder().strict(true).create();
         try {
             final JexlScript script = jexl.createScript("for(let x : 1..3) { 
let c = 0} for(let x : 1..3) { var c = 0}; return x");
@@ -575,7 +576,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testUndeclaredVariable() throws Exception {
+    public void testUndeclaredVariable() {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         f.lexicalShade(true);
@@ -590,7 +591,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testLexical6a1() throws Exception {
+    public void testLexical6a1() {
         final String str = "i = 0; { var i = 32; }; i";
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
@@ -627,7 +628,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testInternalLexicalFeatures() throws Exception {
+    public void testInternalLexicalFeatures() {
         final String str = "42";
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
@@ -649,7 +650,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testOptionsPragma() throws Exception {
+    public void testOptionsPragma() {
         try {
             JexlOptions.setDefaultFlags("+safe", "-lexical", "-lexicalShade");
             final VarContext vars = new VarContext();
@@ -683,7 +684,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testVarLoop0() throws Exception {
+    public void testVarLoop0() {
         final String src0 = "var count = 10;\n"
                 + "for (var i : 0 .. count-1) {\n"
                 + "  $out.add(i);\n"
@@ -717,7 +718,7 @@ public class LexicalTest {
         Assert.assertNotEquals(sff0, sft1);
     }
 
-    private JexlFeatures runVarLoop(final boolean flag, final String src) 
throws Exception {
+    private JexlFeatures runVarLoop(final boolean flag, final String src) {
         final VarContext vars = new VarContext();
         final JexlOptions options = vars.getEngineOptions();
         options.setLexical(true);
@@ -757,7 +758,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testAnnotation() throws Exception {
+    public void testAnnotation() {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         final JexlEngine jexl = new 
JexlBuilder().strict(true).features(f).create();
@@ -768,7 +769,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testNamed() throws Exception {
+    public void testNamed() {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         final JexlEngine jexl = new 
JexlBuilder().strict(true).features(f).create();
@@ -779,7 +780,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void tesstCaptured0() throws Exception {
+    public void tesstCaptured0() {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         final JexlEngine jexl = new 
JexlBuilder().strict(true).features(f).create();
@@ -791,7 +792,7 @@ public class LexicalTest {
     }
 
     @Test
-    public void testCaptured1() throws Exception {
+    public void testCaptured1() {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         final JexlEngine jexl = new 
JexlBuilder().strict(true).features(f).create();
@@ -815,4 +816,64 @@ public class LexicalTest {
         Assert.assertEquals(43, result);
 
     }
+
+    @Test
+    public void testLetSucceed() {
+        List<String> srcs = Arrays.asList(
+                "var x = 1; var x = 0;",
+            "{ let x = 0; } var x = 1;",
+                "var x = 0; var f = () -> { let x = 1; } f()",
+                //"let x = 0; function f() { let x = 1; }; f()" ,
+                "var x = 0; var f = (let x) -> { x = 1; } f()",
+                "var x = 0; let f = (let x) -> { x = 1; } f()",
+                "var x = 0; const f = (let x) -> { x = 1; } f()",
+            ""
+        );
+        checkParse(srcs, true);
+    }
+
+    @Test
+    public void testLetFail() {
+        List<String> srcs = Arrays.asList(
+            "let x = 0; var x = 1;",
+            "var x = 0; let x = 1;",
+            "let x = 0; let x = 1;",
+            "var x = 0; const f = (var x) -> { let x = 1; } f()",
+            "var x = 0; const f = (let x) -> { let x = 1; } f()",
+            "var x = 0; const f = (let x) -> { var x = 1; } f()",
+            ""
+        );
+        checkParse(srcs, false);
+    }
+
+    @Test
+    public void testConstFail() {
+        List<String> srcs = Arrays.asList(
+                "const x = 0;  x = 1;",
+                "const x = 0; x *= 1;",
+                "cont x = 0; var x = 1;",
+                "cont x = 0; if (true) { var x = 1;}" ,
+                "cont x = 0; if (true) { x = 1;}" ,
+                ""
+        );
+        checkParse(srcs, false);
+    }
+
+    private void checkParse(List<String> srcs, boolean expected) {
+        final JexlEngine jexl = new JexlBuilder().strict(true).create();
+        for(String src : srcs) {
+            if (!src.isEmpty()) try {
+                final JexlScript script = jexl.createScript(src);
+                if (!expected) {
+                    Assert.fail(src);
+                }
+            } catch (JexlException.Parsing xlexical) {
+                if (expected) {
+                    Assert.fail(src);
+                }
+                Assert.assertTrue(xlexical.detailedMessage().contains("x"));
+            }
+        }
+
+    }
 }

Reply via email to