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")); + } + } + + } }