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/07 22:32:22 UTC

[20/21] incubator-freemarker git commit: FREEMARKER-63: Lot of refinement in the API-s and implementation. #macro now creates a `TemplateDirectiveModel`, and #function now creates `TemplateFunctionModel` (though the function/method call syntax doesn't ye

FREEMARKER-63: Lot of refinement in the API-s and implementation. #macro now creates a `TemplateDirectiveModel`, and #function now creates `TemplateFunctionModel` (though the function/method call syntax doesn't yet allow named parameters). Test suite passes.


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

Branch: refs/heads/3
Commit: 3cacd9ed04a231af67e3964525fd592b97da1665
Parents: da4c332
Author: ddekany <dd...@apache.org>
Authored: Thu Aug 3 23:11:57 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Thu Aug 3 23:11:57 2017 +0200

----------------------------------------------------------------------
 FM3-CHANGE-LOG.txt                              |  48 ++-
 .../freemarker/core/CanonicalFormTest.java      |   4 +-
 .../freemarker/core/DirectiveCallPlaceTest.java |   1 -
 .../EnvironmentGetTemplateVariantsTest.java     |   1 -
 .../core/MistakenlyPublicMacroAPIsTest.java     |  21 +-
 .../core/ParsingErrorMessagesTest.java          |  65 +++-
 .../core/TemplateCallableModelTest.java         | 140 +++++--
 .../core/TheadInterruptingSupportTest.java      |   1 -
 .../core/userpkg/AllFeaturesDirective.java      |   9 +-
 .../core/userpkg/AllFeaturesFunction.java       | 114 ++++++
 .../core/userpkg/NamedVarargsOnlyDirective.java |   5 +-
 .../userpkg/PositionalVarargsOnlyDirective.java |  13 +-
 .../userpkg/PositionalVarargsOnlyFunction.java  |  62 +++
 .../core/userpkg/TestTemplateCallableModel.java |  88 +++++
 .../userpkg/TestTemplateDirectiveModel.java     |  88 -----
 .../core/userpkg/TwoNamedParamsDirective.java   |   5 +-
 .../TwoNestedContentParamsDirective.java        |   5 +-
 .../userpkg/TwoPositionalParamsDirective.java   |   5 +-
 .../userpkg/TwoPositionalParamsFunction.java    |  63 +++
 .../core/userpkg/UpperCaseDirective.java        |   2 +-
 .../core/util/StringToIndexMapTest.java         |   5 +
 .../core/valueformat/NumberFormatTest.java      |   2 +-
 .../org/apache/freemarker/core/ast-1.ast        |  24 +-
 .../apache/freemarker/core/ast-assignments.ast  |   3 +-
 .../freemarker/core/cano-macro-and-function.ftl |  43 +++
 .../core/cano-macro-and-function.ftl.out        |  42 ++
 .../org/apache/freemarker/core/cano-macros.ftl  |  29 --
 .../apache/freemarker/core/cano-macros.ftl.out  |  28 --
 .../core/templatesuite/expected/macros2.txt     |  22 --
 .../templatesuite/templates/api-builtins.ftl    |  20 +-
 .../core/templatesuite/templates/boolean.ftl    |   8 +-
 .../templatesuite/templates/comparisons.ftl     | 216 +++++------
 .../templates/date-type-builtins.ftl            |  32 +-
 .../templatesuite/templates/dateparsing.ftl     |   4 +-
 .../templates/existence-operators.ftl           |  34 +-
 .../core/templatesuite/templates/hashconcat.ftl |   2 +-
 .../templates/identifier-non-ascii.ftl          |   2 +-
 .../core/templatesuite/templates/list.ftl       |   2 +-
 .../core/templatesuite/templates/list2.ftl      |   2 +-
 .../core/templatesuite/templates/list3.ftl      |   8 +-
 .../core/templatesuite/templates/listhash.ftl   |   2 +-
 .../core/templatesuite/templates/macros.ftl     |  17 +-
 .../core/templatesuite/templates/macros2.ftl    |  35 --
 .../templates/number-math-builtins.ftl          |  68 ++--
 .../core/templatesuite/templates/recover.ftl    |   8 +-
 .../core/templatesuite/templates/root.ftl       |   2 +-
 .../templates/sequence-builtins.ftl             |  18 +-
 .../templates/string-builtin-coercion.ftl       |  24 +-
 .../templates/string-builtins-regexps.ftl       |   6 +-
 .../core/templatesuite/templates/var-layers.ftl |   2 +-
 .../freemarker/core/templatesuite/testcases.xml |   1 -
 .../org/apache/freemarker/core/ASTDirMacro.java | 325 ----------------
 .../freemarker/core/ASTDirMacroOrFunction.java  | 369 ++++++++++++++++++
 .../apache/freemarker/core/ASTDirNested.java    |  91 ++---
 .../apache/freemarker/core/ASTDirReturn.java    |   2 +-
 .../freemarker/core/ASTDynamicTopLevelCall.java | 164 ++++----
 .../org/apache/freemarker/core/ASTElement.java  |   4 +-
 .../freemarker/core/ASTExpBuiltInVariable.java  |   4 +-
 .../freemarker/core/ASTExpListLiteral.java      |   8 +-
 .../freemarker/core/ASTExpMethodCall.java       | 117 +++++-
 .../apache/freemarker/core/ASTStaticText.java   |   2 +-
 .../core/BuiltInsForMultipleTypes.java          |  14 +-
 .../freemarker/core/BuiltInsForStringsMisc.java |   1 -
 .../org/apache/freemarker/core/CallPlace.java   | 195 ++++++++++
 ...lPlaceCustomDataInitializationException.java |   3 +-
 .../org/apache/freemarker/core/Environment.java | 379 +++++++++++--------
 .../apache/freemarker/core/LocalContext.java    |   2 +-
 .../org/apache/freemarker/core/MessageUtil.java |   2 +-
 .../freemarker/core/NonDirectiveException.java  |  63 +++
 .../freemarker/core/NonTemplateCallPlace.java   | 166 ++++++++
 .../NonUserDefinedDirectiveLikeException.java   |  65 ----
 .../apache/freemarker/core/ParameterRole.java   |   6 +-
 .../org/apache/freemarker/core/Template.java    |   2 +-
 .../core/TemplateCallableModelUtils.java        |  46 ---
 ...nterruptionSupportTemplatePostProcessor.java |   1 -
 .../org/apache/freemarker/core/_CoreAPI.java    |  10 +-
 .../core/_ErrorDescriptionBuilder.java          |   5 +-
 .../core/_TemplateCallableModelUtils.java       | 112 ++++++
 .../core/model/ArgumentArrayLayout.java         | 101 +++--
 .../apache/freemarker/core/model/CallPlace.java | 179 ---------
 .../apache/freemarker/core/model/Constants.java |   2 +
 .../core/model/TemplateCallableModel.java       |   6 +-
 .../core/model/TemplateDirectiveModel.java      |  43 ++-
 .../core/model/TemplateFunctionModel.java       |  41 +-
 .../core/util/DuplicateStringKeyException.java  |  38 ++
 .../apache/freemarker/core/util/FTLUtil.java    |  40 +-
 .../freemarker/core/util/StringToIndexMap.java  |  29 +-
 freemarker-core/src/main/javacc/FTL.jj          | 328 ++++++++++------
 .../examples/AutoEscapingExample-infoBox.ftlh   |   2 +-
 .../apache/freemarker/servlet/IncludePage.java  |   2 +-
 .../jsp/CustomTagAndELFunctionCombiner.java     |   2 +-
 .../servlet/jsp/SimpleTagDirectiveModel.java    |   2 +-
 .../servlet/jsp/TagDirectiveModel.java          |   2 +-
 .../test/templateutil/AssertDirective.java      |  16 +-
 .../templateutil/AssertEqualsDirective.java     |   2 +-
 .../test/templateutil/AssertFailsDirective.java |   2 +-
 .../test/templateutil/NoOutputDirective.java    |   2 +-
 97 files changed, 2709 insertions(+), 1739 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/FM3-CHANGE-LOG.txt b/FM3-CHANGE-LOG.txt
