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:13 UTC

[11/21] incubator-freemarker git commit: FREEMARKER-63: Removed TemplateTransformModel and the old TemplateDirectiveModel, renamed TemplateDirectiveModel2 to TemplateDirectiveModel. Removed the temporary `<~...>` syntax; now `<@...>` is used to call the

FREEMARKER-63: Removed TemplateTransformModel and the old TemplateDirectiveModel, renamed TemplateDirectiveModel2 to TemplateDirectiveModel. Removed the temporary `<~...>` syntax; now `<@...>` is used to call the new TemplateDirectiveModel. Lot of API refinement, like introduced ArgumentArrayLayout class. Several test cases won't yet pass... work in progress.


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

Branch: refs/heads/3
Commit: 52a5f9eb8881a2c976a2b84ff683d8c11238efca
Parents: a6399a7
Author: ddekany <dd...@apache.org>
Authored: Sun Jul 30 02:04:56 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sun Jul 30 02:04:56 2017 +0200

----------------------------------------------------------------------
 FM3-CHANGE-LOG.txt                              |  10 +-
 .../core/FM2ASTToFM3SourceConverter.java        |   2 +
 .../converter/FM2ToFM3ConverterTest.java        |   3 +
 .../freemarker/core/DirectiveCallPlaceTest.java |  82 ++--
 .../EnvironmentGetTemplateVariantsTest.java     |  17 +-
 .../core/TagSyntaxVariationsTest.java           |  19 +-
 .../core/TemplateCallableModelTest.java         | 175 ++++---
 .../core/TheadInterruptingSupportTest.java      |  33 +-
 .../core/userpkg/AllFeaturesDirective.java      |  66 +--
 .../core/userpkg/NamedVarargsOnlyDirective.java |  59 +++
 .../userpkg/PositionalVarargsOnlyDirective.java |  57 +++
 .../userpkg/TestTemplateDirectiveModel.java     |   4 +-
 .../core/userpkg/TwoNamedParamsDirective.java   |  50 +-
 .../userpkg/TwoPositionalParamsDirective.java   |  43 +-
 .../core/userpkg/UpperCaseDirective.java        |  40 +-
 .../core/valueformat/NumberFormatTest.java      |  12 +-
 .../org/apache/freemarker/core/ast-1.ast        |   4 +-
 .../org/apache/freemarker/core/ast-range.ast    |   2 +-
 .../freemarker/core/ast-strlitinterpolation.ast |   2 +-
 .../freemarker/core/ast-whitespacestripping.ast |   6 +-
 .../freemarker/core/ASTDirDynamicCall.java      | 468 ------------------
 .../freemarker/core/ASTDirUserDefined.java      | 344 -------------
 .../freemarker/core/ASTDynamicTopLevelCall.java | 484 +++++++++++++++++++
 .../apache/freemarker/core/ASTExpBuiltIn.java   |   3 +-
 .../core/BuiltInsForMultipleTypes.java          |  13 +-
 .../freemarker/core/BuiltInsForStringsMisc.java |  23 +-
 ...lPlaceCustomDataInitializationException.java |   3 +-
 .../freemarker/core/DirectiveCallPlace.java     | 135 ------
 .../org/apache/freemarker/core/Environment.java | 197 +-------
 .../NestedContentNotSupportedException.java     |  28 +-
 .../NonUserDefinedDirectiveLikeException.java   |   5 +-
 .../apache/freemarker/core/ParseException.java  |   2 +-
 .../core/TemplateCallableModelUtils.java        |   2 -
 ...nterruptionSupportTemplatePostProcessor.java |   6 +-
 .../freemarker/core/debug/DebugModel.java       |   2 +-
 .../core/debug/RmiDebugModelImpl.java           |   4 +-
 .../core/model/ArgumentArrayLayout.java         | 199 ++++++++
 .../apache/freemarker/core/model/CallPlace.java |  69 +--
 .../core/model/TemplateCallableModel.java       |  70 +--
 .../core/model/TemplateDirectiveBody.java       |  43 --
 .../core/model/TemplateDirectiveModel.java      |  91 ++--
 .../core/model/TemplateDirectiveModel2.java     |  51 --
 .../core/model/TemplateFunctionModel.java       |   2 +-
 .../core/model/TemplateTransformModel.java      |  54 ---
 .../freemarker/core/model/TransformControl.java | 101 ----
 .../apache/freemarker/core/util/FTLUtil.java    |   3 -
 .../freemarker/core/util/StringToIndexMap.java  |  27 ++
 freemarker-core/src/main/javacc/FTL.jj          | 121 +----
 .../apache/freemarker/servlet/IncludePage.java  |  48 +-
 .../freemarker/servlet/jsp/BodyContentImpl.java | 222 +++++++++
 .../jsp/CustomTagAndELFunctionCombiner.java     |  87 +---
 .../servlet/jsp/FreeMarkerPageContext.java      |   2 +-
 .../freemarker/servlet/jsp/JspTagModelBase.java |  19 +-
 .../servlet/jsp/SimpleTagDirectiveModel.java    |  31 +-
 .../servlet/jsp/TagDirectiveModel.java          | 256 ++++++++++
 .../servlet/jsp/TagTransformModel.java          | 419 ----------------
 .../freemarker/servlet/jsp/TaglibFactory.java   |   6 +-
 .../test/templateutil/AssertDirective.java      |  42 +-
 .../templateutil/AssertEqualsDirective.java     |  63 +--
 .../test/templateutil/AssertFailsDirective.java | 125 +++--
 .../test/templateutil/NoOutputDirective.java    |  17 +-
 61 files changed, 1940 insertions(+), 2633 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/FM3-CHANGE-LOG.txt b/FM3-CHANGE-LOG.txt
index 7943289..6b347e5 100644
--- a/FM3-CHANGE-LOG.txt
+++ b/FM3-CHANGE-LOG.txt
@@ -48,6 +48,7 @@ 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.
 
 Smaller changes
 ---------------
@@ -99,7 +100,14 @@ Node: Changes already mentioned above aren't repeated here!
   Converter note: This conversion is done, but note that in the rare case where a template has no name (when
   creating a `Template` directly with its constructor using `null` as the `name` parameter) `.templateName` was an
   empty string, while `.currentTemplateName` will be null.
