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 2022/05/09 15:20:26 UTC

[commons-jexl] 02/03: JEXL-367: function f() {} creates a const f variable; - checked the thin vs fat arrow feature;

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

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

commit 840b9d6cc41d00046625c3bb2227f8ddc628d901
Author: henrib <he...@apache.org>
AuthorDate: Mon May 9 17:06:13 2022 +0200

    JEXL-367: function f() {} creates a const f variable;
    - checked the thin vs fat arrow feature;
---
 .../org/apache/commons/jexl3/JexlFeatures.java     |  4 +-
 .../apache/commons/jexl3/internal/Interpreter.java |  5 ++-
 .../apache/commons/jexl3/parser/JexlParser.java    | 11 +++--
 .../org/apache/commons/jexl3/parser/Parser.jjt     | 27 +++++++------
 .../org/apache/commons/jexl3/JexlTestCase.java     | 10 +++--
 .../java/org/apache/commons/jexl3/LambdaTest.java  | 47 ++++++++++++++++++++++
 6 files changed, 81 insertions(+), 23 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index 06ed52a5..124468fb 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -442,7 +442,7 @@ public final class JexlFeatures {
     /**
      * Sets whether thin-arrow lambda syntax is enabled.
      * <p>
-     * When disabled, parsing a script/expression using syntactic thin-arrow (->)
+     * When disabled, parsing a script/expression using syntactic thin-arrow (-&lt;)
      * will throw a parsing exception.
      * @param flag true to enable, false to disable
      * @return this features instance
@@ -462,7 +462,7 @@ public final class JexlFeatures {
     /**
      * Sets whether fat-arrow lambda syntax is enabled.
      * <p>
-     * When disabled, parsing a script/expression using syntactic fat-arrow (=>)
+     * When disabled, parsing a script/expression using syntactic fat-arrow (=&lt;)
      * will throw a parsing exception.
      * @param flag true to enable, false to disable
      * @return this features instance
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 8c3e7351..98b01ea4 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -989,7 +989,10 @@ public class Interpreter extends InterpreterBase {
             if (child0 instanceof ASTVar) {
                 ASTVar var = (ASTVar) child0;
                 this.visit(var, data);
-                frame.set(var.getSymbol(), closure);
+                int symbol = var.getSymbol();
+                frame.set(symbol, closure);
+                // make the closure accessible to itself, ie capture the currently set variable after frame creation
+                closure.setCaptured(symbol, closure);
             }
             return closure;
         }
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 990c4c57..615de42b 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -205,7 +205,7 @@ public abstract class JexlParser extends StringParser {
      * regain access after parsing to known which / how-many registers are needed. </p>
      * @return the named register map
      */
-    protected Scope getFrame() {
+    protected Scope getScope() {
         return scope;
     }
 
@@ -388,7 +388,11 @@ public abstract class JexlParser extends StringParser {
      */
     protected void declareFunction(final ASTVar variable, final Token token, Scope scope) {
         final String name = token.image;
-        final int symbol = scope.declareVariable(name);
+        // function foo() ... <=> const foo = ()->...
+        if (scope == null) {
+            scope = new Scope(null);
+        }
+        final int symbol = scope.declareVariable(name, true, true);
         variable.setSymbol(symbol, name);
         if (scope.isCapturedSymbol(symbol)) {
             variable.setCaptured(true);
@@ -416,7 +420,7 @@ public abstract class JexlParser extends StringParser {
             throwFeatureException(JexlFeatures.LOCAL_VAR, token);
         }
         if (scope == null) {
-            scope = new Scope(null, (String[]) null);
+            scope = new Scope(null);
         }
         final int symbol = scope.declareVariable(name, lexical, constant);
         variable.setSymbol(symbol, name);
@@ -601,7 +605,6 @@ public abstract class JexlParser extends StringParser {
             if (script.getScope() != scope) {
                 script.setScope(scope);
             }
-            popScope();
         } else if (ASSIGN_NODES.contains(node.getClass())) {
             final JexlNode lv = node.jjtGetChild(0);
             if (!lv.isLeftValue()) {
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 b25f529d..bfc476e6 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -358,18 +358,19 @@ void Statement() #void : {}
 void StatementNoVar() #void : {}
 {
     <SEMICOL>
+    | LOOKAHEAD(<PRAGMA>) Pragma()
     | LOOKAHEAD(<ANNOTATION>) AnnotatedStatement()
+    | LOOKAHEAD(<IF>) IfStatement()
+    | LOOKAHEAD(<FOR>) ForeachStatement()
+    | LOOKAHEAD(<WHILE>) WhileStatement()
+    | LOOKAHEAD(<DO>) DoWhileStatement()
+    | LOOKAHEAD(<RETURN>) ReturnStatement()
+    | LOOKAHEAD(<CONTINUE>) Continue()
+    | LOOKAHEAD(<BREAK>) Break()
+    | LOOKAHEAD(<FUNCTION> <IDENTIFIER> <LPAREN>) Lambda()
     | LOOKAHEAD(Expression()) ExpressionStatement()
     | Block()
-    | IfStatement()
-    | ForeachStatement()
-    | WhileStatement()
-    | DoWhileStatement()
-    | ReturnStatement()
-    | Continue()
-    | Break()
     | LOOKAHEAD(<VAR>, {!getFeatures().isLexical()} ) Var()
-    | Pragma()
 }
 
 void Block() #Block : {}
@@ -897,15 +898,15 @@ void Lambda() #JexlLambda :
 {
    Token arrow;
    Token name;
-   Scope scope = getFrame();
-   pushFrame();
+   Scope scope = getScope();
+   pushScope();
 }
 {
-  { pushUnit(jjtThis); } <FUNCTION> (LOOKAHEAD(<IDENTIFIER>) DeclareFunction(scope))? Parameters() ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); }
+  { pushUnit(jjtThis); } <FUNCTION> (LOOKAHEAD(<IDENTIFIER>) DeclareFunction(scope))? Parameters() ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); popScope(); }
   |
-  { pushUnit(jjtThis); } Parameters() (arrow=<LAMBDA> | arrow=<FATARROW>) ( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); }
+  { pushUnit(jjtThis); } Parameters() (arrow=<LAMBDA> | arrow=<FATARROW>) ( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); popScope(); }
   |
-  { pushUnit(jjtThis); } Parameter() (arrow=<LAMBDA> | arrow=<FATARROW>)( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); }
+  { pushUnit(jjtThis); } Parameter() (arrow=<LAMBDA> | arrow=<FATARROW>)( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); popScope(); }
 }
 
 
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
index 5c63c58d..a8249556 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
@@ -20,11 +20,8 @@ package org.apache.commons.jexl3;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 
-
-import org.apache.commons.jexl3.internal.Interpreter;
 import org.apache.commons.jexl3.internal.OptionsContext;
 import org.apache.commons.jexl3.internal.Util;
