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 2019/08/21 22:04:59 UTC

[commons-jexl] branch master updated: JEXL-311: parse verbatim expressions with correct scope, added tests using lambdas Task #JEXL-311 - Jxlt template scripts fail using verbatim expressions embedded in lambdas

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 9064d42  JEXL-311: parse verbatim expressions with correct scope, added tests using lambdas Task #JEXL-311 - Jxlt template scripts fail using verbatim expressions embedded in lambdas
9064d42 is described below

commit 9064d425b5e1d3e7cea1f5a6deeeb23effb66c2a
Author: henrib <he...@apache.org>
AuthorDate: Thu Aug 22 00:04:11 2019 +0200

    JEXL-311: parse verbatim expressions with correct scope, added tests using lambdas
    Task #JEXL-311 - Jxlt template scripts fail using verbatim expressions embedded in lambdas
---
 RELEASE-NOTES.txt                                  |  1 +
 .../org/apache/commons/jexl3/internal/Closure.java |  5 +-
 .../commons/jexl3/internal/TemplateEngine.java     |  4 +-
 .../jexl3/internal/TemplateInterpreter.java        | 68 ++++++---------
 .../commons/jexl3/internal/TemplateScript.java     | 56 ++++++++++--
 src/site/xdoc/changes.xml                          |  3 +
 .../java/org/apache/commons/jexl3/JXLTTest.java    | 99 +++++++++++++++++++++-
 7 files changed, 180 insertions(+), 56 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index eeca54d..8876a2b 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -73,6 +73,7 @@ New Features in 3.2:
 Bugs Fixed in 3.2:
 ==================
 
+* JEXL-311:      Jxlt template scripts fail using verbatim expressions embedded in lambdas
 * JEXL-309:      Line numbers are not correct when template report errors
 * JEXL-306:      Ternary operator ? protects also its branches from resolution errors
 * JEXL-305:      Script debugger produces incorrect syntax
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Closure.java b/src/main/java/org/apache/commons/jexl3/internal/Closure.java
index 009c962..a346fa7 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Closure.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Closure.java
@@ -124,7 +124,7 @@ public class Closure extends Script {
         if (frame != null) {
             callFrame = frame.assign(args);
         }
-        Interpreter interpreter = jexl.createInterpreter(context, callFrame);
+        Interpreter interpreter = createInterpreter(context, callFrame);
         JexlNode block = script.jjtGetChild(script.jjtGetNumChildren() - 1);
         return interpreter.interpret(block);
     }