-
+- Refactroing of callable TemplateModel interfaces and related classes:
+  - 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.
+  - ?isTransform was removed (as there are no transforms anymore).
+    Converter note: The template converter tool replaces it with ?isDirective
 
 Java API changes
 ================

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java
----------------------------------------------------------------------
diff --git a/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java b/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java
index 26bbe55..ed86057 100644
--- a/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java
+++ b/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java
@@ -1967,6 +1967,8 @@ public class FM2ASTToFM3SourceConverter {
     private static Map<String, String> IRREGULAR_BUILT_IN_NAME_CONVERSIONS = new ImmutableMap.Builder<String, String>()
             .put("webSafe", "html")
             .put("web_safe", "html")
+            .put("is_transform", "isDirective")
+            .put("isTransform", "isDirective")
             .put("iso_utc_fz", "isoUtcFZ")
             .put("iso_utc_nz", "isoUtcNZ")
             .put("iso_utc_ms_nz", "isoUtcMsNZ")

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java
----------------------------------------------------------------------
diff --git a/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java b/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java
index 61eaa35..63c2a92 100644
--- a/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java
+++ b/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java
@@ -472,6 +472,9 @@ public class FM2ToFM3ConverterTest extends ConverterTest {
     public void testBuiltInExpressions() throws IOException, ConverterException {
         assertConverted("${s?upperCase} ${s?leftPad(123)}", "${s?upper_case} ${s?left_pad(123)}");
         assertConverted("${s?html}", "${s?web_safe}");
+        assertConverted("${s?html}", "${s?webSafe}");
+        assertConverted("${s?isDirective}", "${s?is_transform}");
+        assertConverted("${s?isDirective}", "${s?isTransform}");
         assertConvertedSame("${s  ?   upperCase\t?\t\tleftPad(5)}");
         assertConvertedSame("${s <#--1--> ? <#--2--> upperCase}");
         // Runtime params:

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 28744b3..37cd2b6 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
@@ -26,8 +26,10 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
+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;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateScalarModel;
@@ -73,20 +75,14 @@ public class DirectiveCallPlaceTest extends TemplateTest {
                 "<@pa />\n"
                 + "..<@pa\n"
                 + "/><@pa>xxx</@>\n"
-                + "<@pa>{<@pa/> <@pa/>}</@>\n"
-                + "${curDirLine}<@argP p=curDirLine?string>${curDirLine}</...@argP>${curDirLine}\n"
-                + "<#macro m p>(p=${p}){<#nested>}</#macro>\n"
-                + "${curDirLine}<@m p=curDirLine?string>${curDirLine}</...@m>${curDirLine}");
+                + "<@pa>{<@pa/> <@pa/>}</@>\n");
         
         assertOutputForNamed(
                 "positions.ftl",
                 "[positions.ftl:1:1-1:7]"
                 + "..[positions.ftl:2:3-3:2]"
                 + "[positions.ftl:3:3-3:14]xxx\n"
-                + "[positions.ftl:4:1-4:24]{[positions.ftl:4:7-4:12] [positions.ftl:4:14-4:19]}\n"
-                + "-(p=5){-}-\n"
-                + "-(p=7){-}-"
-                );
+                + "[positions.ftl:4:1-4:24]{[positions.ftl:4:7-4:12] [positions.ftl:4:14-4:19]}\n");
     }
     
     @SuppressWarnings("boxing")
@@ -97,7 +93,6 @@ public class DirectiveCallPlaceTest extends TemplateTest {
         dm.put("lc", new CachingLowerCaseDirective());
         dm.put("pa", new PositionAwareDirective());
         dm.put("argP", new ArgPrinterDirective());
-        dm.put("curDirLine", new CurDirLineScalar());
         dm.put("x", 123);
         return dm;
     }
