You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/08/08 11:56:19 UTC

[1/2] incubator-freemarker git commit: FREEMARKER-64: Function (and thus also method) call syntax now supports passing parameter by name.

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 89d436965 -> 1b6f894ee


FREEMARKER-64: Function (and thus also method) call syntax now supports passing parameter by name.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/b59a03a8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/b59a03a8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/b59a03a8

Branch: refs/heads/3
Commit: b59a03a8add0f47427ddd9594852f7bd86a8a7e9
Parents: 8d5263f
Author: ddekany <dd...@apache.org>
Authored: Tue Aug 8 13:46:50 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Tue Aug 8 13:46:50 2017 +0200

----------------------------------------------------------------------
 .../core/TemplateCallableModelTest.java         |  36 +++-
 .../core/userpkg/AllFeaturesFunction.java       |  29 ++-
 .../core/userpkg/NamedVarargsOnlyFunction.java  |  61 ++++++
 .../userpkg/PositionalVarargsOnlyFunction.java  |  19 +-
 .../core/userpkg/TestTemplateCallableModel.java |  58 +++---
 .../core/userpkg/TwoNamedParamsFunction.java    |  71 +++++++
 .../userpkg/TwoPositionalParamsFunction.java    |  21 +-
 .../templates/string-builtins3.ftl              |  16 +-
 .../freemarker/core/ASTDynamicTopLevelCall.java | 154 +-------------
 .../freemarker/core/ASTExpFunctionCall.java     | 201 +++++++++++--------
 .../apache/freemarker/core/_CallableUtils.java  | 180 ++++++++++++++++-
 .../core/model/ArgumentArrayLayout.java         |  14 ++
 freemarker-core/src/main/javacc/FTL.jj          | 118 +++++++++--
 13 files changed, 650 insertions(+), 328 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
index 3459aea..d6c9e66 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
@@ -24,9 +24,11 @@ import java.io.IOException;
 import org.apache.freemarker.core.userpkg.AllFeaturesDirective;
 import org.apache.freemarker.core.userpkg.AllFeaturesFunction;
 import org.apache.freemarker.core.userpkg.NamedVarargsOnlyDirective;
+import org.apache.freemarker.core.userpkg.NamedVarargsOnlyFunction;
 import org.apache.freemarker.core.userpkg.PositionalVarargsOnlyDirective;
 import org.apache.freemarker.core.userpkg.PositionalVarargsOnlyFunction;
 import org.apache.freemarker.core.userpkg.TwoNamedParamsDirective;
+import org.apache.freemarker.core.userpkg.TwoNamedParamsFunction;
 import org.apache.freemarker.core.userpkg.TwoNestedContentParamsDirective;
 import org.apache.freemarker.core.userpkg.TwoPositionalParamsDirective;
 import org.apache.freemarker.core.userpkg.TwoPositionalParamsFunction;
@@ -48,7 +50,9 @@ public class TemplateCallableModelTest extends TemplateTest {
 
         addToDataModel("fa", AllFeaturesFunction.INSTANCE);
         addToDataModel("fp", TwoPositionalParamsFunction.INSTANCE);
+        addToDataModel("fn", TwoNamedParamsFunction.INSTANCE);
         addToDataModel("fpvo", PositionalVarargsOnlyFunction.INSTANCE);
+        addToDataModel("fnvo", NamedVarargsOnlyFunction.INSTANCE);
     }
 
     @Test
