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(-)
----------------------------------------------------------------------