-import org.apache.commons.jexl3.internal.introspection.Permissions;
 import org.apache.commons.jexl3.internal.introspection.Uberspect;
 import org.apache.commons.jexl3.introspection.JexlPermissions;
 import org.junit.After;
@@ -70,6 +67,9 @@ public class JexlTestCase {
     static JexlEngine createEngine() {
         return new JexlBuilder().create();
     }
+    static JexlEngine createEngine(JexlFeatures features) {
+        return new JexlBuilder().features(features).create();
+    }
 
     // define mode pro50
     static final JexlOptions MODE_PRO50 = new JexlOptions();
@@ -121,6 +121,10 @@ public class JexlTestCase {
         return lhsw.equals(rhsw);
     }
 
+    public String simpleWhitespace(String arg) {
+        return arg.trim().replaceAll("\\s+", " ");
+    }
+
     /**
      * A very secure singleton.
      */
diff --git a/src/test/java/org/apache/commons/jexl3/LambdaTest.java b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
index 31a836ae..ea32a7e9 100644
--- a/src/test/java/org/apache/commons/jexl3/LambdaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
@@ -400,6 +400,53 @@ public class LambdaTest extends JexlTestCase {
         Assert.assertEquals(1120, result);
     }
 
+    @Test public void testFatFact0() {
+        JexlFeatures features = new JexlFeatures();
+        features.fatArrow(true);
+        String src = "function (a) { const fact = (x)=>{ x <= 1? 1 : x * fact(x - 1) }; fact(a) }";
+        JexlEngine jexl = createEngine(features);
+        JexlScript script = jexl.createScript(src);
+        Object result = script.execute(null, 6);
+        Assert.assertEquals(720, result);
+    }
+
+    @Test public void testFatFact1() {
+        String src = "function (a) { const fact = (x)=> x <= 1? 1 : x * fact(x - 1) ; fact(a) }";
+        JexlFeatures features = new JexlFeatures();
+        features.fatArrow(true);
+        JexlEngine jexl = createEngine(features);
+        JexlScript script = jexl.createScript(src);
+        Object result = script.execute(null, 6);
+        Assert.assertEquals(720, result);
+        features.fatArrow(false);
+        jexl = createEngine(features);
+        try {
+            script = jexl.createScript(src);
+        } catch(JexlException.Feature xfeature) {
+            Assert.assertTrue(xfeature.getMessage().contains("fat-arrow"));
+        }
+    }
+
+    @Test public void testNamedFunc() {
+        String src = "(a)->{ function fact(x) { x <= 1? 1 : x * fact(x - 1); } fact(a); }";
+        JexlEngine jexl = createEngine();
+        JexlScript script = jexl.createScript(src);
+        Object result = script.execute(null, 6);
+        Assert.assertEquals(720, result);
+        String parsed = simpleWhitespace(script.getParsedText());
+        Assert.assertEquals(simpleWhitespace(src), parsed);
+    }
+
+    @Test public void testNamedFuncIsConst() {
+        String src = "function foo(x) { x + x }; var foo ='nonononon'";
+        JexlEngine jexl = createEngine();
+        try {
+            JexlScript script = jexl.createScript(src);
+        } catch(JexlException.Parsing xparse) {
+            Assert.assertTrue(xparse.getMessage().contains("foo"));
+        }
+    }
+
     @Test public void testLambdaExpr0() {
         String src = "(x, y) -> x + y";
         JexlEngine jexl = createEngine();