@@ -135,7 +135,7 @@ public class Closure extends Script {
         if (frame != null) {
             local = frame.assign(args);
         }
-        return new Callable(jexl.createInterpreter(context, local)) {
+        return new Callable(createInterpreter(context, local)) {
             @Override
             public Object interpret() {
                 JexlNode block = script.jjtGetChild(script.jjtGetNumChildren() - 1);
@@ -143,5 +143,4 @@ public class Closure extends Script {
             }
         };
     }
-
 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
index 43a3023..4609a3f 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
@@ -925,7 +925,7 @@ public final class TemplateEngine extends JxltEngine {
             line = theLine;
             body = theBlock;
         }
-
+        
         /**
          * @return type
          */
@@ -980,8 +980,6 @@ public final class TemplateEngine extends JxltEngine {
         }
     }
 
-
-
     /**
      * Whether a sequence starts with a given set of characters (following spaces).
      * <p>Space characters at beginning of line before the pattern are discarded.</p>
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
index 5e9ec70..ffca1ef 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
@@ -18,16 +18,14 @@ package org.apache.commons.jexl3.internal;
 
 import org.apache.commons.jexl3.JexlContext;
 import org.apache.commons.jexl3.JexlInfo;
-import org.apache.commons.jexl3.JxltEngine;
 import org.apache.commons.jexl3.internal.TemplateEngine.TemplateExpression;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlUberspect;
-import org.apache.commons.jexl3.parser.ASTArguments;
-import org.apache.commons.jexl3.parser.ASTFunctionNode;
 import org.apache.commons.jexl3.parser.ASTIdentifier;
 import org.apache.commons.jexl3.parser.JexlNode;
 import java.io.Writer;
-import java.util.Arrays;
+import org.apache.commons.jexl3.parser.ASTJexlLambda;
+import org.apache.commons.jexl3.parser.ASTJexlScript;
 
 /**
  * The type of interpreter to use during evaluation of templates.
@@ -137,45 +135,6 @@ public class TemplateInterpreter extends Interpreter {
     }
 
     @Override
-    protected Object visit(ASTFunctionNode node, Object data) {
-        int argc = node.jjtGetNumChildren();
-        if (argc > 2) {
-            // objectNode 0 is the prefix
-            String prefix = ((ASTIdentifier) node.jjtGetChild(0)).getName();
-            if ("jexl".equals(prefix)) {
-                ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(1);
-                ASTArguments argNode = (ASTArguments) node.jjtGetChild(2);
-                String fname = functionNode.getName();
-                if ("print".equals(fname)) {
-                    // evaluate the arguments
-                    Object[] argv = visit(argNode, null);
-                    print((Integer) argv[0]);
-                    return null;
-                }
-                if ("include".equals(fname)) {
-                    // evaluate the arguments
-                    Object[] argv = visit(argNode, null);
-                    if (argv != null && argv.length > 0) {
-                        if (argv[0] instanceof TemplateScript) {
-                            TemplateScript script = (TemplateScript) argv[0];
-                            if (argv.length > 1) {
-                                argv = Arrays.copyOfRange(argv, 1, argv.length);
-                            } else {
-                                argv = null;
-                             }
-                            include(script, argv);
-                            return null;
-                        }
-                    }
-                }
-                // fail safe
-                throw new JxltEngine.Exception(node.jexlInfo(), "no callable template function " + fname, null);
-            }
-        }
-        return super.visit(node, data);
-    }
-
-    @Override
     protected Object visit(ASTIdentifier node, Object data) {
         String name = node.getName();
         if ("$jexl".equals(name)) {
@@ -183,5 +142,26 @@ public class TemplateInterpreter extends Interpreter {
         }
         return super.visit(node, data);
     }
-
+    
+    @Override
+    protected Object visit(ASTJexlScript node, Object data) {
+        if (node instanceof ASTJexlLambda && !((ASTJexlLambda) node).isTopLevel()) {
+            return new Closure(this, (ASTJexlLambda) node) {
+                @Override
+                protected Interpreter createInterpreter(JexlContext context, Scope.Frame local) {
+                    return new TemplateInterpreter(jexl, context, local, exprs, writer);
+                }
+            };
+        }
+        // otherwise...
+        final int numChildren = node.jjtGetNumChildren();
+        Object result = null;
+        for (int i = 0; i < numChildren; i++) {
+            JexlNode child = node.jjtGetChild(i);
+            result = child.jjtAccept(this, data);
+            cancelCheck(child);
+        }
+        return result;
+    }
+    
 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
index 9d6c7bc..63829f2 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
@@ -29,6 +29,12 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
+import org.apache.commons.jexl3.parser.ASTArguments;
+import org.apache.commons.jexl3.parser.ASTFunctionNode;
+import org.apache.commons.jexl3.parser.ASTIdentifier;
+import org.apache.commons.jexl3.parser.ASTNumberLiteral;
+import org.apache.commons.jexl3.parser.JexlNode;
 
 /**
  * A Template instance.
@@ -70,8 +76,7 @@ public final class TemplateScript implements JxltEngine.Template {
             throw new NullPointerException("null input");
         }
         this.jxlt = engine;
-        Scope scope = parms == null ? null : new Scope(null, parms);
-        prefix = directive;
+        this.prefix = directive;
         List<Block> blocks = jxlt.readTemplate(prefix, reader);
         List<TemplateExpression> uexprs = new ArrayList<TemplateExpression>();
         StringBuilder strb = new StringBuilder();
@@ -109,9 +114,15 @@ public final class TemplateScript implements JxltEngine.Template {
             info = jxlt.getEngine().createInfo();
         }
         // allow lambda defining params
+        Scope scope = parms == null ? null : new Scope(null, parms);
         script = jxlt.getEngine().parse(info.at(1, 1), false, strb.toString(), scope).script();
-        scope = script.getScope();
-        // create the exprs using the code frame for those appearing after the first block of code
+        // seek the map of expression number to scope so we can parse Unified
+        // expression blocks with the appropriate symbols
+        Map<Integer, Scope> mscope = new TreeMap<Integer, Scope>();
+        collectPrintScope(script.script(), null, mscope);
+        // jexl:print(...) expression counter
+        int jpe = 0;
+        // create the exprs using the intended scopes
         for (int b = 0; b < blocks.size(); ++b) {
             Block block = blocks.get(b);
             if (block.getType() == BlockType.VERBATIM) {
@@ -119,7 +130,7 @@ public final class TemplateScript implements JxltEngine.Template {
                         jxlt.parseExpression(
                                 info.at(block.getLine(), 1),
                                 block.getBody(),
-                                b > codeStart ? scope : null)
+                                mscope.get(jpe++))
                 );
             }
         }
@@ -146,6 +157,41 @@ public final class TemplateScript implements JxltEngine.Template {
         script = theScript;
         exprs = theExprs;
     }
+    
+    /**
+     * Collects the scope surrounding a call to jexl:print(i).
+     * <p>This allows to later parse the blocks with the known symbols 
+     * in the frame visible to the parser.
+     * @param node the visited node
+     * @param scope the current scope
+     * @param mscope the map of printed expression number to scope
+     */
+    static void collectPrintScope(JexlNode node, Scope scope, Map<Integer, Scope> mscope) {
+        int nc = node.jjtGetNumChildren();
+        if (node instanceof ASTFunctionNode) {
+            if (nc == 2) {
+                // 0 must be the prefix jexl:
+                ASTIdentifier nameNode = (ASTIdentifier) node.jjtGetChild(0);
+                if ("print".equals(nameNode.getName()) && "jexl".equals(nameNode.getNamespace())) {
+                    ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
+                    if (argNode.jjtGetNumChildren() == 1) {
+                        // seek the epression number
+                        JexlNode arg0 = argNode.jjtGetChild(0);
+                        if (arg0 instanceof ASTNumberLiteral) {
+                            int exprNumber = ((ASTNumberLiteral) arg0).getLiteral().intValue();
+                            mscope.put(exprNumber, scope);
+                            return;
+                        }
+                    }
+                }
+            }
+        } else if (node instanceof ASTJexlScript) {
+            scope = ((ASTJexlScript) node).getScope();
+        }
+        for (int c = 0; c < nc; ++c) {
+            collectPrintScope(node.jjtGetChild(c), scope, mscope);
+        }
+    }
 
     /**
      * @return script
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index 7d7ffa6..fdfe443 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -26,6 +26,9 @@
     </properties>
     <body>
         <release version="3.2" date="unreleased">
+            <action dev="henrib" type="fix" issue="JEXL-311">
+                Jxlt template scripts fail using verbatim expressions embedded in lambdas
+            </action>
             <action dev="henrib" type="fix" issue="JEXL-309">
                 Line numbers are not correct when template report errors
             </action>
diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
index 664a088..cc2b8d5 100644
--- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
@@ -728,7 +728,8 @@ public class JXLTTest extends JexlTestCase {
         Object value = JEXL.createScript(expr).execute(context);
         Assert.assertEquals(expr, "H\"ello \nHenrib", value);
     }
-        @Test
+        
+    @Test
     public void testInterpolationLvsG2() throws Exception {
         String expr =  "user='Dimitri'; var user='Henrib'; `H\\`ello \n${user}`";
         Object value = JEXL.createScript(expr).execute(context);
@@ -764,4 +765,100 @@ public class JXLTTest extends JexlTestCase {
 //        Assert.assertEquals("fourty-two", output);
 //
 //    }
+    
+    public static class Executor311 {
+        private final String name;
+        
+        public Executor311(String name) {
+            this.name = name;
+        }
+        // Injects name as first arg of any called script
+        public Object execute(JexlScript script, Object ...args) {
+            Object[] actuals;
+            if (args != null && args.length > 0) {
+                actuals = new Object[args.length + 1] ;
+                System.arraycopy(args, 0, actuals, 1, args.length);
+                actuals[0] = name;
+            } else {
+                actuals = new Object[]{name};
+            }
+            return script.execute(JexlEngine.getThreadContext(), actuals);
+        }
+    }
+    
+    public static class Context311 extends MapContext {
+        public Executor311 exec(String name) {
+            return new Executor311(name);
+        }
+    }
+    
+    @Test
+    public void test311a() throws Exception {
+        JexlContext ctx = null;
+        String rpt
+                = "$$((a)->{\n"
+                + "<p>Universe ${a}</p>\n"
+                + "$$})(42)";
+        JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
+        StringWriter strw = new StringWriter();
+        t.evaluate(ctx, strw);
+        String output = strw.toString();
+        Assert.assertEquals("<p>Universe 42</p>\n", output);
+    }
+
+    @Test
+    public void test311b() throws Exception {
+        JexlContext ctx311 = new Context311();
+        String rpt
+                = "$$ exec('42').execute(()->{\n"
+                + "<p>Universe 42</p>\n"
+                + "$$})";
+        JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
+        StringWriter strw = new StringWriter();
+        t.evaluate(ctx311, strw, 42);
+        String output = strw.toString();
+        Assert.assertEquals("<p>Universe 42</p>\n", output);
+    }
+    
+    @Test
+    public void test311c() throws Exception {
+        JexlContext ctx311 = new Context311();
+        String rpt
+                = "$$ exec('42').execute((a)->{"
+                + "\n<p>Universe ${a}</p>"
+                + "\n$$})";
+        JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
+        StringWriter strw = new StringWriter();
+        t.evaluate(ctx311, strw, 42);
+        String output = strw.toString();
+        Assert.assertEquals("<p>Universe 42</p>\n", output);
+    }
+       
+    @Test
+    public void test311d() throws Exception {
+        JexlContext ctx311 = new Context311();
+        String rpt
+                = "$$ exec('4').execute((a, b)->{"
+                + "\n<p>Universe ${a}${b}</p>"
+                + "\n$$}, '2')";
+        JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
+        StringWriter strw = new StringWriter();
+        t.evaluate(ctx311, strw, 42);
+        String output = strw.toString();
+        Assert.assertEquals("<p>Universe 42</p>\n", output);
+    }
+           
+    @Test
+    public void test311e() throws Exception {
+        JexlContext ctx311 = new Context311();
+        String rpt
+                = "$$var u = 'Universe'; exec('4').execute((a, b)->{"
+                + "\n<p>${u} ${a}${b}</p>"
+                + "\n$$}, '2')";
+        JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
+        StringWriter strw = new StringWriter();
+        t.evaluate(ctx311, strw, 42);
+        String output = strw.toString();
+        Assert.assertEquals("<p>Universe 42</p>\n", output);
+    }
 }