@@ -111,17 +106,16 @@ public class DirectiveCallPlaceTest extends TemplateTest {
         static void resetCacheRecreationCount() {
             cacheRecreationCount.set(0);
         }
-        
+
         @Override
-        public void execute(Environment env, Map params, TemplateModel[] loopVars, final TemplateDirectiveBody body)
+        public void execute(TemplateModel[] args, final CallPlace callPlace, Writer out, final Environment env)
                 throws TemplateException, IOException {
-            if (body == null) {
+            if (callPlace.hasNestedContent()) {
                 return;
             }
             
             final String convertedText;
 
-            final DirectiveCallPlace callPlace = env.getCurrentDirectiveCallPlace();
             if (callPlace.isNestedOutputCacheable()) {
                 try {
                     convertedText = (String) callPlace.getOrCreateCustomData(
@@ -129,7 +123,7 @@ public class DirectiveCallPlaceTest extends TemplateTest {
 
                                 @Override
                                 public String get() throws TemplateException, IOException {
-                                    return convertBodyText(body)
+                                    return convertBodyText(callPlace, env)
                                             + "[cached " + cacheRecreationCount.incrementAndGet() + "]";
                                 }
 
@@ -138,18 +132,23 @@ public class DirectiveCallPlaceTest extends TemplateTest {
                     throw new TemplateModelException("Failed to pre-render nested content", e);
                 }
             } else {
-                convertedText = convertBodyText(body);
+                convertedText = convertBodyText(callPlace, env);
             }
 
             env.getOut().write(convertedText);
         }
 
+        @Override
+        public ArgumentArrayLayout getArgumentArrayLayout() {
+            return ArgumentArrayLayout.PARAMETERLESS;
+        }
+
         protected abstract Class getTextConversionIdentity();
 
-        private String convertBodyText(TemplateDirectiveBody body) throws TemplateException,
+        private String convertBodyText(CallPlace callPlace, Environment env) throws TemplateException,
                 IOException {
             StringWriter sw = new StringWriter();
-            body.render(sw);
+            callPlace.executeNestedContent(null, sw, env);
             return convertText(sw.toString());
         }
         
@@ -188,10 +187,8 @@ public class DirectiveCallPlaceTest extends TemplateTest {
     private static class PositionAwareDirective implements TemplateDirectiveModel {
 
         @Override
-        public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+        public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
                 throws TemplateException, IOException {
-            Writer out = env.getOut();
-            DirectiveCallPlace callPlace = env.getCurrentDirectiveCallPlace();
             out.write("[");
             out.write(getTemplateSourceName(callPlace));
             out.write(":");
@@ -203,47 +200,46 @@ public class DirectiveCallPlaceTest extends TemplateTest {
             out.write(":");
             out.write(Integer.toString(callPlace.getEndColumn()));
             out.write("]");
-            if (body != null) {
-                body.render(out);
-            }
+            callPlace.executeNestedContent(null, out, env);
         }
 
-        private String getTemplateSourceName(DirectiveCallPlace callPlace) {
-            return ((ASTDirUserDefined) callPlace).getTemplate().getSourceName();
+        @Override
+        public ArgumentArrayLayout getArgumentArrayLayout() {
+            return ArgumentArrayLayout.PARAMETERLESS;
+        }
+
+        private String getTemplateSourceName(CallPlace callPlace) {
+            return callPlace.getTemplate().getSourceName();
         }
-        
     }
 
     private static class ArgPrinterDirective implements TemplateDirectiveModel {
 
+        private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
+                0, false,
+                null, true
+        );
+
         @Override
-        public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+        public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
                 throws TemplateException, IOException {
-            final Writer out = env.getOut();
-            if (params.size() > 0) {
+            TemplateHashModelEx2 varargs = (TemplateHashModelEx2) args[ARGS_LAYOUT.getNamedVarargsArgumentIndex()];
+            if (varargs.size() > 0) {
                 out.write("(p=");
-                out.write(((TemplateScalarModel) params.get("p")).getAsString());
+                out.write(((TemplateScalarModel) varargs.get("p")).getAsString());
                 out.write(")");
             }
-            if (body != null) {
+            if (callPlace.hasNestedContent()) {
                 out.write("{");
-                body.render(out);
+                callPlace.executeNestedContent(null, out, env);
                 out.write("}");
             }
         }
-        
-    }
-    
-    private static class CurDirLineScalar implements TemplateScalarModel {
 
         @Override
-        public String getAsString() throws TemplateModelException {
-            DirectiveCallPlace callPlace = Environment.getCurrentEnvironment().getCurrentDirectiveCallPlace();
-            return callPlace != null
-                    ? String.valueOf(Environment.getCurrentEnvironment().getCurrentDirectiveCallPlace().getBeginLine())
-                    : "-";
+        public ArgumentArrayLayout getArgumentArrayLayout() {
+            return ARGS_LAYOUT;
         }
-        
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 fc3bbf5..40f7c63 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
@@ -24,9 +24,9 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 import java.io.Writer;
 import java.util.Collections;
-import java.util.Map;
 
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
+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;
@@ -197,17 +197,20 @@ public class EnvironmentGetTemplateVariantsTest extends TemplateTest {
     @Override
     protected Object createDataModel() {
         return Collections.singletonMap("tNames", new TemplateDirectiveModel() {
-
             @Override
-            public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
-                    throws TemplateException, IOException {
-                Writer out = env.getOut();
+            public void execute(TemplateModel[] args, CallPlace callPlace, Writer out,
+                    Environment env) throws
+                    IOException {
                 final String r = "<ct=" + env.getCurrentTemplate().getLookupName() + " mt="
                         + env.getMainTemplate().getLookupName() + ">";
                 out.write(r);
                 env.setGlobalVariable("lastTNamesResult", new SimpleScalar(r));
             }
-            
+
+            @Override
+            public ArgumentArrayLayout getArgumentArrayLayout() {
+                return ArgumentArrayLayout.PARAMETERLESS;
+            }
         });
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core-test/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
index 02c74df..ee521d9 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
@@ -25,9 +25,7 @@ import java.io.StringWriter;
 import java.util.Collections;
 import java.util.Map;
 
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
-import org.apache.freemarker.core.model.TemplateDirectiveModel;
-import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.userpkg.UpperCaseDirective;
 import org.apache.freemarker.core.util._StringUtil;
 import org.apache.freemarker.test.TestConfigurationBuilder;
 
@@ -194,19 +192,4 @@ public class TagSyntaxVariationsTest extends TestCase {
         assertEquals(expected, out.toString());
     }
 
-    // This will be removed when the legacy TemplateDirectiveModel is removed; the use the other UpperCaseDirective
-    // instead!
-    private static class UpperCaseDirective implements TemplateDirectiveModel {
-
-        private static final UpperCaseDirective INSTANCE = new UpperCaseDirective();
-
-        @Override
-        public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
-                throws TemplateException, IOException {
-            StringWriter sw = new StringWriter();
-            body.render(sw);
-            env.getOut().write(sw.toString().toUpperCase());
-        }
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 f5c1b61..74edbaf 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,6 +22,8 @@ package org.apache.freemarker.core;
 import java.io.IOException;
 
 import org.apache.freemarker.core.userpkg.AllFeaturesDirective;
+import org.apache.freemarker.core.userpkg.NamedVarargsOnlyDirective;
+import org.apache.freemarker.core.userpkg.PositionalVarargsOnlyDirective;
 import org.apache.freemarker.core.userpkg.TwoNamedParamsDirective;
 import org.apache.freemarker.core.userpkg.TwoPositionalParamsDirective;
 import org.apache.freemarker.core.userpkg.UpperCaseDirective;
@@ -34,120 +36,137 @@ public class TemplateCallableModelTest extends TemplateTest {
     @Before
     public void addCommonData() {
         addToDataModel("a", new AllFeaturesDirective());
-        addToDataModel("p", new TwoPositionalParamsDirective());
-        addToDataModel("n", new TwoNamedParamsDirective());
+        addToDataModel("p", TwoPositionalParamsDirective.INSTANCE);
+        addToDataModel("n", TwoNamedParamsDirective.INSTANCE);
+        addToDataModel("pvo", PositionalVarargsOnlyDirective.INSTANCE);
+        addToDataModel("nvo", NamedVarargsOnlyDirective.INSTANCE);
     }
 
     @Test
     public void testArguments() throws IOException, TemplateException {
-        assertOutput("<~p />",
+        assertOutput("<@p />",
                 "#p(p1=null, p2=null)");
-        assertOutput("<~p 1 />",
+        assertOutput("<@p 1 />",
                 "#p(p1=1, p2=null)");
-        assertOutput("<~p 1, 2 />",
+        assertOutput("<@p 1, 2 />",
                 "#p(p1=1, p2=2)");
 
-        assertOutput("<~n />",
+        assertOutput("<@n />",
                 "#n(n1=null, n2=null)");
-        assertOutput("<~n n1=11/>",
+        assertOutput("<@n n1=11/>",
                 "#n(n1=11, n2=null)");
-        assertOutput("<~n n1=11 n2=22/>",
+        assertOutput("<@n n1=11 n2=22/>",
                 "#n(n1=11, n2=22)");
 
-        assertOutput("<~a />",
-                "#a(p1=null, p2=null, pOthers=[], n1=null, n2=null, nOthers={})");
-        assertOutput("<~a 1, 2 />",
-                "#a(p1=1, p2=2, pOthers=[], n1=null, n2=null, nOthers={})");
-        assertOutput("<~a n1=11 n2=22 />",
-                "#a(p1=null, p2=null, pOthers=[], n1=11, n2=22, nOthers={})");
-
-        assertOutput("<~a 1, 2 n1=11 n2=22 />",
-                "#a(p1=1, p2=2, pOthers=[], n1=11, n2=22, nOthers={})");
-        assertOutput("<~a 1 n1=11 />",
-                "#a(p1=1, p2=null, pOthers=[], n1=11, n2=null, nOthers={})");
-        assertOutput("<~a 1, 2, 3 n1=11 n2=22 n3=33 />",
-                "#a(p1=1, p2=2, pOthers=[3], n1=11, n2=22, nOthers={\"n3\": 33})");
-        assertOutput("<~a 1 n1=11 n3=33 />",
-                "#a(p1=1, p2=null, pOthers=[], n1=11, n2=null, nOthers={\"n3\": 33})");
-        assertOutput("<~a 1 n1=11 a=1 b=2 c=3 d=4 e=5 f=6 g=7 />",
-                "#a(p1=1, p2=null, pOthers=[], n1=11, n2=null, nOthers={"
+        assertOutput("<@pvo />",
+                "#pvo(pVarargs=[])");
+        assertOutput("<@pvo 1 />",
+                "#pvo(pVarargs=[1])");
+        assertOutput("<@pvo 1, 2 />",
+                "#pvo(pVarargs=[1, 2])");
+
+        assertOutput("<@nvo />",
+                "#nvo(nVarargs={})");
+        assertOutput("<@nvo n1=11 />",
+                "#nvo(nVarargs={\"n1\": 11})");
+        assertOutput("<@nvo n1=11 n2=22/>",
+                "#nvo(nVarargs={\"n1\": 11, \"n2\": 22})");
+
+        assertOutput("<@a />",
+                "#a(p1=null, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={})");
+        assertOutput("<@a 1, 2 />",
+                "#a(p1=1, p2=2, pVarargs=[], n1=null, n2=null, nVarargs={})");
+        assertOutput("<@a n1=11 n2=22 />",
+                "#a(p1=null, p2=null, pVarargs=[], n1=11, n2=22, nVarargs={})");
+
+        assertOutput("<@a 1, 2 n1=11 n2=22 />",
+                "#a(p1=1, p2=2, pVarargs=[], n1=11, n2=22, nVarargs={})");
+        assertOutput("<@a 1 n1=11 />",
+                "#a(p1=1, p2=null, pVarargs=[], n1=11, n2=null, nVarargs={})");
+        assertOutput("<@a 1, 2, 3 n1=11 n2=22 n3=33 />",
+                "#a(p1=1, p2=2, pVarargs=[3], n1=11, n2=22, nVarargs={\"n3\": 33})");
+        assertOutput("<@a 1 n1=11 n3=33 />",
+                "#a(p1=1, p2=null, pVarargs=[], n1=11, n2=null, nVarargs={\"n3\": 33})");
+        assertOutput("<@a 1 n1=11 a=1 b=2 c=3 d=4 e=5 f=6 g=7 />",
+                "#a(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("<~a; a, b, c/>",
-                "#a(p1=null, p2=null, pOthers=[], n1=null, n2=null, nOthers={}; 3)");
-        assertOutput("<~a 1, 2; a, b, c />",
-                "#a(p1=1, p2=2, pOthers=[], n1=null, n2=null, nOthers={}; 3)");
-        assertOutput("<~a n1=11 n2=22; a, b, c />",
-                "#a(p1=null, p2=null, pOthers=[], n1=11, n2=22, nOthers={}; 3)");
-        assertOutput("<~a 1, 2 n1=11 n2=22; a, b, c />",
-                "#a(p1=1, p2=2, pOthers=[], n1=11, n2=22, nOthers={}; 3)");
+        assertOutput("<@a; a, b, c/>",
+                "#a(p1=null, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={}; 3)");
+        assertOutput("<@a 1, 2; a, b, c />",
+                "#a(p1=1, p2=2, pVarargs=[], n1=null, n2=null, nVarargs={}; 3)");
+        assertOutput("<@a n1=11 n2=22; a, b, c />",
+                "#a(p1=null, p2=null, pVarargs=[], n1=11, n2=22, nVarargs={}; 3)");
+        assertOutput("<@a 1, 2 n1=11 n2=22; a, b, c />",
+                "#a(p1=1, p2=2, pVarargs=[], n1=11, n2=22, nVarargs={}; 3)");
     }
 
     @Test
     public void testNestedContent() throws IOException, TemplateException {
-        assertOutput("<~a />",
-                "#a(p1=null, p2=null, pOthers=[], n1=null, n2=null, nOthers={})");
-        assertOutput("<~a></~a>",
-                "#a(p1=null, p2=null, pOthers=[], n1=null, n2=null, nOthers={})");
-
-        assertOutput("<~a>x</~a>",
-                "#a(p1=null, p2=null, pOthers=[], n1=null, n2=null, nOthers={}) {}");
-        assertOutput("<~a 1>x</~a>",
-                "#a(p1=1, p2=null, pOthers=[], n1=null, n2=null, nOthers={}) {x}");
-        assertOutput("<~a 3>x</~a>",
-                "#a(p1=3, p2=null, pOthers=[], n1=null, n2=null, nOthers={}) {xxx}");
-        assertOutput("<~a 3; i, j, k>[${i}${j}${k}]</~a>",
-                "#a(p1=3, p2=null, pOthers=[], n1=null, n2=null, nOthers={}; 3) {[123][246][369]}");
-        assertOutput("<#assign i = '-'>${i} <~a 3; i>${i}</~a> ${i}",
-                "- #a(p1=3, p2=null, pOthers=[], n1=null, n2=null, nOthers={}; 1) {123} -");
+        assertOutput("<@a />",
+                "#a(p1=null, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={})");
+        assertOutput("<@a...@a>",
+                "#a(p1=null, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={})");
+
+        assertOutput("<@a...@a>",
+                "#a(p1=null, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={}) {}");
+        assertOutput("<@a 1>x</...@a>",
+                "#a(p1=1, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={}) {x}");
+        assertOutput("<@a 3>x</...@a>",
+                "#a(p1=3, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={}) {xxx}");
+        assertOutput("<@a 3; i, j, k>[${i}${j}${k}]</...@a>",
+                "#a(p1=3, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={}; 3) {[123][246][369]}");
+        assertOutput("<#assign i = '-'>${i} <@a 3; i>${i}</...@a> ${i}",
+                "- #a(p1=3, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={}; 1) {123} -");
     }
 
     @Test
     public void testSyntaxEdgeCases() throws IOException, TemplateException {
-        assertOutput("<~a; x/>",
-                "#a(p1=null, p2=null, pOthers=[], n1=null, n2=null, nOthers={}; 1)");
-        assertOutput("<~a;x/>",
-                "#a(p1=null, p2=null, pOthers=[], n1=null, n2=null, nOthers={}; 1)");
-        assertOutput("<~a;x />",
-                "#a(p1=null, p2=null, pOthers=[], n1=null, n2=null, nOthers={}; 1)");
-
-        assertOutput("<~a  1  ,  2 n1 = 11  n2 = 22  ;  a  ,  b  ,  c  />",
-                "#a(p1=1, p2=2, pOthers=[], n1=11, n2=22, nOthers={}; 3)");
-        assertOutput("<~a 1<#-- -->,<#-- -->2<#-- -->n1<#-- -->=<#-- -->11<#-- -->n2=22<#-- -->;"
+        assertOutput("<@a; x/>",
+                "#a(p1=null, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={}; 1)");
+        assertOutput("<@a;x/>",
+                "#a(p1=null, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={}; 1)");
+        assertOutput("<@a;x />",
+                "#a(p1=null, p2=null, pVarargs=[], n1=null, n2=null, nVarargs={}; 1)");
+
+        assertOutput("<@a  1  ,  2 n1 = 11  n2 = 22  ;  a  ,  b  ,  c  />",
+                "#a(p1=1, p2=2, pVarargs=[], n1=11, n2=22, nVarargs={}; 3)");
+        assertOutput("<@a 1<#-- -->,<#-- -->2<#-- -->n1<#-- -->=<#-- -->11<#-- -->n2=22<#-- -->;"
                         + "<#-- -->a<#-- -->,<#-- -->b<#-- -->,<#-- -->c<#-- -->/>",
-                "#a(p1=1, p2=2, pOthers=[], n1=11, n2=22, nOthers={}; 3)");
-        assertOutput("<~a\t1,2\tn1=11\tn2=22;a,b,c/>",
-                "#a(p1=1, p2=2, pOthers=[], n1=11, n2=22, nOthers={}; 3)");
+                "#a(p1=1, p2=2, pVarargs=[], n1=11, n2=22, nVarargs={}; 3)");
+        assertOutput("<@a\t1,2\tn1=11\tn2=22;a,b,c/>",
+                "#a(p1=1, p2=2, pVarargs=[], n1=11, n2=22, nVarargs={}; 3)");
 
-        assertOutput("<~a + 1 />",
-                "#a(p1=1, p2=null, pOthers=[], n1=null, n2=null, nOthers={})");
+        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");
+        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
     @SuppressWarnings("ThrowableNotThrown")
     public void testRuntimeErrors() throws IOException, TemplateException {
-        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 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("<@a 1; i, j, k, l>x</...@a>", "(4: \"i\", \"j\", \"k\", \"l\")", "(3)");
     }
 
     @Test
     public void testMacros() throws IOException, TemplateException {
         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 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>"
@@ -156,14 +175,14 @@ public class TemplateCallableModelTest extends TemplateTest {
                         + "<#list others as k, v>${k}=${v}<#sep>, </#list>"
                         + "</#if>]"
                         + "</#macro>"
-                        + "<~m 1, 2 /> <~m 1, 2, 3, 4 /> <~m a=1 b=2 /> <~m a=1 b=2 c=3 d=4 />",
+                        + "<@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]");
     }
 
     @Test
     public void testFilterDirective() throws IOException, TemplateException {
-        addToDataModel("uc", new UpperCaseDirective());
-        assertOutput("<~uc>foo ${1 + 1}</~>", "FOO 2");
+        addToDataModel("uc", UpperCaseDirective.INSTANCE);
+        assertOutput("<@uc>foo ${1 + 1}</@>", "FOO 2");
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 003f66a..2ab270a 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
@@ -22,10 +22,11 @@ package org.apache.freemarker.core;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
-import java.util.Map;
+import java.io.Writer;
 
 import org.apache.freemarker.core.ThreadInterruptionSupportTemplatePostProcessor.TemplateProcessingThreadInterruptedException;
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
+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;
@@ -119,35 +120,41 @@ public class TheadInterruptingSupportTest {
         }
 
         public class StartedDirective implements TemplateDirectiveModel {
-            
             @Override
-            public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+            public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
                     throws TemplateException, IOException {
                 synchronized (TemplateRunnerThread.this) {
                     started = true;
                     TemplateRunnerThread.this.notifyAll();
                 }
             }
-            
+
+            @Override
+            public ArgumentArrayLayout getArgumentArrayLayout() {
+                return ArgumentArrayLayout.PARAMETERLESS;
+            }
         }
 
         public class CustomLoopDirective implements TemplateDirectiveModel {
 
             @Override
-            public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+            public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
                     throws TemplateException, IOException {
                 // Deliberate infinite loop
                 while (true) {
-                    body.render(_NullWriter.INSTANCE);
+                    callPlace.executeNestedContent(null, _NullWriter.INSTANCE, env);
                 }
             }
-            
+
+            @Override
+            public ArgumentArrayLayout getArgumentArrayLayout() {
+                return ArgumentArrayLayout.PARAMETERLESS;
+            }
         }
         
         public class SleepDirective implements TemplateDirectiveModel {
-
             @Override
-            public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+            public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
                     throws TemplateException, IOException {
                 try {
                     Thread.sleep(100);
@@ -156,7 +163,11 @@ public class TheadInterruptingSupportTest {
                     Thread.currentThread().interrupt();
                 }
             }
-            
+
+            @Override
+            public ArgumentArrayLayout getArgumentArrayLayout() {
+                return ArgumentArrayLayout.PARAMETERLESS;
+            }
         }
         
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 437bb72..c8d7329 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
@@ -23,10 +23,10 @@ import static org.apache.freemarker.core.TemplateCallableModelUtils.*;
 
 import java.io.IOException;
 import java.io.Writer;
-import java.util.Collection;
 
 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.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModel;
@@ -39,14 +39,24 @@ public class AllFeaturesDirective extends TestTemplateDirectiveModel {
 
     private static final int P1_ARG_IDX = 0;
     private static final int P2_ARG_IDX = 1;
-    private static final int P_OTHERS_ARG_IDX = 2;
-    private static final int N1_ARG_IDX = 3;
-    private static final int N2_ARG_IDX = 4;
-    private static final int N_OTHERS_IDX = 5;
+    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;
@@ -63,19 +73,15 @@ public class AllFeaturesDirective extends TestTemplateDirectiveModel {
         this.n2AllowNull = n2AllowNull;
     }
 
-    private static final StringToIndexMap PARAM_NAME_TO_IDX = StringToIndexMap.of(
-            N1_ARG_NAME, N1_ARG_IDX,
-            N2_ARG_NAME, N2_ARG_IDX);
-
     @Override
     public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
             throws TemplateException, IOException {
         execute(castArgumentToNumber(args, P1_ARG_IDX, p1AllowNull, env),
                 castArgumentToNumber(args, P2_ARG_IDX, p2AllowNull, env),
-                (TemplateSequenceModel) args[P_OTHERS_ARG_IDX],
+                (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_OTHERS_IDX],
+                (TemplateHashModelEx2) args[N_VARARGS_ARG_IDX],
                 out, env, callPlace);
     }
 
@@ -85,10 +91,10 @@ public class AllFeaturesDirective extends TestTemplateDirectiveModel {
         out.write("#a(");
         printParam("p1", p1, out, true);
         printParam("p2", p2, out);
-        printParam("pOthers", pOthers, out);
+        printParam("pVarargs", pOthers, out);
         printParam(N1_ARG_NAME, n1, out);
         printParam(N2_ARG_NAME, n2, out);
-        printParam("nOthers", nOthers, out);
+        printParam("nVarargs", nOthers, out);
         int loopVariableCount = callPlace.getLoopVariableCount();
         if (loopVariableCount != 0) {
             out.write("; " + loopVariableCount);
@@ -100,8 +106,9 @@ public class AllFeaturesDirective extends TestTemplateDirectiveModel {
             if (p1 != null) {
                 int intP1 = p1.getAsNumber().intValue();
                 for (int i = 0; i < intP1; i++) {
-                    TemplateModel[] loopVariableValues = new TemplateModel[loopVariableCount];
-                    for (int loopVarIdx = 0; loopVarIdx < loopVariableCount; loopVarIdx++) {
+                    // We limit the number of loop variables passed to 3, so that related errors can be tested.
+                    TemplateModel[] loopVariableValues = new TemplateModel[Math.min(loopVariableCount, 3)];
+                    for (int loopVarIdx = 0; loopVarIdx < loopVariableValues.length; loopVarIdx++) {
                         loopVariableValues[loopVarIdx] = new SimpleNumber((i + 1) * (loopVarIdx + 1));
                     }
                     callPlace.executeNestedContent(loopVariableValues, out, env);
@@ -112,32 +119,7 @@ public class AllFeaturesDirective extends TestTemplateDirectiveModel {
     }
 
     @Override
-    public int getPredefinedPositionalArgumentCount() {
-        return 2;
-    }
-
-    @Override
-    public boolean hasPositionalVarargsArgument() {
-        return true;
-    }
-
-    @Override
-    public int getPredefinedNamedArgumentIndex(String name) {
-        return PARAM_NAME_TO_IDX.get(name);
-    }
-
-    @Override
-    public int getNamedVarargsArgumentIndex() {
-        return N_OTHERS_IDX;
-    }
-
-    @Override
-    public Collection<String> getPredefinedNamedArgumentNames() {
-        return PARAM_NAME_TO_IDX.getKeys();
-    }
-
-    @Override
-    public int getArgumentArraySize() {
-        return N_OTHERS_IDX + 1;
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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
new file mode 100644
index 0000000..aa69e8f
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java
@@ -0,0 +1,59 @@
+/*
+ * 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.Environment;
+import org.apache.freemarker.core.NestedContentNotSupportedException;
+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.model.TemplateModel;
+
+public class NamedVarargsOnlyDirective extends TestTemplateDirectiveModel {
+
+    public static final NamedVarargsOnlyDirective INSTANCE = new NamedVarargsOnlyDirective();
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
+            0, false,
+            null, true);
+
+    private static final int NAMED_VARARGS_ARG_IDX = ARGS_LAYOUT.getNamedVarargsArgumentIndex();
+
+    private NamedVarargsOnlyDirective() {
+        //
+    }
+
+    @Override
+    public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
+            throws TemplateException, IOException {
+        NestedContentNotSupportedException.check(callPlace);
+        out.write("#nvo(");
+        printParam("nVarargs", args[NAMED_VARARGS_ARG_IDX], out, true);
+        out.write(")");
+    }
+
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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
new file mode 100644
index 0000000..cc3f9d8
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java
@@ -0,0 +1,57 @@
+/*
+ * 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.Environment;
+import org.apache.freemarker.core.NestedContentNotSupportedException;
+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.model.TemplateModel;
+
+public class PositionalVarargsOnlyDirective extends TestTemplateDirectiveModel {
+
+    public static final PositionalVarargsOnlyDirective INSTANCE = new PositionalVarargsOnlyDirective();
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
+            0, true,
+            null, false);
+
+    private PositionalVarargsOnlyDirective() {
+        //
+    }
+
+    @Override
+    public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
+            throws TemplateException, IOException {
+        NestedContentNotSupportedException.check(callPlace);
+        out.write("#pvo(");
+        printParam("pVarargs", args[ARGS_LAYOUT.getPositionalVarargsArgumentIndex()], out, true);
+        out.write(")");
+    }
+
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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
index c69497a..49bb049 100644
--- 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
@@ -22,7 +22,7 @@ package org.apache.freemarker.core.userpkg;
 import java.io.IOException;
 import java.io.Writer;
 
-import org.apache.freemarker.core.model.TemplateDirectiveModel2;
+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;
@@ -31,7 +31,7 @@ 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 TemplateDirectiveModel2 {
+public abstract class TestTemplateDirectiveModel implements TemplateDirectiveModel {
 
     protected void printParam(String name, TemplateModel value, Writer out) throws IOException, TemplateModelException {
         printParam(name, value, out, false);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 ed1b501..dbff203 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
@@ -21,28 +21,41 @@ package org.apache.freemarker.core.userpkg;
 
 import java.io.IOException;
 import java.io.Writer;
-import java.util.Collection;
 
 import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.NestedContentNotSupportedException;
 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.model.TemplateModel;
 import org.apache.freemarker.core.util.StringToIndexMap;
 
 public class TwoNamedParamsDirective extends TestTemplateDirectiveModel {
 
-    private static final String N1_ARG_NAME = "n1";
-    private static final String N2_ARG_NAME = "n2";
+    public static final TwoNamedParamsDirective INSTANCE = new TwoNamedParamsDirective();
+
     private static final int N1_ARG_IDX = 0;
     private static final int N2_ARG_IDX = 1;
 
-    private static final StringToIndexMap PARAM_NAME_TO_IDX = StringToIndexMap.of(
+    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 TwoNamedParamsDirective() {
+        //
+    }
+
     @Override
     public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
             throws TemplateException, IOException {
+        NestedContentNotSupportedException.check(callPlace);
         out.write("#n(");
         printParam(N1_ARG_NAME, args[N1_ARG_IDX], out, true);
         printParam(N2_ARG_NAME, args[N2_ARG_IDX], out);
@@ -50,32 +63,7 @@ public class TwoNamedParamsDirective extends TestTemplateDirectiveModel {
     }
 
     @Override
-    public int getPredefinedPositionalArgumentCount() {
-        return 0;
-    }
-
-    @Override
-    public boolean hasPositionalVarargsArgument() {
-        return false;
-    }
-
-    @Override
-    public int getPredefinedNamedArgumentIndex(String name) {
-        return PARAM_NAME_TO_IDX.get(name);
-    }
-
-    @Override
-    public int getNamedVarargsArgumentIndex() {
-        return -1;
-    }
-
-    @Override
-    public int getArgumentArraySize() {
-        return PARAM_NAME_TO_IDX.size();
-    }
-
-    @Override
-    public Collection<String> getPredefinedNamedArgumentNames() {
-        return PARAM_NAME_TO_IDX.getKeys();
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 ef51640..52fe77e 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
@@ -21,18 +21,30 @@ package org.apache.freemarker.core.userpkg;
 
 import java.io.IOException;
 import java.io.Writer;
-import java.util.Collection;
 
 import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.NestedContentNotSupportedException;
 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.model.TemplateModel;
 
 public class TwoPositionalParamsDirective extends TestTemplateDirectiveModel {
 
+    public static final TwoPositionalParamsDirective INSTANCE = new TwoPositionalParamsDirective();
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
+            2, false,
+            null, false);
+
+    private TwoPositionalParamsDirective() {
+        //
+    }
+
     @Override
     public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
             throws TemplateException, IOException {
+        NestedContentNotSupportedException.check(callPlace);
         out.write("#p(");
         printParam("p1", args[0], out, true);
         printParam("p2", args[1], out);
@@ -40,32 +52,7 @@ public class TwoPositionalParamsDirective extends TestTemplateDirectiveModel {
     }
 
     @Override
-    public int getPredefinedPositionalArgumentCount() {
-        return 2;
-    }
-
-    @Override
-    public boolean hasPositionalVarargsArgument() {
-        return false;
-    }
-
-    @Override
-    public int getPredefinedNamedArgumentIndex(String name) {
-        return -1;
-    }
-
-    @Override
-    public int getNamedVarargsArgumentIndex() {
-        return -1;
-    }
-
-    @Override
-    public int getArgumentArraySize() {
-        return getPredefinedPositionalArgumentCount();
-    }
-
-    @Override
-    public Collection<String> getPredefinedNamedArgumentNames() {
-        return null;
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 b23e56c..507b820 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
@@ -22,14 +22,21 @@ package org.apache.freemarker.core.userpkg;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Collection;
 
 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.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 
-public class UpperCaseDirective extends TestTemplateDirectiveModel {
+public class UpperCaseDirective implements TemplateDirectiveModel {
+
+    public static final UpperCaseDirective INSTANCE = new UpperCaseDirective();
+
+    private UpperCaseDirective() {
+        //
+    }
 
     @Override
     public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
@@ -40,32 +47,7 @@ public class UpperCaseDirective extends TestTemplateDirectiveModel {
     }
 
     @Override
-    public int getPredefinedPositionalArgumentCount() {
-        return 0;
-    }
-
-    @Override
-    public boolean hasPositionalVarargsArgument() {
-        return false;
-    }
-
-    @Override
-    public int getPredefinedNamedArgumentIndex(String name) {
-        return -1;
-    }
-
-    @Override
-    public int getNamedVarargsArgumentIndex() {
-        return -1;
-    }
-
-    @Override
-    public int getArgumentArraySize() {
-        return 0;
-    }
-
-    @Override
-    public Collection<String> getPredefinedNamedArgumentNames() {
-        return null;
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ArgumentArrayLayout.PARAMETERLESS;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 8900d2b..830a16c 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
@@ -22,6 +22,7 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
+import java.io.Writer;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.Collections;
@@ -33,7 +34,8 @@ import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateConfiguration;
 import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
+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.TemplateModelException;
@@ -177,12 +179,16 @@ public class NumberFormatTest extends TemplateTest {
         nm.setNumber(123);
         addToDataModel("n", nm);
         addToDataModel("incN", new TemplateDirectiveModel() {
-            
             @Override
-            public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+            public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
                     throws TemplateException, IOException {
                 nm.setNumber(nm.getAsNumber().intValue() + 1);
             }
+
+            @Override
+            public ArgumentArrayLayout getArgumentArrayLayout() {
+                return ArgumentArrayLayout.PARAMETERLESS;
+            }
         });
         assertOutput(
                 "<#assign s1 = n?string>"

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 90b4956..b7cf57f 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
@@ -19,7 +19,7 @@
 #mixedContent  // o.a.f.c.ASTImplicitParent
     #text  // o.a.f.c.ASTStaticText
         - content: "1 "  // String
-    @  // o.a.f.c.ASTDirUserDefined
+    @  // o.a.f.c.ASTDynamicTopLevelCall
         - callee: foo  // o.a.f.c.ASTExpVariable
         - argument name: "x"  // String
         - argument value: 1  // o.a.f.c.ASTExpNumberLiteral
@@ -31,7 +31,7 @@
             - content: "x"  // String
     #text  // o.a.f.c.ASTStaticText
         - content: "\n2 "  // String
-    @  // o.a.f.c.ASTDirUserDefined
+    @  // o.a.f.c.ASTDynamicTopLevelCall
         - callee: .  // o.a.f.c.ASTExpDot
             - left-hand operand: ns  // o.a.f.c.ASTExpVariable
             - right-hand operand: "bar"  // String

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-range.ast
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-range.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-range.ast
index 474b298..9d9bb4b 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-range.ast
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-range.ast
@@ -257,7 +257,7 @@
                     - AST-node subtype: "0"  // Integer
     #text  // o.a.f.c.ASTStaticText
         - content: "\n\n"  // String
-    @  // o.a.f.c.ASTDirUserDefined
+    @  // o.a.f.c.ASTDynamicTopLevelCall
         - callee: m  // o.a.f.c.ASTExpVariable
         - argument value: ..  // o.a.f.c.ASTExpRange
             - left-hand operand: *  // o.a.f.c.ASTExpArithmetic

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-strlitinterpolation.ast
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-strlitinterpolation.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-strlitinterpolation.ast
index da4cf66..a87144e 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-strlitinterpolation.ast
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-strlitinterpolation.ast
@@ -19,7 +19,7 @@
 #mixedContent  // o.a.f.c.ASTImplicitParent
     #text  // o.a.f.c.ASTStaticText
         - content: "1. "  // String
-    @  // o.a.f.c.ASTDirUserDefined
+    @  // o.a.f.c.ASTDynamicTopLevelCall
         - callee: m  // o.a.f.c.ASTExpVariable
         - argument name: "x"  // String
         - argument value: dynamic "..."  // o.a.f.c.ASTExpStringLiteral

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-whitespacestripping.ast
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-whitespacestripping.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-whitespacestripping.ast
index 41e3b12..dd87f1f 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-whitespacestripping.ast
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-whitespacestripping.ast
@@ -50,17 +50,17 @@
         - namespace: null  // Null
     #text  // o.a.f.c.ASTStaticText
         - content: "\n"  // String
-    @  // o.a.f.c.ASTDirUserDefined
+    @  // o.a.f.c.ASTDynamicTopLevelCall
         - callee: b  // o.a.f.c.ASTExpVariable
         #text  // o.a.f.c.ASTStaticText
             - content: "    x\n"  // String
     #text  // o.a.f.c.ASTStaticText
         - content: "\n"  // String
-    @  // o.a.f.c.ASTDirUserDefined
+    @  // o.a.f.c.ASTDynamicTopLevelCall
         - callee: c  // o.a.f.c.ASTExpVariable
     #text  // o.a.f.c.ASTStaticText
         - content: "\n"  // String
-    @  // o.a.f.c.ASTDirUserDefined
+    @  // o.a.f.c.ASTDynamicTopLevelCall
         - callee: d  // o.a.f.c.ASTExpVariable
     #text  // o.a.f.c.ASTStaticText
         - content: "\na\n"  // String

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirDynamicCall.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirDynamicCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirDynamicCall.java
deleted file mode 100644
index 23e081f..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirDynamicCall.java
+++ /dev/null
@@ -1,468 +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;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-
-import org.apache.freemarker.core.model.CallPlace;
-import org.apache.freemarker.core.model.Constants;
-import org.apache.freemarker.core.model.TemplateCallableModel;
-import org.apache.freemarker.core.model.TemplateDirectiveModel2;
-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.StringToIndexMap;
-import org.apache.freemarker.core.util._ArrayAdapterList;
-import org.apache.freemarker.core.util._StringUtil;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * AST directive node: {@code <@exp ...>}.
- * Executes a {@link TemplateCallableModel} that's embedded directly into the static text. At least in the default
- * template language the value must be a {@link TemplateDirectiveModel2}, though technically calling a
- * {@link TemplateFunctionModel} is possible as well.
- * <p>
- * The {@link TemplateCallableModel} object is obtained on runtime by evaluating an expression, and the parameter list
- * is also validated (how many positional parameters are allowed, what named parameters are supported) then. Hence, the
- * call is "dynamic".
- */
-class ASTDirDynamicCall 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;
-    private final StringToIndexMap loopVarNames;
-    private final boolean allowCallingFunctions;
-
-    private CustomDataHolder customDataHolder;
-
-    /**
-     * @param allowCallingFunctions Some template languages may allow calling {@link TemplateFunctionModel}-s
-     *                              directly embedded into the static text, in which case this should be {@code true}.
-     */
-    ASTDirDynamicCall(
-            ASTExpression callableValueExp, boolean allowCallingFunctions,
-            ASTExpression[] positionalArgs, NamedArgument[] namedArgs, StringToIndexMap loopVarNames,
-            TemplateElements children) {
-        this.callableValueExp = callableValueExp;
-        this.allowCallingFunctions = allowCallingFunctions;
-
-        if (positionalArgs != null && positionalArgs.length == 0
-                || namedArgs != null && namedArgs.length == 0
-                || loopVarNames != null && loopVarNames.size() == 0) {
-            throw new IllegalArgumentException("Use null instead of empty collections");
-        }
-        this.positionalArgs = positionalArgs;
-        this.namedArgs = namedArgs;
-        this.loopVarNames = loopVarNames;
-
-        setChildren(children);
-    }
-
-    @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
-        TemplateCallableModel callableValue;
-        TemplateDirectiveModel2 directive;
-        TemplateFunctionModel function;
-        {
-            TemplateModel callableValueTM = callableValueExp._eval(env);
-            if (callableValueTM instanceof TemplateDirectiveModel2) {
-                callableValue = (TemplateCallableModel) callableValueTM;
-                directive = (TemplateDirectiveModel2) callableValueTM;
-                function = null;
-            } else if (callableValueTM instanceof TemplateFunctionModel) {
-                if (!allowCallingFunctions) {
-                    // TODO [FM3][CF] Better exception
-                    throw new NonUserDefinedDirectiveLikeException(
-                            "Calling functions is not allowed on the top level in this template language", env);
-                }
-                callableValue = (TemplateCallableModel) callableValueTM;
-                directive = null;
-                function = (TemplateFunctionModel) callableValue;
-            } else if (callableValueTM instanceof ASTDirMacro) {
-                // TODO [FM3][CF] Until macros were refactored to be TemplateDirectiveModel2-s, we have this hack here.
-                ASTDirMacro macro = (ASTDirMacro) callableValueTM;
-                if (macro.isFunction()) {
-                    throw new _MiscTemplateException(env,
-                            "Routine ", new _DelayedJQuote(macro.getName()), " is a function, not a directive. "
-                            + "Functions can only be called from expressions, like in ${f()}, ${x + f()} or ",
-                            "<@someDirective someParam=f() />", ".");
-                }
-
-                // We have to convert arguments to the legacy data structures... yet again, it's only a temporary hack.
-                LinkedHashMap<String, ASTExpression> macroNamedArgs;
-                if (namedArgs != null) {
-                    macroNamedArgs = new LinkedHashMap<>(namedArgs.length * 4 / 3);
-                    for (NamedArgument namedArg : namedArgs) {
-                        macroNamedArgs.put(namedArg.name, namedArg.value);
-                    }
-                } else {
-                    macroNamedArgs = null;
-                }
-                env.invoke(macro,
-                        macroNamedArgs,
-                        _ArrayAdapterList.adapt(positionalArgs),
-                        loopVarNames != null ? loopVarNames.getKeys() : null,
-                        getChildBuffer());
-                return null;
-            } else if (callableValueTM == null) {
-                throw InvalidReferenceException.getInstance(callableValueExp, env);
-            } else {
-                throw new NonUserDefinedDirectiveLikeException(callableValueExp, callableValueTM, env);
-            }
-        }
-
-        int predefPosArgCnt = callableValue.getPredefinedPositionalArgumentCount();
-        boolean hasPosVarargsArg = callableValue.hasPositionalVarargsArgument();
-
-        if (positionalArgs != null && positionalArgs.length > predefPosArgCnt && !hasPosVarargsArg) {
-            throw new _MiscTemplateException(this,
-                    "The target callable ",
-                    (predefPosArgCnt != 0
-                        ? new Object[] { "can only have ", predefPosArgCnt }
-                        : "can't have"
-                    ),
-                    " arguments passed by position, but the invocation has ",
-                    positionalArgs.length, " such arguments.");
-        }
-
-        TemplateModel[] execArgs = new TemplateModel[callableValue.getArgumentArraySize()];
-
-        // 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 (hasPosVarargsArg) {
-            int posVarargCnt = positionalArgs != null ? positionalArgs.length - predefPosArgCnt : 0;
-            TemplateSequenceModel varargsSeq;
-            if (posVarargCnt <= 0) {
-                varargsSeq = Constants.EMPTY_SEQUENCE;
-            } else {
-                NativeSequence nativeSeq = new NativeSequence(posVarargCnt);
-                varargsSeq = nativeSeq;
-                for (int posVarargIdx = 0; posVarargIdx < posVarargCnt; posVarargIdx++) {
-                    nativeSeq.add(positionalArgs[predefPosArgCnt + posVarargIdx].eval(env));
-                }
-            }
-            execArgs[predefPosArgCnt] = varargsSeq;
-        }
-
-        int namedVarargsArgumentIndex = callableValue.getNamedVarargsArgumentIndex();
-        NativeHashEx2 namedVarargsHash = null;
-        if (namedArgs != null) {
-            for (NamedArgument namedArg : namedArgs) {
-                int argIdx = callableValue.getPredefinedNamedArgumentIndex(namedArg.name);
-                if (argIdx != -1) {
-                    execArgs[argIdx] = namedArg.value.eval(env);
-                } else {
-                    if (namedVarargsHash == null) {
-                        if (namedVarargsArgumentIndex == -1) {
-                            Collection<String> validNames = callableValue.getPredefinedNamedArgumentNames();
-                            throw new _MiscTemplateException(this,
-                                    validNames == null || validNames.isEmpty()
-                                    ? new Object[] {
-                                            "The target callable doesn't have any by-name-passed parameters (like ",
-                                            new _DelayedJQuote(namedArg.name), ")"
-                                    }
-                                    : new Object[] {
-                                            "The target callable has no by-name-passed parameter 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;
-        }
-
-        if (directive != null) {
-            directive.execute(execArgs, this, env.getOut(), env);
-        } else {
-            TemplateModel result = function.execute(execArgs, env, this);
-            if (result == null) {
-                throw new _MiscTemplateException(this, "Function has returned no value (or null)");
-            }
-            // TODO [FM3][CF]
-            throw new BugException("Top-level function call not yet implemented");
-        }
-
-        return null;
-    }
-
-    @Override
-    boolean isNestedBlockRepeater() {
-        return true;
-    }
-
-    @Override
-    boolean isShownInStackTrace() {
-        return true;
-    }
-
-    @Override
-    protected String dump(boolean canonical) {
-        StringBuilder sb = new StringBuilder();
-        if (canonical) sb.append('<');
-        sb.append('@');
-        MessageUtil.appendExpressionAsUntearable(sb, callableValueExp);
-        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());
-            }
-        }
-        if (namedArgs != null) {
-            for (NamedArgument namedArg : namedArgs) {
-                sb.append(' ');
-                sb.append(_StringUtil.toFTLTopLevelIdentifierReference(namedArg.name));
-                sb.append('=');
-                MessageUtil.appendExpressionAsUntearable(sb, namedArg.value);
-            }
-        }
-        if (loopVarNames != null) {
-            sb.append("; ");
-            boolean first = true;
-            for (String loopVarName : loopVarNames.getKeys()) {
-                if (!first) {
-                    sb.append(", ");
-                } else {
-                    first = false;
-                }
-                sb.append(_StringUtil.toFTLTopLevelIdentifierReference(loopVarName));
-            }
-        }
-        if (canonical) {
-            if (getChildCount() == 0) {
-                sb.append("/>");
-            } else {
-                sb.append('>');
-                sb.append(getChildrenCanonicalForm());
-                sb.append("</@");
-                if (!nameIsInParen
-                        && (callableValueExp instanceof ASTExpVariable
-                        || (callableValueExp instanceof ASTExpDot && ((ASTExpDot) callableValueExp).onlyHasIdentifiers()))) {
-                    sb.append(callableValueExp.getCanonicalForm());
-                }
-                sb.append('>');
-            }
-        }
-        return sb.toString();
-    }
-
-    @Override
-    String getASTNodeDescriptor() {
-        return "~";
-    }
-
-    @Override
-    int getParameterCount() {
-        return 1/*nameExp*/
-                + (positionalArgs != null ? positionalArgs.length : 0)
-                + (namedArgs != null ? namedArgs.length * 2 : 0)
-                + (loopVarNames != null ? loopVarNames.size() : 0);
-    }
-
-    @Override
-    Object getParameterValue(int idx) {
-        if (idx == 0) {
-            return callableValueExp;
-        } else {
-            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.name : namedArg.value;
-                } else {
-                    base += namedArgsSize * 2;
-                    final int bodyParameterNamesSize = loopVarNames != null ? loopVarNames.size() : 0;
-                    if (idx - base < bodyParameterNamesSize) {
-                        return loopVarNames.getKeys().get(idx - base);
-                    } else {
-                        throw new IndexOutOfBoundsException();
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    ParameterRole getParameterRole(int idx) {
-        if (idx == 0) {
-            return ParameterRole.CALLEE;
-        } else {
-            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 {
-                    base += namedArgsSize * 2;
-                    final int bodyParameterNamesSize = loopVarNames != null ? loopVarNames.size() : 0;
-                    if (idx - base < bodyParameterNamesSize) {
-                        return ParameterRole.TARGET_LOOP_VARIABLE;
-                    } else {
-                        throw new IndexOutOfBoundsException();
-                    }
-                }
-            }
-        }
-    }
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // CallPlace API:
-
-    @Override
-    public boolean hasNestedContent() {
-        return getChildCount() != 0;
-    }
-
-    @Override
-    public int getLoopVariableCount() {
-        return loopVarNames != null ? loopVarNames.size() : 0;
-    }
-
-    @Override
-    public void executeNestedContent(TemplateModel[] loopVariableValues, Writer out, Environment env)
-            throws TemplateException, IOException {
-        env.visit(getChildBuffer(), loopVarNames, loopVariableValues, out);
-    }
-
-    @Override
-    @SuppressFBWarnings(value={ "IS2_INCONSISTENT_SYNC", "DC_DOUBLECHECK" }, justification="Performance tricks")
-    public Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> supplier)
-            throws CallPlaceCustomDataInitializationException {
-        // We are using double-checked locking, utilizing Java memory model "final" trick.
-        // Note that this.customDataHolder is NOT volatile.
-
-        CustomDataHolder customDataHolder = this.customDataHolder;  // Findbugs false alarm
-        if (customDataHolder == null) {  // Findbugs false alarm
-            synchronized (this) {
-                customDataHolder = this.customDataHolder;
-                if (customDataHolder == null || customDataHolder.providerIdentity != providerIdentity) {
-                    customDataHolder = createNewCustomData(providerIdentity, supplier);
-                    this.customDataHolder = customDataHolder;
-                }
-            }
-        }
-
-        if (customDataHolder.providerIdentity != providerIdentity) {
-            synchronized (this) {
-                customDataHolder = this.customDataHolder;
-                if (customDataHolder == null || customDataHolder.providerIdentity != providerIdentity) {
-                    customDataHolder = createNewCustomData(providerIdentity, supplier);
-                    this.customDataHolder = customDataHolder;
-                }
-            }
-        }
-
-        return customDataHolder.customData;
-    }
-
-    private CustomDataHolder createNewCustomData(Object provierIdentity, CommonSupplier supplier)
-            throws CallPlaceCustomDataInitializationException {
-        CustomDataHolder customDataHolder;
-        Object customData;
-        try {
-            customData = supplier.get();
-        } catch (Exception e) {
-            throw new CallPlaceCustomDataInitializationException(
-                    "Failed to initialize custom data for provider identity "
-                            + _StringUtil.tryToString(provierIdentity) + " via factory "
-                            + _StringUtil.tryToString(supplier), e);
-        }
-        if (customData == null) {
-            throw new NullPointerException("CommonSupplier.get() has returned null");
-        }
-        customDataHolder = new CustomDataHolder(provierIdentity, customData);
-        return customDataHolder;
-    }
-
-    @Override
-    public boolean isNestedOutputCacheable() {
-        return isChildrenOutputCacheable();
-    }
-
-    @Override
-    public int getFirstTargetJavaParameterTypeIndex() {
-        // TODO [FM3]
-        return -1;
-    }
-
-    @Override
-    public Class<?> getTargetJavaParameterType(int argIndex) {
-        // TODO [FM3]
-        return null;
-    }
-
-    /**
-     * Used for implementing double check locking in implementing the
-     * {@link #getOrCreateCustomData(Object, CommonSupplier)}.
-     */
-    private static class CustomDataHolder {
-
-        private final Object providerIdentity;
-        private final Object customData;
-        public CustomDataHolder(Object providerIdentity, Object customData) {
-            this.providerIdentity = providerIdentity;
-            this.customData = customData;
-        }
-
-    }
-
-}