index 21ce533..aab66b7 100644
--- a/FM3-CHANGE-LOG.txt
+++ b/FM3-CHANGE-LOG.txt
@@ -48,7 +48,21 @@ Major changes / features
   Examples:
   `<@x x + 1 2 3 />` now must be written as `<@x x + 1, 2, 3 />`
   `<#nested x y>` now must be written as `<#nested x, y>`
-- TemplateDirectiveModel was redesigned to be more universal.
+- #macro-s and #function  
+  - Both #macro-s and #function-s can now define parameters that are passed by position, and that are passed by name.
+    Function parameters are by default positional, which can be changed by adding `{named}` after the parameter name.
+    Macro parameters are by default named, which can be changed by adding `{positional}` after the parameter name.
+    Positional parameters always precede the named ones. There can be both a positional and a named varargs parameter;
+    the first is always a sequnce, the later is always a hash.
+    Examples:
+    - All named: `<#macro heading title icon>...</#macro>` `<@heading title="Test" icon="foo.jpg" />`
+    - Mixed: `<#macro heading title{positional} icon>...</#macro>` `<@heading "Test" icon="foo.jpg" />`
+    - All positional: `<#function f(x, y, r=0)>...</#function>` `${f(1, 2)} ${f(1, 2, 10)}`
+    - Mixed: `<#function f(x, y, r{named}=0)>...</#function>` `${f(1, 2)} ${f(1, 2, r=10)}`
+    - Positional varargs: `<#function sum(xs... abs{named}=false)>...</#function>` `${sum(1, 2, 3, abs=true)}
+    - Positional and named varargs: `<#function f(xs..., props{named}...)>...</#function>` `${f(1, 2, 3, x=1, y=2)}`
+  - In FM2, the same macro could be called with specifying all parameters by position, or by specifying all parameters
+    by name. In FM3 that won't work anymore, as now a parameter is either strictly positional or strictly named.
 
 Smaller changes
 ---------------
@@ -104,13 +118,25 @@ Node: Changes already mentioned above aren't repeated here!
   - TemplateDirectiveModel was redesigend (see in the major changes section)
   - Removed `TemplateTransformModel`; `TemplateDirectiveModel` can do the same things, and more.
   - Renamed `DirectiveCallPlace` to `CallPlace`
-  - Removed Environment.getDirectiveCallPlace(), as TemplateDirectiveModel-s now get the CallPlace as the parameter
-    of the `execute` method.
+  - Removed Environment.getDirectiveCallPlace(), as TemplateDirectiveModel-s now get the CallPlace as the
+    parameter of the `execute` method.
   - ?isTransform was removed (as there are no transforms anymore).
     Converter note: The template converter tool replaces it with ?isDirective
 - The directive returned by `?interpret` doesn't allow nested content anymore. (It wasn't useful earlier either;
   the nested content was simply executed after the interpreted string.)
-
+- Changes in #macro/#functions
+  - See major changes first, in the earlier chapter
+  - It's not tolerated anymore if the caller of a macro has declared more nested content parameters than
+    what `#nested` passes to it (like in `<#macro m><#nested 1, 2></#macro> <@m ; i, j, k>...</@>`, where `k` has
+    no corresponding value in `#nested`).
+  - In `#macro` (and `function`) named parameters with default values need not be at the end of the parameter list
+    anymore. (Positional parameters with default values need to be at the end of the list of positional parameters
+    only.)
+  - When parameter default expressions are evaluated, only the parameters defined earlier are already set.
+    So `<#macro m a b=a>` works, but `<#macro m a=b b>` won't work anymore (unless there's `b` outside the
+    parameter list), as `b` is not yet set when the default of `a` is calculated.
+
+  
 Java API changes
 ================
 
@@ -165,7 +191,18 @@ Major changes / features
 - Configuration is now immutable. You should use Configuration.Builder to set up the setting values, then create
   the Configuration with the builder's build() method.
 - Configuration defaults were changed to follow the current best practices (like default charset is UTF-8 everywhere)