@@ -121,6 +125,13 @@ public class TemplateCallableModelTest extends TemplateTest {
         assertOutput("${fp(1, 2)}",
                 "fp(p1=1, p2=2)");
 
+        assertOutput("${fn()}",
+                "fn(n1=null, n2=null)");
+        assertOutput("${fn(n1=11)}",
+                "fn(n1=11, n2=null)");
+        assertOutput("${fn(n1=11, n2=22)}",
+                "fn(n1=11, n2=22)");
+
         assertOutput("${fpvo()}",
                 "fpvo(pVarargs=[])");
         assertOutput("${fpvo(1)}",
@@ -132,10 +143,27 @@ public class TemplateCallableModelTest extends TemplateTest {
                 "fa(p1=null, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={})");
         assertOutput("${fa(1, 2)}",
                 "fa(p1=1, p2=2, pVarargs=[], n1=null, n2=null, nVarargs={})");
-        assertOutput("${fa(1, 2, 3)}",
-                "fa(p1=1, p2=2, pVarargs=[3], n1=null, n2=null, nVarargs={})");
-        assertOutput("${fa(1, 2, 3, 4)}",
-                "fa(p1=1, p2=2, pVarargs=[3, 4], n1=null, n2=null, nVarargs={})");
+        assertOutput("${fa(n1=11, n2=22)}",
+                "fa(p1=null, p2=null, pVarargs=[], n1=11, n2=22, nVarargs={})");
+
+        assertOutput("${fa(1, 2, n1=11, n2=22)}",
+                "fa(p1=1, p2=2, pVarargs=[], n1=11, n2=22, nVarargs={})");
+        assertOutput("${fa(1, n1=11)}",
+                "fa(p1=1, p2=null, pVarargs=[], n1=11, n2=null, nVarargs={})");
+        assertOutput("${fa(1, 2, 3, n1=11, n2=22, n3=33)}",
+                "fa(p1=1, p2=2, pVarargs=[3], n1=11, n2=22, nVarargs={\"n3\": 33})");
+        assertOutput("${fa(1, n1=11, n3=33)}",
+                "fa(p1=1, p2=null, pVarargs=[], n1=11, n2=null, nVarargs={\"n3\": 33})");
+        assertOutput("${fa(1, n1=11, a=1, b=2, c=3, d=4, e=5, f=6, g=7)}",
+                "fa(p1=1, p2=null, pVarargs=[], n1=11, n2=null, nVarargs={"
+                        + "\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4, \"e\": 5, \"f\": 6, \"g\": 7})");
+
+        assertOutput("${fnvo()}",
+                "fnvo(nVarargs={})");
+        assertOutput("${fnvo(n1=11)}",
+                "fnvo(nVarargs={\"n1\": 11})");
+        assertOutput("${fnvo(n1=11, n2=22)}",
+                "fnvo(nVarargs={\"n1\": 11, \"n2\": 22})");
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesFunction.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesFunction.java
index f468be7..66817bb 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesFunction.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesFunction.java
@@ -21,9 +21,6 @@ package org.apache.freemarker.core.userpkg;
 
 import static org.apache.freemarker.core._CallableUtils.*;
 
-import java.io.IOException;
-import java.io.StringWriter;
-
 import org.apache.freemarker.core.CallPlace;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
@@ -88,20 +85,18 @@ public class AllFeaturesFunction extends TestTemplateCallableModel implements Te
 
     private TemplateModel execute(Number p1, Number p2, TemplateSequenceModel pOthers,
             Number n1, Number n2, TemplateHashModelEx2 nOthers) throws TemplateException {
-        StringWriter out = new StringWriter();
-        try {
-            out.write("fa(");
-            printParam("p1", p1, out, true);
-            printParam("p2", p2, out);
-            printParam("pVarargs", pOthers, out);
-            printParam(N1_ARG_NAME, n1, out);
-            printParam(N2_ARG_NAME, n2, out);
-            printParam("nVarargs", nOthers, out);
-            out.write(")");
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-        return new SimpleScalar(out.toString());
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("fa(");
+        printParam("p1", p1, sb, true);
+        printParam("p2", p2, sb);
+        printParam("pVarargs", pOthers, sb);
+        printParam(N1_ARG_NAME, n1, sb);
+        printParam(N2_ARG_NAME, n2, sb);
+        printParam("nVarargs", nOthers, sb);
+        sb.append(")");
+
+        return new SimpleScalar(sb.toString());
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyFunction.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyFunction.java
new file mode 100644
index 0000000..8413647
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyFunction.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.userpkg;
+
+import org.apache.freemarker.core.CallPlace;
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.TemplateFunctionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+
+public class NamedVarargsOnlyFunction extends TestTemplateCallableModel implements TemplateFunctionModel {
+
+    public static final NamedVarargsOnlyFunction INSTANCE = new NamedVarargsOnlyFunction();
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
+            0, false,
+            null, true);
+
+    private static final int NAMED_VARARGS_ARG_IDX = ARGS_LAYOUT.getNamedVarargsArgumentIndex();
+
+    private NamedVarargsOnlyFunction() {
+        //
+    }
+
+    @Override
+    public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
+            throws TemplateException {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("fnvo(");
+        printParam("nVarargs", args[NAMED_VARARGS_ARG_IDX], sb, true);
+        sb.append(")");
+
+        return new SimpleScalar(sb.toString());
+    }
+
+    @Override
+    public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyFunction.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyFunction.java
index 1c0c82c..7c8040d 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyFunction.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyFunction.java
@@ -19,9 +19,6 @@
 
 package org.apache.freemarker.core.userpkg;
 
-import java.io.IOException;
-import java.io.StringWriter;
-
 import org.apache.freemarker.core.CallPlace;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
@@ -44,15 +41,13 @@ public class PositionalVarargsOnlyFunction extends TestTemplateCallableModel imp
 
     @Override
     public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env) throws TemplateException {
-        try {
-            StringWriter out = new StringWriter();
-            out.write("fpvo(");
-            printParam("pVarargs", args[ARGS_LAYOUT.getPositionalVarargsArgumentIndex()], out, true);
-            out.write(")");
-            return new SimpleScalar(out.toString());
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("fpvo(");
+        printParam("pVarargs", args[ARGS_LAYOUT.getPositionalVarargsArgumentIndex()], sb, true);
+        sb.append(")");
+
+        return new SimpleScalar(sb.toString());
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java
index f0a15d7..a1b263c 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java
@@ -33,62 +33,72 @@ import org.apache.freemarker.core.util._StringUtil;
 
 public abstract class TestTemplateCallableModel implements TemplateCallableModel {
 
+    protected void printParam(String name, Object value, StringBuilder sb) throws TemplateModelException {
+        printParam(name, value, sb, false);
+    }
+
+    protected void printParam(String name, Object value, StringBuilder sb, boolean first)
+            throws TemplateModelException {
+        if (!first) {
+            sb.append(", ");
+        }
+        sb.append(name);
+        sb.append("=");
+        printValue(value, sb);
+    }
+
     protected void printParam(String name, Object value, Writer out) throws IOException, TemplateModelException {
         printParam(name, value, out, false);
     }
 
     protected void printParam(String name, Object value, Writer out, boolean first)
             throws IOException, TemplateModelException {
-        if (!first) {
-            out.write(", ");
-        }
-        out.write(name);
-        out.write("=");
-        printValue(value, out);
+        StringBuilder sb = new StringBuilder();
+        printParam(name, value, sb, first);
+        out.write(sb.toString());
     }
 
-    private void printValue(Object value, Writer out) throws IOException, TemplateModelException {
+    private void printValue(Object value, StringBuilder sb) throws TemplateModelException {
         if (value == null) {
-            out.write("null");
+            sb.append("null");
         } else if (value instanceof TemplateNumberModel) {
-            out.write(((TemplateNumberModel) value).getAsNumber().toString());
+            sb.append(((TemplateNumberModel) value).getAsNumber().toString());
         } else if (value instanceof TemplateScalarModel) {
-            out.write(FTLUtil.toStringLiteral(((TemplateScalarModel) value).getAsString()));
+            sb.append(FTLUtil.toStringLiteral(((TemplateScalarModel) value).getAsString()));
         } else if (value instanceof TemplateSequenceModel) {
             int len = ((TemplateSequenceModel) value).size();
-            out.write('[');
+            sb.append('[');
             for (int i = 0; i < len; i++) {
                 if (i != 0) {
-                    out.write(", ");
+                    sb.append(", ");
                 }
-                printValue(((TemplateSequenceModel) value).get(i), out);
+                printValue(((TemplateSequenceModel) value).get(i), sb);
             }
-            out.write(']');
+            sb.append(']');
         } else if (value instanceof TemplateHashModelEx2) {
             TemplateHashModelEx2.KeyValuePairIterator it = ((TemplateHashModelEx2) value).keyValuePairIterator();
-            out.write('{');
+            sb.append('{');
             while (it.hasNext()) {
                 TemplateHashModelEx2.KeyValuePair kvp = it.next();
 
-                printValue(kvp.getKey(), out);
-                out.write(": ");
-                printValue(kvp.getValue(), out);
+                printValue(kvp.getKey(), sb);
+                sb.append(": ");
+                printValue(kvp.getValue(), sb);
 
                 if (it.hasNext()) {
-                    out.write(", ");
+                    sb.append(", ");
                 }
             }
-            out.write('}');
+            sb.append('}');
         } else if (value instanceof String) {
-            out.write(_StringUtil.jQuote(value));
+            sb.append(_StringUtil.jQuote(value));
         } else if (value instanceof Number) {
-            out.write(value.toString());
+            sb.append(value.toString());
         } else if (value instanceof Boolean) {
-            out.write(value.toString());
+            sb.append(value.toString());
         } else {
             throw new IllegalArgumentException("Unsupported value class: " + value.getClass().getName());
         }
     }
 
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsFunction.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsFunction.java
new file mode 100644
index 0000000..b866034
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsFunction.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.userpkg;
+
+import org.apache.freemarker.core.CallPlace;
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.TemplateFunctionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.util.StringToIndexMap;
+
+public class TwoNamedParamsFunction extends TestTemplateCallableModel implements TemplateFunctionModel {
+
+    public static final TwoNamedParamsFunction INSTANCE = new TwoNamedParamsFunction();
+
+    private static final int N1_ARG_IDX = 0;
+    private static final int N2_ARG_IDX = 1;
+
+    private static final String N1_ARG_NAME = "n1";
+    private static final String N2_ARG_NAME = "n2";
+
+    private static final StringToIndexMap ARG_NAME_TO_IDX = StringToIndexMap.of(
+            N1_ARG_NAME, N1_ARG_IDX,
+            N2_ARG_NAME, N2_ARG_IDX);
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
+            0, false,
+            ARG_NAME_TO_IDX, false);
+
+    private TwoNamedParamsFunction() {
+        //
+    }
+
+    @Override
+    public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
+            throws TemplateException {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("fn(");
+        printParam(N1_ARG_NAME, args[N1_ARG_IDX], sb, true);
+        printParam(N2_ARG_NAME, args[N2_ARG_IDX], sb);
+        sb.append(")");
+
+        return new SimpleScalar(sb.toString());
+    }
+
+    @Override
+    public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsFunction.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsFunction.java
index e406c99..cfce364 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsFunction.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsFunction.java
@@ -19,9 +19,6 @@
 
 package org.apache.freemarker.core.userpkg;
 
-import java.io.IOException;
-import java.io.StringWriter;
-
 import org.apache.freemarker.core.CallPlace;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
@@ -44,16 +41,14 @@ public class TwoPositionalParamsFunction extends TestTemplateCallableModel imple
 
     @Override
     public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env) throws TemplateException {
-        try {
-            StringWriter out = new StringWriter();
-            out.write("fp(");
-            printParam("p1", args[0], out, true);
-            printParam("p2", args[1], out);
-            out.write(")");
-            return new SimpleScalar(out.toString());
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("fp(");
+        printParam("p1", args[0], sb, true);
+        printParam("p2", args[1], sb);
+        sb.append(")");
+
+        return new SimpleScalar(sb.toString());
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtins3.ftl
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtins3.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtins3.ftl
index ece74f9..0a387ce 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtins3.ftl
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtins3.ftl
@@ -35,7 +35,7 @@
 <@assertFails message='"m" flag'>
     ${'x'?keepBefore('x', 'm')}
 </...@assertFails>
-<@assertFails message='Too many arguments'>
+<@assertFails message='can only have'>
     ${'x'?keepBefore('x', 'i', 'x')}
 </...@assertFails>
 <@assertFails message='argument'>
@@ -61,7 +61,7 @@
 <@assertFails message='"m" flag'>
     ${'x'?keepBeforeLast('x', 'm')}
 </...@assertFails>
-<@assertFails message='Too many arguments'>
+<@assertFails message='can only have'>
     ${'x'?keepBeforeLast('x', 'i', 'x')}
 </...@assertFails>
 <@assertFails message='argument'>
@@ -87,7 +87,7 @@
 <@assertFails message='"m" flag'>
     ${'x'?keepAfter('x', 'm')}
 </...@assertFails>
-<@assertFails message='Too many arguments'>
+<@assertFails message='can only have'>
     ${'x'?keepAfter('x', 'i', 'x')}
 </...@assertFails>
 <@assertFails message='argument'>
@@ -115,7 +115,7 @@
 <@assertFails message='"m" flag'>
     ${'x'?keepAfterLast('x', 'm')}
 </...@assertFails>
-<@assertFails message='Too many arguments'>
+<@assertFails message='can only have'>
     ${'x'?keepAfterLast('x', 'i', 'x')}
 </...@assertFails>
 <@assertFails message='argument'>
@@ -129,7 +129,7 @@
 <@assertEquals expected='o' actual='foo'?removeBeginning('fo') />
 <@assertEquals expected='' actual='foo'?removeBeginning('foo') />
 <@assertEquals expected='foo' actual='foo'?removeBeginning('') />
-<@assertFails message='Too many arguments'>
+<@assertFails message='can only have'>
     ${'x'?removeBeginning('x', 'x')}
 </...@assertFails>
 <@assertFails message='argument'>
@@ -143,7 +143,7 @@
 <@assertEquals expected='b' actual='bar'?removeEnding('ar') />
 <@assertEquals expected='' actual='bar'?removeEnding('bar') />
 <@assertEquals expected='bar' actual='bar'?removeEnding('') />
-<@assertFails message='Too many arguments'>
+<@assertFails message='can only have'>
     ${'x'?removeEnding('x', 'x')}
 </...@assertFails>
 <@assertFails message='argument'>
@@ -171,7 +171,7 @@
 <@assertEquals expected='https://example.com' actual="https://example.com"?ensureStartsWith("[a-z]+://", "http://") />
 <@assertEquals expected='http://HTTP://example.com' actual="HTTP://example.com"?ensureStartsWith("[a-z]+://", "http://") />
 <@assertEquals expected='HTTP://example.com' actual="HTTP://example.com"?ensureStartsWith("[a-z]+://", "http://", "ir") />
-<@assertFails message='Too many arguments'>
+<@assertFails message='can only have'>
     ${'x'?ensureStartsWith('x', 'x', 'x', 'x')}
 </...@assertFails>
 <@assertFails message='argument'>
@@ -185,7 +185,7 @@
 <@assertEquals expected='foo' actual='foo'?ensureEndsWith('') />
 <@assertEquals expected='x' actual=''?ensureEndsWith('x') />
 <@assertEquals expected='' actual=''?ensureEndsWith('') />
-<@assertFails message='Too many arguments'>
+<@assertFails message='can only have'>
     ${'x'?ensureEndsWith('x', 'x')}
 </...@assertFails>
 <@assertFails message='argument'>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
index f579f6f..7bf4737 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
@@ -21,22 +21,17 @@ package org.apache.freemarker.core;
 
 import java.io.IOException;
 import java.io.Writer;
-import java.util.Collection;
-import java.util.List;
 
 import org.apache.freemarker.core.ThreadInterruptionSupportTemplatePostProcessor.ASTThreadInterruptionCheck;
+import org.apache.freemarker.core._CallableUtils.NamedArgument;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
-import org.apache.freemarker.core.model.Constants;
 import org.apache.freemarker.core.model.TemplateCallableModel;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util.CommonSupplier;
-import org.apache.freemarker.core.util.FTLUtil;
 import org.apache.freemarker.core.util.StringToIndexMap;
-import org.apache.freemarker.core.util._CollectionUtil;
 import org.apache.freemarker.core.util._StringUtil;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -54,16 +49,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  */
 class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace  {
 
-    static final class NamedArgument {
-        private final String name;
-        private final ASTExpression value;
-
-        public NamedArgument(String name, ASTExpression value) {
-            this.name = name;
-            this.value = value;
-        }
-    }
-
     private final ASTExpression callableValueExp;
     private final ASTExpression[] positionalArgs;
     private final NamedArgument[] namedArgs;
@@ -132,9 +117,8 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace  {
             throw new _MiscTemplateException(env, "Nested content is not supported by this directive.");
         }
 
-        TemplateModel[] execArgs = argsLayout != null
-                ? getExecuteArgsBasedOnLayout(argsLayout, callableValue, env)
-                : getExecuteArgsWithoutLayout(callableValue, env);
+        TemplateModel[] execArgs = _CallableUtils.getExecuteArgs(
+                positionalArgs, namedArgs, argsLayout, callableValue, env);
 
         if (directive != null) {
             directive.execute(execArgs, this, env.getOut(), env);
@@ -150,129 +134,6 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace  {
         return null;
     }
 
-    private TemplateModel[] getExecuteArgsWithoutLayout(TemplateCallableModel callableValue, Environment env)
-            throws TemplateException {
-        if (namedArgs != null) {
-            throw new _MiscTemplateException(env, getNamedArgumentsNotSupportedMessage(callableValue, namedArgs[0]));
-        }
-        TemplateModel[] execArgs = new TemplateModel[positionalArgs.length];
-        for (int i = 0; i < positionalArgs.length; i++) {
-            ASTExpression positionalArg = positionalArgs[i];
-            execArgs[i] = positionalArg.eval(env);
-        }
-        return execArgs;
-    }
-
-    private TemplateModel[] getExecuteArgsBasedOnLayout(ArgumentArrayLayout argsLayout, TemplateCallableModel callableValue,
-            Environment env) throws TemplateException {
-        int predefPosArgCnt = argsLayout.getPredefinedPositionalArgumentCount();
-        int posVarargsArgIdx = argsLayout.getPositionalVarargsArgumentIndex();
-
-        TemplateModel[] execArgs = new TemplateModel[argsLayout.getTotalLength()];
-
-        // Fill predefined positional args:
-        if (positionalArgs != null) {
-            int actualPredefPosArgCnt = Math.min(positionalArgs.length, predefPosArgCnt);
-            for (int argIdx = 0; argIdx < actualPredefPosArgCnt; argIdx++) {
-                execArgs[argIdx] = positionalArgs[argIdx].eval(env);
-            }
-        }
-
-        if (posVarargsArgIdx != -1) {
-            int posVarargsLength = positionalArgs != null ? positionalArgs.length - predefPosArgCnt : 0;
-            TemplateSequenceModel varargsSeq;
-            if (posVarargsLength <= 0) {
-                varargsSeq = Constants.EMPTY_SEQUENCE;
-            } else {
-                NativeSequence nativeSeq = new NativeSequence(posVarargsLength);
-                varargsSeq = nativeSeq;
-                for (int posVarargIdx = 0; posVarargIdx < posVarargsLength; posVarargIdx++) {
-                    nativeSeq.add(positionalArgs[predefPosArgCnt + posVarargIdx].eval(env));
-                }
-            }
-            execArgs[posVarargsArgIdx] = varargsSeq;
-        } else if (positionalArgs != null && positionalArgs.length > predefPosArgCnt) {
-            checkSupportsAnyParameters(callableValue, argsLayout, env);
-            List<String> validPredefNames = argsLayout.getPredefinedNamedArgumentsMap().getKeys();
-            _ErrorDescriptionBuilder errorDesc = new _ErrorDescriptionBuilder(
-                    "The target ", FTLUtil.getCallableTypeName(callableValue), " ",
-                    (predefPosArgCnt != 0
-                            ? new Object[]{ "can only have ", predefPosArgCnt }
-                            : "can't have"
-                    ),
-                    " arguments passed by position, but the invocation has ",
-                    positionalArgs.length, " such arguments. Try to pass arguments by name (as in ",
-                    "<@example x=1 y=2 />", ").",
-                    (!validPredefNames.isEmpty()
-                            ? new Object[] { " The supported parameter names are:\n",
-                                    new _DelayedJQuotedListing(validPredefNames)}
-                            : _CollectionUtil.EMPTY_OBJECT_ARRAY)
-            );
-            if (callableValue instanceof Environment.TemplateLanguageDirective) {
-                errorDesc.tip("You can pass a parameter by position (i.e., without specifying its name, as you"
-                        + " have tried now) when the macro has defined that parameter to be a positional parameter. "
-                        + "See in the documentation how, and when that's a good practice.");
-            }
-            throw new _MiscTemplateException(env,
-                    errorDesc
-            );
-        }
-
-        int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex();
-        NativeHashEx2 namedVarargsHash = null;
-        if (namedArgs != null) {
-            StringToIndexMap predefNamedArgsMap = argsLayout.getPredefinedNamedArgumentsMap();
-            for (NamedArgument namedArg : namedArgs) {
-                int argIdx = predefNamedArgsMap.get(namedArg.name);
-                if (argIdx != -1) {
-                    execArgs[argIdx] = namedArg.value.eval(env);
-                } else {
-                    if (namedVarargsHash == null) {
-                        if (namedVarargsArgumentIndex == -1) {
-                            checkSupportsAnyParameters(callableValue, argsLayout, env);
-                            Collection<String> validNames = predefNamedArgsMap.getKeys();
-                            throw new _MiscTemplateException(env,
-                                    validNames == null || validNames.isEmpty()
-                                    ? getNamedArgumentsNotSupportedMessage(callableValue, namedArg)
-                                    : new Object[] {
-                                            "The called ", FTLUtil.getCallableTypeName(callableValue),
-                                            " has no parameter that's passed by name and is called ",
-                                            new _DelayedJQuote(namedArg.name), ". The supported parameter names are:\n",
-                                            new _DelayedJQuotedListing(validNames)
-                                    });
-                        }
-
-                        namedVarargsHash = new NativeHashEx2();
-                    }
-                    namedVarargsHash.put(namedArg.name, namedArg.value.eval(env));
-                }
-            }
-        }
-        if (namedVarargsArgumentIndex != -1) {
-            execArgs[namedVarargsArgumentIndex] = namedVarargsHash != null ? namedVarargsHash : Constants.EMPTY_HASH;
-        }
-        return execArgs;
-    }
-
-    private Object[] getNamedArgumentsNotSupportedMessage(TemplateCallableModel callableValue,
-            NamedArgument namedArg) {
-        return new Object[] {
-                "The called ", FTLUtil.getCallableTypeName(callableValue),
-                " can't have arguments that are passed by name (like ",
-                new _DelayedJQuote(namedArg.name), "). Try to pass arguments by position "
-                + "(i.e, without name, as in ", "<@example 1, 2, 3 />" ,  ")."
-        };
-    }
-
-    private void checkSupportsAnyParameters(
-            TemplateCallableModel callableValue, ArgumentArrayLayout argsLayout, Environment env)
-            throws TemplateException {
-        if (argsLayout.getTotalLength() == 0) {
-            throw new _MiscTemplateException(env,
-                    "The called ", FTLUtil.getCallableTypeName(callableValue), " doesn't support any parameters.");
-        }
-    }
-
     @Override
     boolean isNestedBlockRepeater() {
         return true;
@@ -292,20 +153,19 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace  {
         boolean nameIsInParen = sb.charAt(sb.length() - 1) == ')';
         if (positionalArgs != null) {
             for (int i = 0; i < positionalArgs.length; i++) {
-                ASTExpression argExp = (ASTExpression) positionalArgs[i];
                 if (i != 0) {
                     sb.append(',');
                 }
                 sb.append(' ');
-                sb.append(argExp.getCanonicalForm());
+                sb.append(positionalArgs[i].getCanonicalForm());
             }
         }
         if (namedArgs != null) {
             for (NamedArgument namedArg : namedArgs) {
                 sb.append(' ');
-                sb.append(_StringUtil.toFTLTopLevelIdentifierReference(namedArg.name));
+                sb.append(_StringUtil.toFTLTopLevelIdentifierReference(namedArg.getName()));
                 sb.append('=');
-                MessageUtil.appendExpressionAsUntearable(sb, namedArg.value);
+                sb.append(namedArg.getValue().getCanonicalForm());
             }
         }
         if (nestedContentParamNames != null) {
@@ -365,7 +225,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace  {
                 final int namedArgsSize = namedArgs != null ? namedArgs.length : 0;
                 if (idx - base < namedArgsSize * 2) {
                     NamedArgument namedArg = namedArgs[(idx - base) / 2];
-                    return (idx - base) % 2 == 0 ? namedArg.name : namedArg.value;
+                    return (idx - base) % 2 == 0 ? namedArg.getName() : namedArg.getValue();
                 } else {
                     base += namedArgsSize * 2;
                     final int bodyParameterNamesSize = nestedContentParamNames != null

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpFunctionCall.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpFunctionCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpFunctionCall.java
index 40dc6e3..5101db7 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpFunctionCall.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpFunctionCall.java
@@ -25,16 +25,12 @@ package org.apache.freemarker.core;
 
 import java.io.IOException;
 import java.io.Writer;
-import java.util.ArrayList;
-import java.util.List;
 
-import org.apache.freemarker.core.model.ArgumentArrayLayout;
-import org.apache.freemarker.core.model.Constants;
+import org.apache.freemarker.core._CallableUtils.NamedArgument;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.util.CommonSupplier;
-import org.apache.freemarker.core.util.FTLUtil;
+import org.apache.freemarker.core.util._StringUtil;
 
 
 /**
@@ -42,93 +38,73 @@ import org.apache.freemarker.core.util.FTLUtil;
  */
 final class ASTExpFunctionCall extends ASTExpression implements CallPlace {
 
-    private final ASTExpression target;
-    private final ASTExpListLiteral arguments;
+    private final ASTExpression functionExp;
+    private final ASTExpression[] positionalArgs;
+    private final NamedArgument[] namedArgs;
 
-    ASTExpFunctionCall(ASTExpression target, ArrayList arguments) {
-        this(target, new ASTExpListLiteral(arguments));
-    }
+    ASTExpFunctionCall(
+            ASTExpression functionExp, ASTExpression[] positionalArgs, NamedArgument[] namedArgs) {
+        this.functionExp = functionExp;
+
+        if (positionalArgs != null && positionalArgs.length == 0
+                || namedArgs != null && namedArgs.length == 0) {
+            throw new IllegalArgumentException("Use null instead of empty collections");
+        }
 
-    private ASTExpFunctionCall(ASTExpression target, ASTExpListLiteral arguments) {
-        this.target = target;
-        this.arguments = arguments;
+        this.positionalArgs = positionalArgs;
+        this.namedArgs = namedArgs;
     }
 
     @Override
     TemplateModel _eval(Environment env) throws TemplateException {
-        TemplateModel targetModel = target.eval(env);
-
-        if (!(targetModel instanceof TemplateFunctionModel)) {
-            throw new NonFunctionException(target, targetModel, env);
-        }
-        TemplateFunctionModel func = (TemplateFunctionModel) targetModel;
-
-        ArgumentArrayLayout arrayLayout = func.getFunctionArgumentArrayLayout();
-
-        // TODO [FM3] This is just temporary, until we support named args. Then the logic in ASTDynamicTopLevelCall
-        // should be reused.
-
-        TemplateModel[] args;
-        if (arrayLayout != null) {
-            int posVarargsLength;
-            int callArgCnt = arguments.size();
-            int predefPosArgCnt = arrayLayout.getPredefinedPositionalArgumentCount();
-            int posVarargsIdx = arrayLayout.getPositionalVarargsArgumentIndex();
-            if (callArgCnt > predefPosArgCnt) {
-                if (posVarargsIdx == -1) {
-                    throw new _MiscTemplateException(env,
-                            "Too many arguments; the target ", FTLUtil.getCallableTypeName(func),
-                            " only has ", predefPosArgCnt, " parameters.");
-                }
+        TemplateFunctionModel function;
+        {
+            TemplateModel functionUncasted = functionExp.eval(env);
+            if (!(functionUncasted instanceof TemplateFunctionModel)) {
+                throw new NonFunctionException(functionExp, functionUncasted, env);
             }
+            function = (TemplateFunctionModel) functionUncasted;
+        }
 
-            List<TemplateModel> callArgList = arguments.getModelList(env);
-
-            args = new TemplateModel[arrayLayout.getTotalLength()];
-            int callPredefArgCnt = Math.min(callArgCnt, predefPosArgCnt);
-            for (int argIdx = 0; argIdx < callPredefArgCnt; argIdx++) {
-                args[argIdx] = callArgList.get(argIdx);
-            }
+        return function.execute(
+                _CallableUtils.getExecuteArgs(
+                        positionalArgs, namedArgs, function.getFunctionArgumentArrayLayout(), function, env),
+                this,
+                env);
+    }
 
-            if (posVarargsIdx != -1) {
-                TemplateSequenceModel varargsSeq;
-                posVarargsLength = callArgCnt - predefPosArgCnt;
-                if (posVarargsLength <= 0) {
-                    varargsSeq = Constants.EMPTY_SEQUENCE;
+    @Override
+    public String getCanonicalForm() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(functionExp.getCanonicalForm());
+        sb.append("(");
+
+        boolean first = true;
+        if (positionalArgs != null) {
+            for (ASTExpression positionalArg : positionalArgs) {
+                if (!first) {
+                    sb.append(", ");
                 } else {
-                    NativeSequence nativeSeq = new NativeSequence(posVarargsLength);
-                    varargsSeq = nativeSeq;
-                    for (int posVarargIdx = 0; posVarargIdx < posVarargsLength; posVarargIdx++) {
-                        nativeSeq.add(callArgList.get(predefPosArgCnt + posVarargIdx));
-                    }
+                    first = false;
                 }
-                args[posVarargsIdx] = varargsSeq;
+                sb.append(positionalArg.getCanonicalForm());
             }
-
-            int namedVarargsArgIdx = arrayLayout.getNamedVarargsArgumentIndex();
-            if (namedVarargsArgIdx != -1) {
-                args[namedVarargsArgIdx] = Constants.EMPTY_HASH;
-            }
-        } else {
-            List<TemplateModel> callArgList = arguments.getModelList(env);
-            args = new TemplateModel[callArgList.size()];
-            for (int i = 0; i < callArgList.size(); i++) {
-                args[i] = callArgList.get(i);
+        }
+        if (namedArgs != null) {
+            for (NamedArgument namedArg : namedArgs) {
+                if (!first) {
+                    sb.append(',');
+                } else {
+                    first = false;
+                }
+                sb.append(_StringUtil.toFTLTopLevelIdentifierReference(namedArg.getName()));
+                sb.append('=');
+                sb.append(namedArg.getValue().getCanonicalForm());
             }
         }
 
-        return func.execute(args, this, env);
-    }
-
-    @Override
-    public String getCanonicalForm() {
-        StringBuilder buf = new StringBuilder();
-        buf.append(target.getCanonicalForm());
-        buf.append("(");
-        String list = arguments.getCanonicalForm();
-        buf.append(list.substring(1, list.length() - 1));
-        buf.append(")");
-        return buf.toString();
+        sb.append(")");
+        return sb.toString();
     }
 
     @Override
@@ -148,24 +124,63 @@ final class ASTExpFunctionCall extends ASTExpression implements CallPlace {
     @Override
     protected ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
+        ASTExpression[] positionalArgsClone;
+        if (positionalArgs != null) {
+            positionalArgsClone = new ASTExpression[positionalArgs.length];
+            for (int i = 0; i < positionalArgs.length; i++) {
+                positionalArgsClone[i] = positionalArgs[i].deepCloneWithIdentifierReplaced(
+                        replacedIdentifier, replacement, replacementState);
+            }
+        } else {
+            positionalArgsClone = null;
+        }
+
+        NamedArgument[] namedArgsClone;
+        if (namedArgs != null) {
+            namedArgsClone = new NamedArgument[namedArgs.length];
+            for (int i = 0; i < namedArgs.length; i++) {
+                NamedArgument namedArg = namedArgs[i];
+                namedArgsClone[i] = new NamedArgument(
+                        namedArg.getName(),
+                        namedArg.getValue().deepCloneWithIdentifierReplaced(
+                                replacedIdentifier, replacement, replacementState));
+
+            }
+        } else {
+            namedArgsClone = null;
+        }
+
         return new ASTExpFunctionCall(
-                target.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),
-                (ASTExpListLiteral) arguments.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState));
+                functionExp.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),
+                positionalArgsClone, namedArgsClone);
     }
 
     @Override
     int getParameterCount() {
-        return 1 + arguments.items.size();
+        return 1/*nameExp*/
+                + (positionalArgs != null ? positionalArgs.length : 0)
+                + (namedArgs != null ? namedArgs.length * 2 : 0);
     }
 
     @Override
     Object getParameterValue(int idx) {
         if (idx == 0) {
-            return target;
-        } else if (idx < getParameterCount()) {
-            return arguments.items.get(idx - 1);
+            return functionExp;
         } else {
-            throw new IndexOutOfBoundsException();
+            int base = 1;
+            final int positionalArgsSize = positionalArgs != null ? positionalArgs.length : 0;
+            if (idx - base < positionalArgsSize) {
+                return positionalArgs[idx - base];
+            } else {
+                base += positionalArgsSize;
+                final int namedArgsSize = namedArgs != null ? namedArgs.length : 0;
+                if (idx - base < namedArgsSize * 2) {
+                    NamedArgument namedArg = namedArgs[(idx - base) / 2];
+                    return (idx - base) % 2 == 0 ? namedArg.getName() : namedArg.getValue();
+                } else {
+                    throw new IndexOutOfBoundsException();
+                }
+            }
         }
     }
 
@@ -173,10 +188,20 @@ final class ASTExpFunctionCall extends ASTExpression implements CallPlace {
     ParameterRole getParameterRole(int idx) {
         if (idx == 0) {
             return ParameterRole.CALLEE;
-        } else if (idx < getParameterCount()) {
-            return ParameterRole.ARGUMENT_VALUE;
         } else {
-            throw new IndexOutOfBoundsException();
+            int base = 1;
+            final int positionalArgsSize = positionalArgs != null ? positionalArgs.length : 0;
+            if (idx - base < positionalArgsSize) {
+                return ParameterRole.ARGUMENT_VALUE;
+            } else {
+                base += positionalArgsSize;
+                final int namedArgsSize = namedArgs != null ? namedArgs.length : 0;
+                if (idx - base < namedArgsSize * 2) {
+                    return (idx - base) % 2 == 0 ? ParameterRole.ARGUMENT_NAME : ParameterRole.ARGUMENT_VALUE;
+                } else {
+                    throw new IndexOutOfBoundsException();
+                }
+            }
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
index 4ab59b4..89bf91b 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
@@ -22,15 +22,21 @@ package org.apache.freemarker.core;
 import java.io.IOException;
 import java.io.Serializable;
 import java.io.Writer;
+import java.util.Collection;
 import java.util.List;
 
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.Constants;
+import org.apache.freemarker.core.model.TemplateCallableModel;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.util.FTLUtil;
+import org.apache.freemarker.core.util.StringToIndexMap;
+import org.apache.freemarker.core.util._CollectionUtil;
 
 /**
  * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
@@ -165,5 +171,177 @@ public final class _CallableUtils {
         }
         throw new NonStringException((Serializable) argName != null ? argName : argIndex, argValue, null, null);
     }
-    
+
+    static TemplateModel[] getExecuteArgs(
+            ASTExpression[] positionalArgs, NamedArgument[] namedArgs, ArgumentArrayLayout argsLayout,
+            TemplateCallableModel callableValue,
+            Environment env) throws
+            TemplateException {
+        return argsLayout != null
+                ? getExecuteArgsBasedOnLayout(positionalArgs, namedArgs, argsLayout, callableValue, env)
+                : getExecuteArgsWithoutLayout(positionalArgs, namedArgs, callableValue, env);
+    }
+
+    private static TemplateModel[] getExecuteArgsWithoutLayout(ASTExpression[] positionalArgs,
+            NamedArgument[] namedArgs, TemplateCallableModel callableValue, Environment env)
+            throws TemplateException {
+        if (namedArgs != null) {
+            throw new _MiscTemplateException(env, getNamedArgumentsNotSupportedMessage(callableValue, namedArgs[0]));
+        }
+
+        TemplateModel[] execArgs;
+        if (positionalArgs != null) {
+            execArgs = new TemplateModel[positionalArgs.length];
+            for (int i = 0; i < positionalArgs.length; i++) {
+                ASTExpression positionalArg = positionalArgs[i];
+                execArgs[i] = positionalArg.eval(env);
+            }
+        } else {
+            execArgs = Constants.EMPTY_TEMPLATE_MODEL_ARRAY;
+        }
+        return execArgs;
+    }
+
+    private static TemplateModel[] getExecuteArgsBasedOnLayout(
+            ASTExpression[] positionalArgs, NamedArgument[] namedArgs, ArgumentArrayLayout argsLayout,
+            TemplateCallableModel callableValue,
+            Environment env) throws TemplateException {
+        int predefPosArgCnt = argsLayout.getPredefinedPositionalArgumentCount();
+        int posVarargsArgIdx = argsLayout.getPositionalVarargsArgumentIndex();
+
+        TemplateModel[] execArgs = new TemplateModel[argsLayout.getTotalLength()];
+
+        // Fill predefined positional args:
+        if (positionalArgs != null) {
+            int actualPredefPosArgCnt = Math.min(positionalArgs.length, predefPosArgCnt);
+            for (int argIdx = 0; argIdx < actualPredefPosArgCnt; argIdx++) {
+                execArgs[argIdx] = positionalArgs[argIdx].eval(env);
+            }
+        }
+
+        if (posVarargsArgIdx != -1) {
+            int posVarargsLength = positionalArgs != null ? positionalArgs.length - predefPosArgCnt : 0;
+            TemplateSequenceModel varargsSeq;
+            if (posVarargsLength <= 0) {
+                varargsSeq = Constants.EMPTY_SEQUENCE;
+            } else {
+                NativeSequence nativeSeq = new NativeSequence(posVarargsLength);
+                varargsSeq = nativeSeq;
+                for (int posVarargIdx = 0; posVarargIdx < posVarargsLength; posVarargIdx++) {
+                    nativeSeq.add(positionalArgs[predefPosArgCnt + posVarargIdx].eval(env));
+                }
+            }
+            execArgs[posVarargsArgIdx] = varargsSeq;
+        } else if (positionalArgs != null && positionalArgs.length > predefPosArgCnt) {
+            checkSupportsAnyParameters(callableValue, argsLayout, env);
+            List<String> validPredefNames = argsLayout.getPredefinedNamedArgumentsMap().getKeys();
+            _ErrorDescriptionBuilder errorDesc = new _ErrorDescriptionBuilder(
+                    "The called ", FTLUtil.getCallableTypeName(callableValue), " ",
+                    (predefPosArgCnt != 0
+                            ? new Object[]{ "can only have ", predefPosArgCnt }
+                            : "can't have"
+                    ),
+                    " arguments passed by position, but the invocation has ",
+                    positionalArgs.length, " such arguments.",
+                    (!argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported() ?
+                            new Object[] {
+                                    " Try to pass arguments by name (as in ",
+                                    (callableValue instanceof TemplateDirectiveModel
+                                            ? "<@example x=1 y=2 />"
+                                            : "example(x=1, y=2)"),
+                                    ")",
+                                    (!validPredefNames.isEmpty()
+                                            ? new Object[] { " The supported parameter names are:\n",
+                                            new _DelayedJQuotedListing(validPredefNames)}
+                                            : _CollectionUtil.EMPTY_OBJECT_ARRAY)}
+                            : "")
+            );
+            if (callableValue instanceof Environment.TemplateLanguageDirective
+                    && !argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported()) {
+                errorDesc.tip("You can pass a parameter by position (i.e., without specifying its name, as you"
+                        + " have tried now) when the macro has defined that parameter to be a positional parameter. "
+                        + "See in the documentation how, and when that's a good practice.");
+            }
+            throw new _MiscTemplateException(env,
+                    errorDesc
+            );
+        }
+
+        int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex();
+        NativeHashEx2 namedVarargsHash = null;
+        if (namedArgs != null) {
+            StringToIndexMap predefNamedArgsMap = argsLayout.getPredefinedNamedArgumentsMap();
+            for (NamedArgument namedArg : namedArgs) {
+                int argIdx = predefNamedArgsMap.get(namedArg.name);
+                if (argIdx != -1) {
+                    execArgs[argIdx] = namedArg.value.eval(env);
+                } else {
+                    if (namedVarargsHash == null) {
+                        if (namedVarargsArgumentIndex == -1) {
+                            checkSupportsAnyParameters(callableValue, argsLayout, env);
+                            Collection<String> validNames = predefNamedArgsMap.getKeys();
+                            throw new _MiscTemplateException(env,
+                                    validNames == null || validNames.isEmpty()
+                                            ? getNamedArgumentsNotSupportedMessage(callableValue, namedArg)
+                                            : new Object[] {
+                                                    "The called ", FTLUtil.getCallableTypeName(callableValue),
+                                                    " has no parameter that's passed by name and is called ",
+                                                    new _DelayedJQuote(namedArg.name),
+                                                    ". The supported parameter names are:\n",
+                                                    new _DelayedJQuotedListing(validNames)
+                                            });
+                        }
+
+                        namedVarargsHash = new NativeHashEx2();
+                    }
+                    namedVarargsHash.put(namedArg.name, namedArg.value.eval(env));
+                }
+            }
+        }
+        if (namedVarargsArgumentIndex != -1) {
+            execArgs[namedVarargsArgumentIndex] = namedVarargsHash != null ? namedVarargsHash : Constants.EMPTY_HASH;
+        }
+        return execArgs;
+    }
+
+    private static Object[] getNamedArgumentsNotSupportedMessage(TemplateCallableModel callableValue,
+            NamedArgument namedArg) {
+        return new Object[] {
+                "The called ", FTLUtil.getCallableTypeName(callableValue),
+                " can't have arguments that are passed by name (like ",
+                new _DelayedJQuote(namedArg.name), "). Try to pass arguments by position "
+                + "(i.e, without name, as in ",
+                (callableValue instanceof TemplateDirectiveModel
+                        ? "<@example arg1, arg2, arg3 />"
+                        : "example(arg1, arg2, arg3)"),
+                ")."
+        };
+    }
+
+    static private void checkSupportsAnyParameters(
+            TemplateCallableModel callableValue, ArgumentArrayLayout argsLayout, Environment env)
+            throws TemplateException {
+        if (argsLayout.getTotalLength() == 0) {
+            throw new _MiscTemplateException(env,
+                    "The called ", FTLUtil.getCallableTypeName(callableValue), " doesn't support any parameters.");
+        }
+    }
+
+    static final class NamedArgument {
+        private final String name;
+        private final ASTExpression value;
+
+        NamedArgument(String name, ASTExpression value) {
+            this.name = name;
+            this.value = value;
+        }
+
+        String getName() {
+            return name;
+        }
+
+        ASTExpression getValue() {
+            return value;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java
index e974570..cc63aaf 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java
@@ -243,4 +243,18 @@ public final class ArgumentArrayLayout {
         return arrayLength;
     }
 
+    /**
+     * Tells if there can be any positional parameters (predefined or varargs).
+     */
+    public boolean isPositionalParametersSupported() {
+        return getPredefinedPositionalArgumentCount() != 0 || getPositionalVarargsArgumentIndex() != -1;
+    }
+
+    /**
+     * Tells if there can be any named parameters (predefined or varargs).
+     */
+    public boolean isNamedParametersSupported() {
+        return getPredefinedNamedArgumentsMap().size() != 0 || getNamedVarargsArgumentIndex() != -1;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b59a03a8/freemarker-core/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/javacc/FTL.jj b/freemarker-core/src/main/javacc/FTL.jj
index 4201d84..2ae31c0 100644
--- a/freemarker-core/src/main/javacc/FTL.jj
+++ b/freemarker-core/src/main/javacc/FTL.jj
@@ -35,7 +35,7 @@ import org.apache.freemarker.core.outputformat.impl.*;
 import org.apache.freemarker.core.model.*;
 import org.apache.freemarker.core.model.impl.*;
 import org.apache.freemarker.core.util.*;
-import org.apache.freemarker.core.ASTDynamicTopLevelCall.NamedArgument;
+import org.apache.freemarker.core._CallableUtils.NamedArgument;
 import java.io.*;
 import java.util.*;
 import java.nio.charset.Charset;
@@ -1838,7 +1838,7 @@ ASTExpression AddSubExpression(ASTExpression exp) :
         |
         result = DynamicKey(exp)
         |
-        result = MethodArgs(exp)
+        result = FunctionCall(exp)
         |
         result = ASTExpBuiltIn(exp)
         |
@@ -2038,25 +2038,115 @@ ASTExpression DynamicKey(ASTExpression exp) :
 }
 
 /**
- * production for an arglist part of a method invocation.
+ * This is the argument list part of a function/method invocation.
  */
-ASTExpFunctionCall MethodArgs(ASTExpression exp) :
+ASTExpFunctionCall FunctionCall(ASTExpression functionExp) :
 {
-        ArrayList args = new ArrayList();
-        Token end;
+    Token t;
+    ASTExpression exp;
+
+    Token start = null, end;
+    Token prevChoiceComma = null;
+    List<ASTExpression> positionalArgs = null;
+    List<NamedArgument> namedArgs = null;
 }
 {
-        <OPEN_PAREN>
-        args = PositionalArgs()
-        end = <CLOSE_PAREN>
-        {
-            args.trimToSize();
-            ASTExpFunctionCall result = new ASTExpFunctionCall(exp, args);
-            result.setLocation(template, exp, end);
-            return result;
+    start = <OPEN_PAREN>
+
+    (
+        LOOKAHEAD(<ID><ASSIGNMENT_EQUALS>)
+        (
+            t = <ID>
+            <ASSIGNMENT_EQUALS>
+            exp = ASTExpression()
+            {
+                if (prevChoiceComma == null && (positionalArgs != null || namedArgs != null)) {
+                        throw new ParseException("Missing comma (\",\") before expression. "
+                                + "Function call arguments must be separated by comma (even named arguments).", exp);
+                }
+                prevChoiceComma = null;
+
+                if (namedArgs == null) {
+                    namedArgs = new ArrayList<NamedArgument>(8);
+                }
+                namedArgs.add(new NamedArgument(t.image, exp));
+            }
+        )
+        |
+        (
+            // This was made to be choice its own, as we can give better error messages this way.
+            t = <COMMA>
+            {
+                if (prevChoiceComma != null) {
+                    throw new ParseException("Two commas (\",\") without argument between.",
+                            template, t);
+                }
+                prevChoiceComma = t;
+
+                if (namedArgs == null && positionalArgs == null) {
+                    throw new ParseException(
+                            "Remove comma (\",\"); it can only be used between arguments.",
+                            template, t);
+                }
+            }
+        )
+        |
+        (
+            exp = ASTExpression()
+            {
+                if (namedArgs != null) {
+                    throw new ParseException(
+                            "Expression looks like an argument passed by position (no preceding \"<argName>=\"), "
+                            + " but those must be earlier than arguments passed by name.",
+                            exp);
+                }
+
+                if (prevChoiceComma == null && (positionalArgs != null || namedArgs != null)) {
+                        throw new ParseException("Missing comma (\",\") before expression. "
+                                + "Function call arguments must be separated by comma.", exp);
+                }
+                prevChoiceComma = null;
+
+                if (positionalArgs == null) {
+                    positionalArgs = new ArrayList<ASTExpression>(8);
+                }
+                positionalArgs.add(exp);
+            }
+        )
+    )*
+
+    end = <CLOSE_PAREN>
+
+    {
+        ASTExpression[] trimmedPositionalArgs;
+        if (positionalArgs == null) {
+            trimmedPositionalArgs = null;
+        } else {
+            int len = positionalArgs.size();
+            trimmedPositionalArgs = new ASTExpression[len];
+            for (int i = 0; i < len; i++) {
+                trimmedPositionalArgs[i] = positionalArgs.get(i);
+            }
         }
+
+        NamedArgument[] trimmedNamedArgs;
+        if (namedArgs == null) {
+            trimmedNamedArgs = null;
+        } else {
+            int len = namedArgs.size();
+            trimmedNamedArgs = new NamedArgument[len];
+            for (int i = 0; i < len; i++) {
+                trimmedNamedArgs[i] = namedArgs.get(i);
+            }
+        }
+
+        ASTExpFunctionCall result = new ASTExpFunctionCall(functionExp, trimmedPositionalArgs, trimmedNamedArgs);
+        result.setLocation(template, start, end);
+        return result;
+    }
 }
 
+
 ASTExpStringLiteral ASTExpStringLiteral(boolean interpolate) :
 {
     Token t;


[2/2] incubator-freemarker git commit: FREEMARKER-64: Merged: Function (and thus also method) call syntax now supports passing parameter by name.

Posted by dd...@apache.org.
FREEMARKER-64: Merged: Function (and thus also method) call syntax now supports passing parameter by name.

Merge commit 'refs/pull/33/head' of https://github.com/apache/incubator-freemarker into 3


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/1b6f894e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/1b6f894e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/1b6f894e

Branch: refs/heads/3
Commit: 1b6f894ee14351d320d434fc1f6f547d29f3335e
Parents: 89d4369 b59a03a
Author: ddekany <dd...@apache.org>
Authored: Tue Aug 8 13:54:45 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Tue Aug 8 13:56:06 2017 +0200

----------------------------------------------------------------------
 .../core/TemplateCallableModelTest.java         |  36 +++-
 .../core/userpkg/AllFeaturesFunction.java       |  29 ++-
 .../core/userpkg/NamedVarargsOnlyFunction.java  |  61 ++++++
 .../userpkg/PositionalVarargsOnlyFunction.java  |  19 +-
 .../core/userpkg/TestTemplateCallableModel.java |  58 +++---
 .../core/userpkg/TwoNamedParamsFunction.java    |  71 +++++++
 .../userpkg/TwoPositionalParamsFunction.java    |  21 +-
 .../templates/string-builtins3.ftl              |  16 +-
 .../freemarker/core/ASTDynamicTopLevelCall.java | 154 +-------------
 .../freemarker/core/ASTExpFunctionCall.java     | 201 +++++++++++--------
 .../apache/freemarker/core/_CallableUtils.java  | 180 ++++++++++++++++-
 .../core/model/ArgumentArrayLayout.java         |  14 ++
 freemarker-core/src/main/javacc/FTL.jj          | 118 +++++++++--
 13 files changed, 650 insertions(+), 328 deletions(-)
----------------------------------------------------------------------