-- Removed freemarker.ext.log, our log abstraction layer from the old times when there was no clear winner on this field.
+- Reworked callable `TemplateModel`-s (i.e., models that can be called like `foo(...)` or as `<@foo .../>`)
+  - Earlier there were several callable `TemplateModel` internfaces (`TemplateMethodModel`, `TemplateMethodModelEx`,
+    `TemplateDirectiveModel`, `TemplateTransformModel`). FM3 replaces them with only two new interfaces,
+    `TemplateDirectiveModel` (differs from the interface with identical name in FM2) and `TemplateFunctionModel`.
+    (These are both the subinterfaces of another new interface `TemplateCallableModel`.)
+    [TODO: TemplateMethodModel[Ex] wasn't yet replaced by TemplateFunctionModel.]
+  - All callable TempalteModel-s support passing parameters by position and by name, even in the same call
+    (e.g., `<@heading "Some title" icon="foo.jpg" />`, `sum(1, 2, 3, abs=true)`)
+  - `#macro` now produces a `TemplateDirectiveModel` and `#function` produces a `TemplateFunctionModel`. (Earlier, the
+    product was just a generic `TemplateModel` that could only be invoked using internal API-s, and had capabilities
+    that the callable TemplateModel-s coulnd't have.)
+- Removed freemarker.ext.log, our log abstraction layer from the times when there was no clear winner on this field.
   Added org.slf4j:slf4j-api as required dependency instead.
 - Added Spring support to the FreeMarker project (freemarker-spring module), instead of relying Spring developers
   [Note: This is in very early stage, as of 2017-07-06.]
@@ -426,6 +463,7 @@ Core / Miscellaneous
   "compress"
 - Removed `NestedContentNotSupportedException`, as `TemplateDirectiveModel.isNestedContentSupported()` now takes care
   of that problem.
+- CallPlaceCustomDataInitializationException is not a checked exception anymore (now it extends RuntimeException)
 
 
 Build / development process changes

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
index fd1a5a5..1bf4888 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
@@ -33,8 +33,8 @@ public class CanonicalFormTest extends FileTestCase {
         super(name);
     }
 
-    public void testMacrosCanonicalForm() throws Exception {
-        assertCanonicalFormOf("cano-macros.ftl");
+    public void testCallableDefinitionCanonicalForm() throws Exception {
+        assertCanonicalFormOf("cano-macro-and-function.ftl");
     }
     
     public void testIdentifierEscapingCanonicalForm() throws Exception {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
index f959b1d..7ee42c2 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
@@ -27,7 +27,6 @@ import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
-import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModel;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
index 10205e9..a094ec6 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
@@ -26,7 +26,6 @@ import java.io.Writer;
 import java.util.Collections;
 
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
-import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.impl.SimpleScalar;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
index 9c87e61..ad27627 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
@@ -19,15 +19,12 @@
 
 package org.apache.freemarker.core;
 
-import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
 import java.io.StringWriter;
 import java.util.Map;
 
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.util._NullWriter;
 import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
@@ -44,7 +41,7 @@ public class MistakenlyPublicMacroAPIsTest {
     @Test
     public void testMacroCopyingExploit() throws IOException, TemplateException {
         Template tMacros = new Template(null, "<#macro m1>1</#macro><#macro m2>2</#macro>", cfg);
-        Map<String, ASTDirMacro> macros = tMacros.getMacros();
+        Map<String, ASTDirMacroOrFunction> macros = tMacros.getMacros();
         
         Template t = new Template(null,
                 "<@m1/><@m2/><@m3/>"
@@ -60,25 +57,11 @@ public class MistakenlyPublicMacroAPIsTest {
     public void testMacroCopyingExploitAndNamespaces() throws IOException, TemplateException {
         Template tMacros = new Template(null, "<#assign x = 0><#macro m1>${x}</#macro>", cfg);
         Template t = new Template(null, "<#assign x = 1><@m1/>", cfg);
-        t.addMacro((ASTDirMacro) tMacros.getMacros().get("m1"));
+        t.addMacro((ASTDirMacroOrFunction) tMacros.getMacros().get("m1"));
         
         assertEquals("1", getTemplateOutput(t));
     }
 
-    @Test
-    public void testMacroCopyingFromFTLVariable() throws IOException, TemplateException {
-        Template tMacros = new Template(null, "<#assign x = 0><#macro m1>${x}</#macro>", cfg);
-        Environment env = tMacros.createProcessingEnvironment(null, _NullWriter.INSTANCE);
-        env.process();
-        TemplateModel m1 = env.getVariable("m1");
-        assertThat(m1, instanceOf(ASTDirMacro.class));
-        
-        Template t = new Template(null, "<#assign x = 1><@m1/>", cfg);
-        t.addMacro((ASTDirMacro) m1);
-        
-        assertEquals("1", getTemplateOutput(t));
-    }
-    
     private String getTemplateOutput(Template t) throws TemplateException, IOException {
         StringWriter sw = new StringWriter();
         t.process(null, sw);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
index 77dbe0a..3189615 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
@@ -62,7 +62,9 @@ public class ParsingErrorMessagesTest {
     @Test
     public void testUnclosedDirectives() {
         assertErrorContains("<#macro x>", "#macro", "unclosed");
+        assertErrorContains("<#macro x></#function>", "macro end tag");
         assertErrorContains("<#function x()>", "#macro", "unclosed");
+        assertErrorContains("<#function x()></#macro>", "function end tag");
         assertErrorContains("<#assign x>", "#assign", "unclosed");
         assertErrorContains("<#macro m><#local x>", "#local", "unclosed");
         assertErrorContains("<#global x>", "#global", "unclosed");
@@ -122,15 +124,62 @@ public class ParsingErrorMessagesTest {
         assertErrorContains("<#assign x = x}>", "\"}\"", "open");
         // TODO assertErrorContains("<#assign x = '${x'>", "unclosed");
     }
-    
+
+    @Test
+    public void testUnknownHeaderParameter() {
+        assertErrorContains("<#ftl foo=1>", "Unknown", "foo");
+        assertErrorContains("<#ftl attributes={}>", "Unknown", "attributes", "customSettings");
+    }
+
+    @Test
+    public void testDynamicTopCalls() throws IOException, TemplateException {
+        assertErrorContains("<@a, n1=1 />", "Remove comma", "between", "by position");
+        assertErrorContains("<@a n1=1, n2=1 />", "Remove comma", "between", "by position");
+        assertErrorContains("<@a n1=1, 2 />", "Remove comma", "between", "by position");
+        assertErrorContains("<@a, 1 />", "Remove comma", "between", "by position");
+        assertErrorContains("<@a 1, , 2 />", "Two commas");
+        assertErrorContains("<@a 1 2 />", "Missing comma");
+        assertErrorContains("<@a n1=1 2 />", "must be earlier than arguments passed by name");
+    }
+
+    @Test
+    public void testMacroAndFunctionDefinitions() {
+        assertErrorContains("<#macro m><#macro n></#macro></#macro>", "nested into each other");
+        assertErrorContains("<#macro m(a)></#macro>", "can't use \"(\"");
+        assertErrorContains("<#function f a></#function>", "must use \"(\"");
+        assertErrorContains("<#macro m a{badOption})></#macro>", "\"badOption\"",
+                "\"" + ASTDirMacroOrFunction.POSITIONAL_PARAMETER_OPTION_NAME + "\"",
+                "\"" + ASTDirMacroOrFunction.NAMED_PARAMETER_OPTION_NAME + "\"");
+        assertErrorContains("<#function f(a{named}, b)></#function>", "Positional", "must precede named");
+        assertErrorContains("<#function f(a..., b)></#function>", "another", "after", "positional varargs");
+        assertErrorContains("<#function f(a..., b...)></#function>", "another", "after", "positional varargs");
+        assertErrorContains("<#macro m a... b></#macro>", "another", "after", "named varargs");
+        assertErrorContains("<#macro m a... b...></#macro>", "another", "after", "named varargs");
+        assertErrorContains("<#function f(a b)></#function>", "Function", "must have comma");
+        assertErrorContains("<#function f(a{named} b{named})></#function>", "Function", "must have comma");
+        assertErrorContains("<#macro m a, b></#macro>", "Named param", "macro", "need no comma");
+        assertErrorContains("<#macro m a{positional} b{positional}></#macro>",
+                "Positional param", "must have comma");
+        assertErrorContains("<#macro m a...=[]></#macro>", "Varargs", "default");
+        assertErrorContains("<#function f(a=0, b)></#function>", "with default", "without a default");
+        assertErrorContains("<#function f(a,)></#function>", "Comma without");
+        assertErrorContains("<#macro m a{positional}, b{positional},></#macro>", "Comma without");
+        assertErrorContains(false, "<#function f(a, b></#function>");
+        assertErrorContains(false, "<#function f(></#function>");
+        assertErrorContains(false, "[#ftl][#function f(a, b][/#function]", "Missing closing \")\"");
+        assertErrorContains(false, "[#ftl][#function f(][/#function]", "Missing closing \")\"");
+        assertErrorContains("<#macro m a b)></#macro>", "\")\" without", "opening");
+        assertErrorContains("<#macro m a b a></#macro>", "\"a\"", "multiple");
+    }
+
     private void assertErrorContains(String ftl, String... expectedSubstrings) {
         assertErrorContains(false, ftl, expectedSubstrings);
         assertErrorContains(true, ftl, expectedSubstrings);
     }
 
-    private void assertErrorContains(boolean squareTags, String ftl, String... expectedSubstrings) {
+    private void assertErrorContains(boolean convertToSquare, String ftl, String... expectedSubstrings) {
         try {
-            if (squareTags) {
+            if (convertToSquare) {
                 ftl = ftl.replace('<', '[').replace('>', ']');
             }
             new Template("adhoc", ftl, cfg);
@@ -139,7 +188,7 @@ public class ParsingErrorMessagesTest {
             String msg = e.getMessage();
             for (String needle: expectedSubstrings) {
                 if (needle.startsWith("\\!")) {
-                    String netNeedle = needle.substring(2); 
+                    String netNeedle = needle.substring(2);
                     if (msg.contains(netNeedle)) {
                         fail("The message shouldn't contain substring " + _StringUtil.jQuote(netNeedle) + ":\n" + msg);
                     }
@@ -153,15 +202,9 @@ public class ParsingErrorMessagesTest {
             throw new RuntimeException(e);
         }
     }
-    
+
     private void showError(Throwable e) {
         //System.out.println(e);
     }
 
-    @Test
-    public void testUnknownHeaderParameter() {
-        assertErrorContains("<#ftl foo=1>", "Unknown", "foo");
-        assertErrorContains("<#ftl attributes={}>", "Unknown", "attributes", "customSettings");
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/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 b7d55f5..3459aea 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
@@ -22,11 +22,14 @@ package org.apache.freemarker.core;
 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.PositionalVarargsOnlyDirective;
+import org.apache.freemarker.core.userpkg.PositionalVarargsOnlyFunction;
 import org.apache.freemarker.core.userpkg.TwoNamedParamsDirective;
 import org.apache.freemarker.core.userpkg.TwoNestedContentParamsDirective;
 import org.apache.freemarker.core.userpkg.TwoPositionalParamsDirective;
+import org.apache.freemarker.core.userpkg.TwoPositionalParamsFunction;
 import org.apache.freemarker.core.userpkg.UpperCaseDirective;
 import org.apache.freemarker.test.TemplateTest;
 import org.junit.Before;
@@ -36,15 +39,20 @@ public class TemplateCallableModelTest extends TemplateTest {
 
     @Before
     public void addCommonData() {
-        addToDataModel("a", new AllFeaturesDirective());
+        addToDataModel("a", AllFeaturesDirective.INSTANCE);
         addToDataModel("p", TwoPositionalParamsDirective.INSTANCE);
         addToDataModel("n", TwoNamedParamsDirective.INSTANCE);
         addToDataModel("pvo", PositionalVarargsOnlyDirective.INSTANCE);
         addToDataModel("nvo", NamedVarargsOnlyDirective.INSTANCE);
+        addToDataModel("uc", UpperCaseDirective.INSTANCE);
+
+        addToDataModel("fa", AllFeaturesFunction.INSTANCE);
+        addToDataModel("fp", TwoPositionalParamsFunction.INSTANCE);
+        addToDataModel("fpvo", PositionalVarargsOnlyFunction.INSTANCE);
     }
 
     @Test
-    public void testArguments() throws IOException, TemplateException {
+    public void testDirectiveArguments() throws IOException, TemplateException {
         assertOutput("<@p />",
                 "#p(p1=null, p2=null)");
         assertOutput("<@p 1 />",
@@ -103,6 +111,34 @@ public class TemplateCallableModelTest extends TemplateTest {
     }
 
     @Test
+    public void testFunctionArguments() throws IOException, TemplateException {
+        // TODO [FM3] Add more tests as named parameters become supported
+
+        assertOutput("${fp()}",
+                "fp(p1=null, p2=null)");
+        assertOutput("${fp(1)}",
+                "fp(p1=1, p2=null)");
+        assertOutput("${fp(1, 2)}",
+                "fp(p1=1, p2=2)");
+
+        assertOutput("${fpvo()}",
+                "fpvo(pVarargs=[])");
+        assertOutput("${fpvo(1)}",
+                "fpvo(pVarargs=[1])");
+        assertOutput("${fpvo(1, 2)}",
+                "fpvo(pVarargs=[1, 2])");
+
+        assertOutput("${fa()}",
+                "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={})");
+    }
+
+    @Test
     public void testNestedContent() throws IOException, TemplateException {
         assertOutput("<@a />",
                 "#a(p1=null, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={})");
@@ -140,18 +176,16 @@ public class TemplateCallableModelTest extends TemplateTest {
 
         assertOutput("<@a + 1 />",
                 "#a(p1=1, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={})");
-    }
 
-    @Test
-    @SuppressWarnings("ThrowableNotThrown")
-    public void testParsingErrors() throws IOException, TemplateException {
-        assertErrorContains("<@a, n1=1 />", "Remove comma", "between", "by position");
-        assertErrorContains("<@a n1=1, n2=1 />", "Remove comma", "between", "by position");
-        assertErrorContains("<@a n1=1, 2 />", "Remove comma", "between", "by position");
-        assertErrorContains("<@a, 1 />", "Remove comma", "between", "by position");
-        assertErrorContains("<@a 1, , 2 />", "Two commas");
-        assertErrorContains("<@a 1 2 />", "Missing comma");
-        assertErrorContains("<@a n1=1 2 />", "must be earlier than arguments passed by name");
+        assertOutput("<@nvo a=x! 1 b=2 />",
+                "#nvo(nVarargs={\"a\": 1, \"b\": 2})");
+        assertOutput("<@nvo a=x! b=2 />",
+                "#nvo(nVarargs={\"a\": \"\", \"b\": 2})");
+        assertOutput("<@nvo a=x!b=2 />",
+                "#nvo(nVarargs={\"a\": \"\", \"b\": 2})");
+
+        assertOutput("<@nvo a=(1)b=2 />",
+                "#nvo(nVarargs={\"a\": 1, \"b\": 2})");
     }
 
     @Test
@@ -160,7 +194,9 @@ public class TemplateCallableModelTest extends TemplateTest {
         assertErrorContains("<@p 9, 9, 9 />", "can only have 2", "3", "by position");
         assertErrorContains("<@n 9 />", "can't have arguments passed by position");
         assertErrorContains("<@n n3=9 />", "has no", "\"n3\"", "supported", "\"n1\", \"n2\"");
-        assertErrorContains("<@p n1=9 />", "doesn't have any by-name-passed");
+        assertErrorContains("<@p n1=9 />", "directive", "can't have arguments that are passed by name", "\"n1\"");
+        assertErrorContains("<@uc n1=9 />", "directive", "doesn't support any parameters");
+        assertErrorContains("<@uc 9 />", "directive", "doesn't support any parameters");
 
         addToDataModel("tncp", TwoNestedContentParamsDirective.INSTANCE);
         assertErrorContains("<@tncp />", " no ", " 2 ");
@@ -175,18 +211,76 @@ public class TemplateCallableModelTest extends TemplateTest {
 
     @Test
     public void testMacros() throws IOException, TemplateException {
+        assertOutput("<#macro m>text</#macro><@m /> <@m />",
+                "text text");
+        assertOutput("<#macro m a>text ${a}</#macro><@m a=1 /> <@m a=2 />",
+                "text 1 text 2");
+        assertOutput("<#macro m a b=2>text ${a} ${b}</#macro><@m a=1 /> <@m a=11 b=22 />",
+                "text 1 2 text 11 22");
+        assertOutput("<#macro m a{positional}>text ${a}</#macro><@m 1 /> <@m 2 />",
+                "text 1 text 2");
+        assertOutput("<#macro m a{positional}, b{positional}=2>text ${a} ${b}</#macro><@m 1 /> <@m 11, 22 />",
+                "text 1 2 text 11 22");
+        assertOutput("<#macro m a{positional}, b{positional}=2 c d=4>text ${a} ${b} ${c} ${d}</#macro>"
+                        + "<@m 1 c=3 /> <@m 11, 22 c=33 d=44 />",
+                "text 1 2 3 4 text 11 22 33 44");
+
+        assertOutput("<#macro m>[<#n...@m>",
+                "[text]");
+        assertOutput("<#macro m>[<#nested 1>, <#nested 2>]</#macro><@m ; i>text ${i}</...@m>",
+                "[text 1, text 2]");
+        assertOutput("<#macro m>[<#nested 1, 2>]</#macro><@m ; i, j>${i} ${j}</...@m>",
+                "[1 2]");
         assertOutput("<#macro m a b=22><#list 1..2 as n>[<#nested a * n, b * n>]</#list></#macro>"
-                + "<@m 11; i, j>${i} ${j}</...@m> <@m a=1 b=2; i, j>${i} ${j}</...@m>",
+                + "<@m a=11; i, j>${i} ${j}</...@m> <@m a=1 b=2; i, j>${i} ${j}</...@m>",
                 "[11 22][22 44] [1 2][2 4]");
-        assertOutput("<#macro m a b others...>[a=${a}, b=${b}<#if others?hasContent>, </#if>"
-                        + "<#if others?isSequence>"
-                        + "<#list others as v>${v}<#sep>, </#list>"
-                        + "<#else>"
-                        + "<#list others as k, v>${k}=${v}<#sep>, </#list>"
-                        + "</#if>]"
+        assertOutput("<#macro m1><#local x = 1>${x}{<@m2>${x}<<#list [3] as x>${x}</#...@m2>}${x}</#macro>"
+                        + "<#macro m2><#local x = 2>${x}[<#nested>]${x}</#macro>"
+                        + "<#assign x = 0>${x}(<@m1 />)${x}",
+                "0(1{2[1<3>1]2}1)0");
+        assertOutput("<#macro m1>"
+                        + "<#local x = 0>${x} <#list [1, 2] as x>${x}{<@m...@m2>}${x}<#sep> </#list> ${x}"
                         + "</#macro>"
-                        + "<@m 1, 2 /> <@m 1, 2, 3, 4 /> <@m a=1 b=2 /> <@m a=1 b=2 c=3 d=4 />",
-                "[a=1, b=2] [a=1, b=2, 3, 4] [a=1, b=2] [a=1, b=2, c=3, d=4]");
+                        + "<#macro m2><#list [3, 4] as x>${x}[<#nested>]${x}<#sep> </#list></#macro>"
+                        + "<@m1 />",
+                "0 1{3[1]3 4[1]4}1 2{3[2]3 4[2]4}2 0");
+
+        assertOutput("<#macro m a b=0 others...>["
+                        + "a=${a}, b=${b}"
+                        + "<#list others>, <#items as k, v>${k}=${v}<#sep>, </#items></#list>"
+                        + "]</#macro>"
+                        + "<@m a=1 /> <@m a=1 b=2 /> <@m a=1 b=2 c=3 d=4 />",
+                "[a=1, b=0] [a=1, b=2] [a=1, b=2, c=3, d=4]");
+        assertOutput("<#macro m a{positional}, b{positional}=0, others{positional}...>["
+                        + "a=${a}, b=${b}"
+                        + "<#list others>, <#items as v>${v}<#sep>, </#items></#list>"
+                        + "]</#macro>"
+                        + "<@m 1 /> <@m 1, 2 /> <@m 1, 2, 3, 4 />",
+                "[a=1, b=0] [a=1, b=2] [a=1, b=2, 3, 4]");
+        assertOutput("<#macro m pVarargs{positional}... nVarargs...>"
+                        + "[<#list pVarargs as v>${v}<#sep>, </#list>]"
+                        + "{<#list nVarargs as k, v>${k}=${v}<#sep>, </#list>}"
+                        + "</#macro>"
+                        + "<@m 1, 2 a=1 b=2 /> <@m 1, 2 /> <@m a=1 b=2 />",
+                "[1, 2]{a=1, b=2} [1, 2]{} []{a=1, b=2}");
+
+        assertOutput("<#macro m x{positional}, y{positional}><#local y++><#local z = x + y>${x} + ${y} = ${z}</#macro>"
+                        + "<@m 1, 2 />",
+                "1 + 3 = 4");
+
+        // Default expression sees previous argument:
+        assertOutput("<#macro m a{positional} b=a>${a}${b}</#macro><@m 1/> <@m 2 b=3/>", "11 23");
+
+        addTemplate("lib.ftl", ""
+                + "<#assign defaultA=1>"
+                + "<#assign b=2>"
+                + "<#macro m a=defaultA>${a} ${b}[<#nested>]${b} ${a}</#macro>");
+        assertOutput("<#import 'lib.ftl' as lib>"
+                + "<#assign a='a'>"
+                + "<#assign b='b'>"
+                + "<@lib.m>${a}${b}</@> "
+                + "<@lib.m a=3>${a}${b}</@>"
+                + "", "1 2[ab]2 1 3 2[ab]2 3");
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
index a6b01ac..61c5823 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
@@ -26,7 +26,6 @@ import java.io.Writer;
 
 import org.apache.freemarker.core.ThreadInterruptionSupportTemplatePostProcessor.TemplateProcessingThreadInterruptedException;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
-import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.util._NullWriter;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java
index 24a3a4e..3509b78 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java
@@ -19,7 +19,7 @@
 
 package org.apache.freemarker.core.userpkg;
 
-import static org.apache.freemarker.core.TemplateCallableModelUtils.*;
+import static org.apache.freemarker.core._TemplateCallableModelUtils.*;
 
 import java.io.IOException;
 import java.io.Writer;
@@ -27,7 +27,8 @@ import java.io.Writer;
 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.CallPlace;
+import org.apache.freemarker.core.CallPlace;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateNumberModel;
@@ -35,7 +36,9 @@ import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.util.StringToIndexMap;
 
-public class AllFeaturesDirective extends TestTemplateDirectiveModel {
+public class AllFeaturesDirective extends TestTemplateCallableModel implements TemplateDirectiveModel {
+
+    public static final AllFeaturesDirective INSTANCE = new AllFeaturesDirective();
 
     private static final int P1_ARG_IDX = 0;
     private static final int P2_ARG_IDX = 1;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/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
new file mode 100644
index 0000000..7f1f6c1
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesFunction.java
@@ -0,0 +1,114 @@
+/*
+ * 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 static org.apache.freemarker.core._TemplateCallableModelUtils.*;
+
+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;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.TemplateFunctionModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.util.StringToIndexMap;
+
+public class AllFeaturesFunction extends TestTemplateCallableModel implements TemplateFunctionModel {
+
+    public static final AllFeaturesFunction INSTANCE = new AllFeaturesFunction();
+
+    private static final int P1_ARG_IDX = 0;
+    private static final int P2_ARG_IDX = 1;
+    private static final int N1_ARG_IDX = 2;
+    private static final int N2_ARG_IDX = 3;
+
+    private static final String N1_ARG_NAME = "n1";
+    private static final String N2_ARG_NAME = "n2";
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
+            2,
+            true,
+            StringToIndexMap.of(
+                    N1_ARG_NAME, N1_ARG_IDX,
+                    N2_ARG_NAME, N2_ARG_IDX),
+            true
+    );
+
+    private static final int P_VARARGS_ARG_IDX = ARGS_LAYOUT.getPositionalVarargsArgumentIndex();
+    private static final int N_VARARGS_ARG_IDX = ARGS_LAYOUT.getNamedVarargsArgumentIndex();
+
+    private final boolean p1AllowNull;
+    private final boolean p2AllowNull;
+    private final boolean n1AllowNull;
+    private final boolean n2AllowNull;
+
+    public AllFeaturesFunction() {
+        this(true, true, true, true);
+    }
+
+    public AllFeaturesFunction(boolean p1AllowNull, boolean p2AllowNull, boolean n1AllowNull, boolean n2AllowNull) {
+        this.p1AllowNull = p1AllowNull;
+        this.p2AllowNull = p2AllowNull;
+        this.n1AllowNull = n1AllowNull;
+        this.n2AllowNull = n2AllowNull;
+    }
+
+    @Override
+    public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env) throws TemplateException {
+        return execute(castArgumentToNumber(args, P1_ARG_IDX, p1AllowNull, env),
+                castArgumentToNumber(args, P2_ARG_IDX, p2AllowNull, env),
+                (TemplateSequenceModel) args[P_VARARGS_ARG_IDX],
+                castArgumentToNumber(args[N1_ARG_IDX], N1_ARG_NAME, n1AllowNull, env),
+                castArgumentToNumber(args[N2_ARG_IDX], N2_ARG_NAME, n2AllowNull, env),
+                (TemplateHashModelEx2) args[N_VARARGS_ARG_IDX],
+                env);
+    }
+
+    private TemplateModel execute(TemplateNumberModel p1, TemplateNumberModel p2, TemplateSequenceModel pOthers,
+            TemplateNumberModel n1, TemplateNumberModel n2, TemplateHashModelEx2 nOthers,
+            Environment env) 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());
+    }
+
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java
index fed4bc7..a087188 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java
@@ -25,10 +25,11 @@ import java.io.Writer;
 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.CallPlace;
+import org.apache.freemarker.core.CallPlace;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 
-public class NamedVarargsOnlyDirective extends TestTemplateDirectiveModel {
+public class NamedVarargsOnlyDirective extends TestTemplateCallableModel implements TemplateDirectiveModel {
 
     public static final NamedVarargsOnlyDirective INSTANCE = new NamedVarargsOnlyDirective();
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java
index 540a641..2f9532e 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java
@@ -25,17 +25,14 @@ import java.io.Writer;
 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.CallPlace;
+import org.apache.freemarker.core.CallPlace;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 
-public class PositionalVarargsOnlyDirective extends TestTemplateDirectiveModel {
+public class PositionalVarargsOnlyDirective extends TestTemplateCallableModel implements TemplateDirectiveModel {
 
     public static final PositionalVarargsOnlyDirective INSTANCE = new PositionalVarargsOnlyDirective();
 
-    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
-            0, true,
-            null, false);
-
     private PositionalVarargsOnlyDirective() {
         //
     }
@@ -44,13 +41,13 @@ public class PositionalVarargsOnlyDirective extends TestTemplateDirectiveModel {
     public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
             throws TemplateException, IOException {
         out.write("#pvo(");
-        printParam("pVarargs", args[ARGS_LAYOUT.getPositionalVarargsArgumentIndex()], out, true);
+        printParam("pVarargs", args[0], out, true);
         out.write(")");
     }
 
     @Override
     public ArgumentArrayLayout getArgumentArrayLayout() {
-        return ARGS_LAYOUT;
+        return ArgumentArrayLayout.POSITIONAL_VARARGS_PARAMETER_ONLY;
     }
 
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/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
new file mode 100644
index 0000000..eb8a3eb
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyFunction.java
@@ -0,0 +1,62 @@
+/*
+ * 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 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;
+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 PositionalVarargsOnlyFunction extends TestTemplateCallableModel implements TemplateFunctionModel {
+
+    public static final PositionalVarargsOnlyFunction INSTANCE = new PositionalVarargsOnlyFunction();
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
+            0, true,
+            null, false);
+
+    private PositionalVarargsOnlyFunction() {
+        //
+    }
+
+    @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);
+        }
+    }
+
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/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
new file mode 100644
index 0000000..9dd34ba
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java
@@ -0,0 +1,88 @@
+/*
+ * 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 java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.TemplateCallableModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+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;
+
+public abstract class TestTemplateCallableModel implements TemplateCallableModel {
+
+    protected void printParam(String name, TemplateModel value, Writer out) throws IOException, TemplateModelException {
+        printParam(name, value, out, false);
+    }
+
+    protected void printParam(String name, TemplateModel value, Writer out, boolean first)
+            throws IOException, TemplateModelException {
+        if (!first) {
+            out.write(", ");
+        }
+        out.write(name);
+        out.write("=");
+        printValue(value, out);
+    }
+
+    private void printValue(TemplateModel value, Writer out) throws IOException, TemplateModelException {
+        if (value == null) {
+            out.write("null");
+        } else if (value instanceof TemplateNumberModel) {
+            out.write(((TemplateNumberModel) value).getAsNumber().toString());
+        } else if (value instanceof TemplateScalarModel) {
+            out.write(FTLUtil.toStringLiteral(((TemplateScalarModel) value).getAsString()));
+        } else if (value instanceof TemplateSequenceModel) {
+            int len = ((TemplateSequenceModel) value).size();
+            out.write('[');
+            for (int i = 0; i < len; i++) {
+                if (i != 0) {
+                    out.write(", ");
+                }
+                printValue(((TemplateSequenceModel) value).get(i), out);
+            }
+            out.write(']');
+        } else if (value instanceof TemplateHashModelEx2) {
+            TemplateHashModelEx2.KeyValuePairIterator it = ((TemplateHashModelEx2) value).keyValuePairIterator();
+            out.write('{');
+            while (it.hasNext()) {
+                TemplateHashModelEx2.KeyValuePair kvp = it.next();
+
+                printValue(kvp.getKey(), out);
+                out.write(": ");
+                printValue(kvp.getValue(), out);
+
+                if (it.hasNext()) {
+                    out.write(", ");
+                }
+            }
+            out.write('}');
+        } else {
+            throw new IllegalArgumentException("Unsupported value class: " + value.getClass().getName());
+        }
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateDirectiveModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateDirectiveModel.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateDirectiveModel.java
deleted file mode 100644
index 49bb049..0000000
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateDirectiveModel.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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 java.io.IOException;
-import java.io.Writer;
-
-import org.apache.freemarker.core.model.TemplateDirectiveModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx2;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-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;
-
-public abstract class TestTemplateDirectiveModel implements TemplateDirectiveModel {
-
-    protected void printParam(String name, TemplateModel value, Writer out) throws IOException, TemplateModelException {
-        printParam(name, value, out, false);
-    }
-
-    protected void printParam(String name, TemplateModel value, Writer out, boolean first)
-            throws IOException, TemplateModelException {
-        if (!first) {
-            out.write(", ");
-        }
-        out.write(name);
-        out.write("=");
-        printValue(value, out);
-    }
-
-    private void printValue(TemplateModel value, Writer out) throws IOException, TemplateModelException {
-        if (value == null) {
-            out.write("null");
-        } else if (value instanceof TemplateNumberModel) {
-            out.write(((TemplateNumberModel) value).getAsNumber().toString());
-        } else if (value instanceof TemplateScalarModel) {
-            out.write(FTLUtil.toStringLiteral(((TemplateScalarModel) value).getAsString()));
-        } else if (value instanceof TemplateSequenceModel) {
-            int len = ((TemplateSequenceModel) value).size();
-            out.write('[');
-            for (int i = 0; i < len; i++) {
-                if (i != 0) {
-                    out.write(", ");
-                }
-                printValue(((TemplateSequenceModel) value).get(i), out);
-            }
-            out.write(']');
-        } else if (value instanceof TemplateHashModelEx2) {
-            TemplateHashModelEx2.KeyValuePairIterator it = ((TemplateHashModelEx2) value).keyValuePairIterator();
-            out.write('{');
-            while (it.hasNext()) {
-                TemplateHashModelEx2.KeyValuePair kvp = it.next();
-
-                printValue(kvp.getKey(), out);
-                out.write(": ");
-                printValue(kvp.getValue(), out);
-
-                if (it.hasNext()) {
-                    out.write(", ");
-                }
-            }
-            out.write('}');
-        } else {
-            throw new IllegalArgumentException("Unsupported value class: " + value.getClass().getName());
-        }
-    }
-
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java
index b4b85d3..83b5435 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java
@@ -25,11 +25,12 @@ import java.io.Writer;
 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.CallPlace;
+import org.apache.freemarker.core.CallPlace;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.util.StringToIndexMap;
 
-public class TwoNamedParamsDirective extends TestTemplateDirectiveModel {
+public class TwoNamedParamsDirective extends TestTemplateCallableModel implements TemplateDirectiveModel {
 
     public static final TwoNamedParamsDirective INSTANCE = new TwoNamedParamsDirective();
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNestedContentParamsDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNestedContentParamsDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNestedContentParamsDirective.java
index ff47315..6f768f4 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNestedContentParamsDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNestedContentParamsDirective.java
@@ -25,12 +25,13 @@ import java.io.Writer;
 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.CallPlace;
+import org.apache.freemarker.core.CallPlace;
 import org.apache.freemarker.core.model.Constants;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 
-public class TwoNestedContentParamsDirective extends TestTemplateDirectiveModel {
+public class TwoNestedContentParamsDirective extends TestTemplateCallableModel implements TemplateDirectiveModel {
 
     public static final TwoNestedContentParamsDirective INSTANCE = new TwoNestedContentParamsDirective();
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java
index 4bd671e..008d8c8 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java
@@ -25,10 +25,11 @@ import java.io.Writer;
 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.CallPlace;
+import org.apache.freemarker.core.CallPlace;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 
-public class TwoPositionalParamsDirective extends TestTemplateDirectiveModel {
+public class TwoPositionalParamsDirective extends TestTemplateCallableModel implements TemplateDirectiveModel {
 
     public static final TwoPositionalParamsDirective INSTANCE = new TwoPositionalParamsDirective();
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/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
new file mode 100644
index 0000000..58e292b
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsFunction.java
@@ -0,0 +1,63 @@
+/*
+ * 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 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;
+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 TwoPositionalParamsFunction extends TestTemplateCallableModel implements TemplateFunctionModel {
+
+    public static TwoPositionalParamsFunction INSTANCE = new TwoPositionalParamsFunction();
+
+    private TwoPositionalParamsFunction() {
+        //
+    }
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
+            2, false,
+            null, false);
+
+    @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);
+        }
+    }
+
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java
index 220aeef..05e5d6d 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java
@@ -26,7 +26,7 @@ import java.io.Writer;
 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.CallPlace;
+import org.apache.freemarker.core.CallPlace;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringToIndexMapTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringToIndexMapTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringToIndexMapTest.java
index 769325f..3be5783 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringToIndexMapTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringToIndexMapTest.java
@@ -47,6 +47,8 @@ public class StringToIndexMapTest {
         assertEquals(-1, m.get("a"));
         assertEquals(0, m.get("i"));
         assertEquals(ImmutableList.of("i"), m.getKeys());
+        assertEquals("i", m.getKeyOfValue(0));
+        assertNull(m.getKeyOfValue(1));
     }
 
     @Test
@@ -57,6 +59,9 @@ public class StringToIndexMapTest {
         assertEquals(0, m.get("i"));
         assertEquals(1, m.get("j"));
         assertEquals(ImmutableList.of("i", "j"), m.getKeys());
+        assertEquals("i", m.getKeyOfValue(0));
+        assertEquals("j", m.getKeyOfValue(1));
+        assertNull(m.getKeyOfValue(2));
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
index 318e7ae..9bdea77 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
@@ -35,7 +35,7 @@ import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateConfiguration;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
-import org.apache.freemarker.core.model.CallPlace;
+import org.apache.freemarker.core.CallPlace;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast
index 18e46e9..81f83dc 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast
@@ -112,30 +112,22 @@
                 - content: "more"  // String
     #text  // o.a.f.c.ASTStaticText
         - content: "\n6 "  // String
-    #macro  // o.a.f.c.ASTDirMacro
+    #macro  // o.a.f.c.ASTDirMacroOrFunction
         - assignment target: "foo"  // String
-        - parameter name: "x"  // String
-        - parameter default: null  // Null
-        - parameter name: "y"  // String
-        - parameter default: 2  // o.a.f.c.ASTExpNumberLiteral
-        - parameter name: "z"  // String
-        - parameter default: +  // o.a.f.c.ASTExpAddOrConcat
-            - left-hand operand: y  // o.a.f.c.ASTExpVariable
-            - right-hand operand: 1  // o.a.f.c.ASTExpNumberLiteral
-        - catch-all parameter name: "q"  // String
+        - parameter definition: "ParameterDefinition(name=\"x\")"  // o.a.f.c.ASTDirMacroOrFunction$ParameterDefinition
+        - parameter definition: "ParameterDefinition(name=\"y\", default=2)"  // o.a.f.c.ASTDirMacroOrFunction$ParameterDefinition
+        - parameter definition: "ParameterDefinition(name=\"z\", default=y + 1)"  // o.a.f.c.ASTDirMacroOrFunction$ParameterDefinition
+        - parameter definition: "ParameterDefinition(name=\"q\")"  // o.a.f.c.ASTDirMacroOrFunction$ParameterDefinition
         - AST-node subtype: "0"  // Integer
         #nested  // o.a.f.c.ASTDirNested
             - passed value: x  // o.a.f.c.ASTExpVariable
             - passed value: y  // o.a.f.c.ASTExpVariable
     #text  // o.a.f.c.ASTStaticText
         - content: "\n7 "  // String
-    #function  // o.a.f.c.ASTDirMacro
+    #function  // o.a.f.c.ASTDirMacroOrFunction
         - assignment target: "foo"  // String
-        - parameter name: "x"  // String
-        - parameter default: null  // Null
-        - parameter name: "y"  // String
-        - parameter default: null  // Null
-        - catch-all parameter name: null  // Null
+        - parameter definition: "ParameterDefinition(name=\"x\")"  // o.a.f.c.ASTDirMacroOrFunction$ParameterDefinition
+        - parameter definition: "ParameterDefinition(name=\"y\")"  // o.a.f.c.ASTDirMacroOrFunction$ParameterDefinition
         - AST-node subtype: "1"  // Integer
         #local  // o.a.f.c.ASTDirAssignment
             - assignment target: "x"  // String

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-assignments.ast
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-assignments.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-assignments.ast
index 479f868..847caab 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-assignments.ast
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-assignments.ast
@@ -92,9 +92,8 @@
             - assignment source: 2  // o.a.f.c.ASTExpNumberLiteral
             - variable scope: "3"  // Integer
             - namespace: null  // Null
-    #macro  // o.a.f.c.ASTDirMacro
+    #macro  // o.a.f.c.ASTDirMacroOrFunction
         - assignment target: "m"  // String
-        - catch-all parameter name: null  // Null
         - AST-node subtype: "0"  // Integer
         #text  // o.a.f.c.ASTStaticText
             - content: "  7 "  // String

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macro-and-function.ftl
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macro-and-function.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macro-and-function.ftl
new file mode 100644
index 0000000..56e816f
--- /dev/null
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macro-and-function.ftl
@@ -0,0 +1,43 @@
+<#ftl stripWhitespace=false>
+<#--
+  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.
+-->
+<#macro m></#macro>
+<#macro m a></#macro>
+<#macro m a b></#macro>
+<#macro m a b c...></#macro>
+<#macro m a...></#macro>
+<#macro m p1{positional}, p2{positional}, pva{positional}... n1 n2 nva...></#macro>
+<#macro m p1{positional}, p2{positional}=2, pva{positional}... n1=3 n2 nva...></#macro>
+<#macro m a{positional}></#macro>
+<#macro m a{positional}=1></#macro>
+<#macro m a{positional}...></#macro>
+<#macro m nva{positional}... pva...></#macro>
+<#function f()></#function>
+<#function f(a)></#function>
+<#function f(a, b)></#function>
+<#function f(a, b, c...)></#function>
+<#function f(a...)></#function>
+<#function f(p1, p2, pva..., n1{named}, n2{named}, nva{named}...)></#function>
+<#function f(p1, p2=2, pva..., n1{named}, n2{named}=3, nva{named}...)></#function>
+<#function f(a{named})></#function>
+<#function f(a{named}=1)></#function>
+<#function f(a{named}...)></#function>
+<#function f(nva..., pva{named}...)></#function>
+
+<#macro  m  p1 { positional } , p2 { positional } = 2 , pva { positional } ... n1 = 3 n2 nva ... ></#macro >

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macro-and-function.ftl.out
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macro-and-function.ftl.out b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macro-and-function.ftl.out
new file mode 100644
index 0000000..c15b35d
--- /dev/null
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macro-and-function.ftl.out
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+<#macro m></#macro>
+<#macro m a></#macro>
+<#macro m a b></#macro>
+<#macro m a b c...></#macro>
+<#macro m a...></#macro>
+<#macro m p1{positional}, p2{positional}, pva{positional}... n1 n2 nva...></#macro>
+<#macro m p1{positional}, p2{positional}=2, pva{positional}... n1=3 n2 nva...></#macro>
+<#macro m a{positional}></#macro>
+<#macro m a{positional}=1></#macro>
+<#macro m a{positional}...></#macro>
+<#macro m nva{positional}... pva...></#macro>
+<#function f()></#function>
+<#function f(a)></#function>
+<#function f(a, b)></#function>
+<#function f(a, b, c...)></#function>
+<#function f(a...)></#function>
+<#function f(p1, p2, pva..., n1{named}, n2{named}, nva{named}...)></#function>
+<#function f(p1, p2=2, pva..., n1{named}, n2{named}=3, nva{named}...)></#function>
+<#function f(a{named})></#function>
+<#function f(a{named}=1)></#function>
+<#function f(a{named}...)></#function>
+<#function f(nva..., pva{named}...)></#function>
+
+<#macro m p1{positional}, p2{positional}=2, pva{positional}... n1=3 n2 nva...></#macro>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macros.ftl
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macros.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macros.ftl
deleted file mode 100644
index 9288e63..0000000
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macros.ftl
+++ /dev/null
@@ -1,29 +0,0 @@
-<#ftl stripWhitespace=false>
-<#--
-  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.
--->
-<#macro m></#macro>
-<#macro m a></#macro>
-<#macro m a b></#macro>
-<#macro m a b c...></#macro>
-<#macro m a...></#macro>
-<#function f()></#function>
-<#function f(a)></#function>
-<#function f(a, b)></#function>
-<#function f(a, b, c...)></#function>
-<#function f(a...)></#function>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macros.ftl.out
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macros.ftl.out b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macros.ftl.out
deleted file mode 100644
index ad49cae..0000000
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-macros.ftl.out
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.
- */
-<#macro m></#macro>
-<#macro m a></#macro>
-<#macro m a b></#macro>
-<#macro m a b c...></#macro>
-<#macro m a...></#macro>
-<#function f()></#function>
-<#function f(a)></#function>
-<#function f(a, b)></#function>
-<#function f(a, b, c...)></#function>
-<#function f(a...)></#function>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros2.txt
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros2.txt b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros2.txt
deleted file mode 100644
index 1b4e007..0000000
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros2.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.
- */
-1 1
-2 2
-4
-m3 with d="4" Failed!

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/api-builtins.ftl
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/api-builtins.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/api-builtins.ftl
index c7d1915..5377f38 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/api-builtins.ftl
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/api-builtins.ftl
@@ -18,8 +18,8 @@
 -->
 <@assertEquals expected="b" actual=map?api.get(2?int) />
 <@assertEquals expected=2 actual=list?api.indexOf(3?int) />
-<@assert test=set?api.contains("b") />
-<@assert test=!set?api.contains("d") />
+<@assert set?api.contains("b") />
+<@assert !set?api.contains("d") />
 
 <#assign dump = "">
 <#list map?api.entrySet() as entry>
@@ -30,11 +30,11 @@
 </#list>
 <@assertEquals expected="1: a, 2: b, 3: c" actual=dump />
 
-<@assert test=map?hasApi />
-<@assert test=list?hasApi />
-<@assert test=set?hasApi />
-<@assert test=!s?hasApi />
-<@assert test=!1?hasApi />
-<@assert test=!""?hasApi />
-<@assert test=!{}?hasApi />
-<@assert test=!true?hasApi />
+<@assert map?hasApi />
+<@assert list?hasApi />
+<@assert set?hasApi />
+<@assert !s?hasApi />
+<@assert !1?hasApi />
+<@assert !""?hasApi />
+<@assert !{}?hasApi />
+<@assert !true?hasApi />

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/boolean.ftl
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/boolean.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/boolean.ftl
index 49816ee..3d70cc5 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/boolean.ftl
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/boolean.ftl
@@ -71,10 +71,10 @@
 <#else>
     boolean4 && boolean5 failed.<br />
 </#if></p>
-<@assert test=true &amp;&amp; true />
-<@assert test=!(false &amp;&amp; true) />
-<@assert test=true \and true />
-<@assert test=!(false \and true) />
+<@assert true &amp;&amp; true />
+<@assert !(false &amp;&amp; true) />
+<@assert true \and true />
+<@assert !(false \and true) />
  
 <p>Now test list models:</p>