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

[18/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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
index e9b2c4e..b67d54d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
@@ -389,7 +389,7 @@ abstract class ASTElement extends ASTNode {
 
     private ASTElement getFirstLeaf() {
         ASTElement te = this;
-        while (!te.isLeaf() && !(te instanceof ASTDirMacro) && !(te instanceof ASTDirCapturingAssignment)) {
+        while (!te.isLeaf() && !(te instanceof ASTDirMacroOrFunction) && !(te instanceof ASTDirCapturingAssignment)) {
             // A macro or macro invocation is treated as a leaf here for special reasons
             te = te.getFirstChild();
         }
@@ -398,7 +398,7 @@ abstract class ASTElement extends ASTNode {
 
     private ASTElement getLastLeaf() {
         ASTElement te = this;
-        while (!te.isLeaf() && !(te instanceof ASTDirMacro) && !(te instanceof ASTDirCapturingAssignment)) {
+        while (!te.isLeaf() && !(te instanceof ASTDirMacroOrFunction) && !(te instanceof ASTDirCapturingAssignment)) {
             // A macro or macro invocation is treated as a leaf here for special reasons
             te = te.getLastChild();
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
index 74ae72f..ab50470 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
@@ -145,7 +145,7 @@ final class ASTExpBuiltInVariable extends ASTExpression {
             return env.getGlobalVariables();
         }
         if (name == LOCALS) {
-            ASTDirMacro.Context ctx = env.getCurrentMacroContext();
+            ASTDirMacroOrFunction.Context ctx = env.getCurrentMacroContext();
             return ctx == null ? null : ctx.getLocals();
         }
         if (name == DATA_MODEL) {
@@ -173,7 +173,7 @@ final class ASTExpBuiltInVariable extends ASTExpression {
             return SimpleScalar.newInstanceOrNull(env.getCurrentTemplate().getLookupName());
         }
         if (name == PASS) {
-            return ASTDirMacro.DO_NOTHING_MACRO;
+            return ASTDirMacroOrFunction.PASS_MACRO;
         }
         if (name == OUTPUT_ENCODING) {
             Charset encoding = env.getOutputEncoding();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
index 1612a79..e5dc679 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
@@ -81,11 +81,11 @@ final class ASTExpListLiteral extends ASTExpression {
     /**
      * For {@link TemplateMethodModelEx} calls, returns the list of arguments as {@link TemplateModel}-s.
      */
-    List/*<TemplateModel>*/ getModelList(Environment env) throws TemplateException {
+    List<TemplateModel> getModelList(Environment env) throws TemplateException {
         int size = items.size();
         switch(size) {
             case 0: {
-                return Collections.EMPTY_LIST;
+                return Collections.emptyList();
             }
             case 1: {
                 return Collections.singletonList(((ASTExpression) items.get(0)).eval(env));
@@ -101,6 +101,10 @@ final class ASTExpListLiteral extends ASTExpression {
         }
     }
 
+    public int size() {
+        return items.size();
+    }
+
     @Override
     public String getCanonicalForm() {
         StringBuilder buf = new StringBuilder("[");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpMethodCall.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpMethodCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpMethodCall.java
index 4f51904..dc8f3ff 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpMethodCall.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpMethodCall.java
@@ -28,16 +28,21 @@ import java.io.Writer;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.Constants;
+import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateMethodModel;
 import org.apache.freemarker.core.model.TemplateMethodModelEx;
 import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.util._NullWriter;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.util.CommonSupplier;
+import org.apache.freemarker.core.util.FTLUtil;
 
 
 /**
  * AST expression node: {@code exp(args)}.
  */
-final class ASTExpMethodCall extends ASTExpression {
+final class ASTExpMethodCall extends ASTExpression implements CallPlace {
 
     private final ASTExpression target;
     private final ASTExpListLiteral arguments;
@@ -62,23 +67,55 @@ final class ASTExpMethodCall extends ASTExpression {
             : arguments.getValueList(env);
             Object result = targetMethod.exec(argumentStrings);
             return env.getObjectWrapper().wrap(result);
-        } else if (targetModel instanceof ASTDirMacro) {
-            ASTDirMacro func = (ASTDirMacro) targetModel;
-            env.setLastReturnValue(null);
-            if (!func.isFunction()) {
-                throw new _MiscTemplateException(env, "A macro cannot be called in an expression. (Functions can be.)");
+        } else if (targetModel instanceof TemplateFunctionModel) {
+            TemplateFunctionModel func = (TemplateFunctionModel) targetModel;
+
+            ArgumentArrayLayout arrayLayout = func.getArgumentArrayLayout();
+
+            // TODO [FM3] This is just temporary, until we support named args. Then the logic in ASTDynamicTopLevelCall
+            // should be reused.
+
+            int posVarargsLength;
+            int callArgCnt = arguments.size();
+            int predefPosArgCnt = arrayLayout.getPredefinedPositionalArgumentCount();
+            int posVarargsIdx = arrayLayout.getPositionalVarargsArgumentIndex();
+            if (callArgCnt > predefPosArgCnt) {
+                if (posVarargsIdx == -1) {
+                    throw new _MiscTemplateException(env,
+                            "Too many arguments; the target ", FTLUtil.getCallableTypeName(func),
+                            " has ", predefPosArgCnt, " arguments.");
+                }
             }
-            Writer prevOut = env.getOut();
-            try {
-                env.setOut(_NullWriter.INSTANCE);
-                env.invoke(func, null, arguments.items, null, null);
-            } catch (IOException e) {
-                // Should not occur
-                throw new TemplateException("Unexpected exception during function execution", e, env);
-            } finally {
-                env.setOut(prevOut);
+
+            List<TemplateModel> callArgList = arguments.getModelList(env);
+
+            TemplateModel[] args = new TemplateModel[arrayLayout.getTotalLength()];
+            int callPredefArgCnt = Math.min(callArgCnt, predefPosArgCnt);
+            for (int argIdx = 0; argIdx < callPredefArgCnt; argIdx++) {
+                args[argIdx] = callArgList.get(argIdx);
             }
-            return env.getLastReturnValue();
+
+            if (posVarargsIdx != -1) {
+                TemplateSequenceModel varargsSeq;
+                posVarargsLength = callArgCnt - predefPosArgCnt;
+                if (posVarargsLength <= 0) {
+                    varargsSeq = Constants.EMPTY_SEQUENCE;
+                } else {
+                    NativeSequence nativeSeq = new NativeSequence(posVarargsLength);
+                    varargsSeq = nativeSeq;
+                    for (int posVarargIdx = 0; posVarargIdx < posVarargsLength; posVarargIdx++) {
+                        nativeSeq.add(callArgList.get(predefPosArgCnt + posVarargIdx));
+                    }
+                }
+                args[posVarargsIdx] = varargsSeq;
+            }
+
+            int namedVarargsArgIdx = arrayLayout.getNamedVarargsArgumentIndex();
+            if (namedVarargsArgIdx != -1) {
+                args[namedVarargsArgIdx] = Constants.EMPTY_HASH;
+            }
+
+            return func.execute(args, this, env);
         } else {
             throw new NonMethodException(target, targetModel, env);
         }
@@ -144,4 +181,50 @@ final class ASTExpMethodCall extends ASTExpression {
         }
     }
 
+    // -----------------------------------------------------------------------------------------------------------------
+    // CallPlace API
+
+    @Override
+    public boolean hasNestedContent() {
+        return false;
+    }
+
+    @Override
+    public int getNestedContentParameterCount() {
+        return 0;
+    }
+
+    @Override
+    public void executeNestedContent(TemplateModel[] nestedContentArgs, Writer out, Environment env)
+            throws TemplateException, IOException {
+        // Do nothing
+    }
+
+    @Override
+    public Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> supplier)
+            throws CallPlaceCustomDataInitializationException {
+        throw new UnsupportedOperationException("Expression call places don't store custom data");
+    }
+
+    @Override
+    public boolean isCustomDataSupported() {
+        return false;
+    }
+
+    @Override
+    public boolean isNestedOutputCacheable() {
+        return false;
+    }
+
+    @Override
+    public int getFirstTargetJavaParameterTypeIndex() {
+        // TODO [FM3]
+        return -1;
+    }
+
+    @Override
+    public Class<?> getTargetJavaParameterType(int argIndex) {
+        // TODO [FM3]
+        return null;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java
index 1ed4d2d..eceb412 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java
@@ -370,7 +370,7 @@ final class ASTStaticText extends ASTElement {
     
 
     private boolean nonOutputtingType(ASTElement element) {
-        return (element instanceof ASTDirMacro ||
+        return (element instanceof ASTDirMacroOrFunction ||
                 element instanceof ASTDirAssignment || 
                 element instanceof ASTDirAssignmentsContainer ||
                 element instanceof ASTDirSetting ||

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
index 467a2f4..74f7c15 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
@@ -312,8 +312,8 @@ class BuiltInsForMultipleTypes {
         TemplateModel _eval(Environment env) throws TemplateException {
             TemplateModel tm = target.eval(env);
             target.assertNonNull(tm, env);
-            // WRONG: it also had to check ASTDirMacro.isFunction()
-            return (tm instanceof ASTDirMacro || tm instanceof TemplateDirectiveModel) ?
+            // WRONG: it also had to check ASTDirMacroOrFunction.isFunction()
+            return (tm instanceof ASTDirMacroOrFunction || tm instanceof TemplateDirectiveModel) ?
                 TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
         }
     }
@@ -360,8 +360,7 @@ class BuiltInsForMultipleTypes {
         TemplateModel _eval(Environment env) throws TemplateException {
             TemplateModel tm = target.eval(env);
             target.assertNonNull(tm, env);
-            // WRONG: it also had to check ASTDirMacro.isFunction()
-            return (tm instanceof ASTDirMacro)  ?
+            return (tm instanceof Environment.TemplateLanguageDirective)  ?
                 TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
         }
     }
@@ -430,14 +429,13 @@ class BuiltInsForMultipleTypes {
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {
             TemplateModel tm = target.eval(env);
-            if (!(tm instanceof ASTDirMacro)) {
+            if (!(tm instanceof Environment.TemplateLanguageCallable)) {
                 throw new UnexpectedTypeException(
                         target, tm,
-                        "macro or function", new Class[] { ASTDirMacro.class },
+                        "macro or function", new Class[] { Environment.TemplateLanguageCallable.class },
                         env);
-            } else {
-                return env.getMacroNamespace((ASTDirMacro) tm);
             }
+            return ((Environment.TemplateLanguageCallable) tm).getNamespace();
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
index d9ad1bf..2d50556 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
@@ -25,7 +25,6 @@ import java.io.Writer;
 import java.util.List;
 
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
-import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlace.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlace.java b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlace.java
new file mode 100644
index 0000000..628c9ec
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlace.java
@@ -0,0 +1,195 @@
+/*
+ * 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.IdentityHashMap;
+
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.util.CommonSupplier;
+
+/**
+ * The place (in a template, usually) from where a directive (like a macro) or function was called;
+ * <b>Do not implement this interface yourself</b>, as new methods may be added any time! Only FreeMarker itself
+ * should provide implementations. In case you have to call something from outside a template, use
+ * {@link NonTemplateCallPlace#INSTANCE}.
+ */
+public interface CallPlace {
+
+    // -------------------------------------------------------------------------------------------------------------
+    // Nested content:
+
+    /**
+     * Tells if there's a non-zero-length nested content. This is {@code false} for {@code <@foo />} or
+     * {@code <@f...@foo>} or for calls inside expressions (i.e., for function calls).
+     */
+    boolean hasNestedContent();
+
+    /**
+     * The number of nested content parameters in this call (like 2 in {@code <@foo xs; k, v>...</@>}). If you want the
+     * caller to specify a fixed number of nested content parameters, then this is not interesting for you, and just
+     * pass an array of that length to {@link #executeNestedContent(TemplateModel[], Writer, Environment)}. If, however,
+     * you want to allow the caller to declare less parameters, then this is how you know how much parameters you should
+     * calculate and pass to {@link #executeNestedContent(TemplateModel[], Writer, Environment)}.
+     */
+    int getNestedContentParameterCount();
+
+    /**
+     * Executes the nested content; it there's none, it just does nothing.
+     *
+     * @param nestedContentArgs
+     *         The nested content parameter values to pass to the nested content (as in {@code <@foo bar; i, j>${i},
+     *         ${j}</...@foo>} there are 2 such parameters, whose value you set here), or {@code null} if there's none.
+     *         This array must be {@link #getNestedContentParameterCount()} long, or else FreeMarker will throw an
+     *         {@link TemplateException} with a descriptive error message that tells to user that they need to declare
+     *         that many nested content parameters as the length of this array. If you want to allow the  caller to not
+     *         declare some of the nested content parameters, then you have to make this array shorter according to
+     *         {@link #getNestedContentParameterCount()}.
+     */
+    void executeNestedContent(TemplateModel[] nestedContentArgs, Writer out, Environment env)
+            throws TemplateException, IOException;
+
+    // -------------------------------------------------------------------------------------------------------------
+    // Source code info:
+
+    /**
+     * The template that contains this call; {@code null} if the call is not from a template (but directly from
+     * user Java code, for example).
+     */
+    Template getTemplate();
+
+    /**
+     * The 1-based column number of the first character of the directive call in the template source code, or -1 if it's
+     * not known.
+     */
+    int getBeginColumn();
+
+    /**
+     * The 1-based line number of the first character of the directive call in the template source code, or -1 if it's
+     * not known.
+     */
+    int getBeginLine();
+
+    /**
+     * The 1-based column number of the last character of the directive call in the template source code, or -1 if it's
+     * not known. If the directive has an end-tag ({@code </...@...>}), then it points to the last character of that.
+     */
+    int getEndColumn();
+
+    /**
+     * The 1-based line number of the last character of the directive call in the template source code, or -1 if it's
+     * not known. If the directive has an end-tag ({@code </...@...>}), then it points to the last character of that.
+     */
+    int getEndLine();
+
+    // -------------------------------------------------------------------------------------------------------------
+    // Caching:
+
+    /**
+     * Returns the custom data, or if that's {@code null}, then it creates and stores it in an atomic operation then
+     * returns it. This method is thread-safe, however, it doesn't ensure thread safe (like synchronized) access to the
+     * custom data itself. Be sure that the custom data only depends on things that get their final value during
+     * template parsing, not on runtime settings.
+     * <p>
+     * This method will block other calls while the {@code supplier} is executing, thus, the object will be
+     * <em>usually</em> created only once, even if multiple threads request the value when it's still {@code null}. It
+     * doesn't stand though when {@code providerIdentity} mismatches occur (see later). Furthermore, then it's also
+     * possible that multiple objects created by the same {@link CommonSupplier} will be in use on the same time,
+     * because of directive executions already running in parallel, and because of memory synchronization delays
+     * (hardware dependent) between the threads.
+     *
+     * @param providerIdentity
+     *         This is usually the class of the {@link TemplateDirectiveModel} that creates (and uses) the custom data,
+     *         or if you are using your own class for the custom data object (as opposed to a class from some more
+     *         generic API), then that class. This is needed as the same call place might calls different directives
+     *         depending on runtime conditions, and so it must be ensured that these directives won't accidentally read
+     *         each other's custom data, ending up with class cast exceptions or worse. In the current implementation,
+     *         if there's a {@code providerIdentity} mismatch (means, the {@code providerIdentity} object used when the
+     *         custom data was last set isn't the exactly same object as the one provided with the parameter now), the
+     *         previous custom data will be just ignored as if it was {@code null}. So if multiple directives that use
+     *         the custom data feature use the same call place, the caching of the custom data can be inefficient, as
+     *         they will keep overwriting each other's custom data. (In a more generic implementation the {@code
+     *         providerIdentity} would be a key in a {@link IdentityHashMap}, but then this feature would be slower,
+     *         while {@code providerIdentity} mismatches aren't occurring in most applications.)
+     * @param supplier
+     *         Called when the custom data wasn't yet set, to invoke its initial value. If this parameter is {@code
+     *         null} and the custom data wasn't set yet, then {@code null} will be returned. The returned value of
+     *         {@link CommonSupplier#get()} can be any kind of object, but can't be {@code null}.
+     *
+     * @return The current custom data object, or possibly {@code null} if there was no {@link CommonSupplier} provided.
+     *
+     * @throws CallPlaceCustomDataInitializationException
+     *         If the {@link CommonSupplier} had to be invoked but failed.
+     * @throws UnsupportedOperationException
+     *         If this call place doesn't support storing custom date; see {@link #isCustomDataSupported()}.
+     */
+    Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> supplier)
+            throws CallPlaceCustomDataInitializationException;
+
+    /**
+     * Tells if this call place supports storing custom data. As of this writing, only top-level (i.e., outside
+     * expression) directive calls do.
+     */
+    boolean isCustomDataSupported();
+
+    /**
+     * Tells if the output of the nested content can be safely cached, as it only depends on the template content (not
+     * on variable values and such) and has no side-effects (other than writing to the output). Examples of cases that
+     * give {@code false}: {@code <@foo>Name: } <tt...@foo>}, {@code <@foo>Name: <#if
+     * condition>bar</#...@foo>}. Examples of cases that give {@code true}: {@code <@foo>Name: Joe</...@foo>}, {@code
+     * <@foo />}. Note that we get {@code true} for no nested content, because that's equivalent to 0-length nested
+     * content.
+     * <p>
+     * This method returns a pessimistic result. For example, if it sees a custom directive call, it can't know what it
+     * does, so it will assume that it's not cacheable.
+     */
+    boolean isNestedOutputCacheable();
+
+    // -------------------------------------------------------------------------------------------------------------
+    // Overloaded method selection:
+
+    /**
+     * The index of the first item in the argument array passed to {@code execute} that has this information.
+     * Used solely for speed optimization (to minimize the number of
+     * {@link #getTargetJavaParameterType(int)} calls).
+     *
+     * @return -1 if no parameter has type hint
+     */
+    int getFirstTargetJavaParameterTypeIndex();
+
+    /**
+     * The type of the parameter in the target Java method; used for overloaded Java method selection. This optional
+     * information is specified by the template author in the source code (the syntax is not yet decided when I write
+     * this).
+     *
+     * @param argIndex
+     *         The index of the argument in the argument array
+     *
+     * @return The desired Java type or {@code null} if this information wasn't specified in the template.
+     *
+     * @throws IndexOutOfBoundsException
+     *         Might be thrown if {@code argIndex} is an invalid index according the number of arguments on the call
+     *         site. Some implementations may just return {@code null} in that case though.
+     */
+    Class<?> getTargetJavaParameterType(int argIndex);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
index 0033afd..9c4d22e 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
@@ -19,13 +19,12 @@
 
 package org.apache.freemarker.core;
 
-import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.util.CommonSupplier;
 
 /**
  * Thrown by {@link CallPlace#getOrCreateCustomData(Object, CommonSupplier)}
  */
-public class CallPlaceCustomDataInitializationException extends Exception {
+public class CallPlaceCustomDataInitializationException extends RuntimeException {
 
     public CallPlaceCustomDataInitializationException(String message, Throwable cause) {
         super(message, cause);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
index af798b4..25ea39d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
@@ -43,10 +43,13 @@ import java.util.Set;
 import java.util.TimeZone;
 
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateCallableModel;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateDateModel;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateModel;
@@ -164,7 +167,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     private Collator cachedCollator;
 
     private Writer out;
-    private ASTDirMacro.Context currentMacroContext;
+    private ASTDirMacroOrFunction.Context currentMacroContext;
     private LocalContextStack localContextStack;
     private final Template mainTemplate;
     private final Namespace mainNamespace;
@@ -175,7 +178,6 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     private Throwable lastThrowable;
 
     private TemplateModel lastReturnValue;
-    private HashMap macroToNamespaceLookup = new HashMap();
 
     private TemplateNodeModel currentVisitorNode;
     private TemplateSequenceModel nodeNamespaces;
@@ -517,28 +519,25 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     /**
-     * Used for {@code #nested}.
+     * Used to execute the nested content of a macro call during a macro execution. It's not enough to simply call
+     * {@link CallPlace#executeNestedContent(TemplateModel[], Writer, Environment)} in such case, because an executing
+     * macro modifies the template language environment for its purposes, and the nested content expects that to be
+     * like before the macro invocation. So things has to be temporarily restored.
      */
-    void invokeNestedContent(ASTDirNested.Context bodyCtx) throws TemplateException, IOException {
-        ASTDirMacro.Context invokingMacroContext = getCurrentMacroContext();
-        LocalContextStack prevLocalContextStack = localContextStack;
-        ASTElement[] nestedContentBuffer = invokingMacroContext.nestedContentBuffer;
-        if (nestedContentBuffer != null) {
+    void executeNestedContentOfMacro(TemplateModel[] nestedContentParamValues)
+            throws TemplateException, IOException {
+        ASTDirMacroOrFunction.Context invokingMacroContext = getCurrentMacroContext();
+        CallPlace callPlace = invokingMacroContext.callPlace;
+        if (callPlace.hasNestedContent()) {
             currentMacroContext = invokingMacroContext.prevMacroContext;
             currentNamespace = invokingMacroContext.nestedContentNamespace;
-
+            LocalContextStack prevLocalContextStack = localContextStack;
             localContextStack = invokingMacroContext.prevLocalContextStack;
-            if (invokingMacroContext.nestedContentParameterNames != null) {
-                pushLocalContext(bodyCtx);
-            }
             try {
-                visit(nestedContentBuffer);
+                callPlace.executeNestedContent(nestedContentParamValues, out, this);
             } finally {
-                if (invokingMacroContext.nestedContentParameterNames != null) {
-                    popLocalContext();
-                }
                 currentMacroContext = invokingMacroContext;
-                currentNamespace = getMacroNamespace(invokingMacroContext.getMacro());
+                currentNamespace = invokingMacroContext.callable.namespace;
                 localContextStack = prevLocalContextStack;
             }
         }
@@ -580,18 +579,20 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             nodeNamespaces = namespaces;
         }
         try {
-            TemplateModel macroOrDirective = getNodeProcessor(node);
-            if (macroOrDirective instanceof ASTDirMacro) {
-                invoke((ASTDirMacro) macroOrDirective, null, null, null, null);
-            } else if (macroOrDirective instanceof TemplateDirectiveModel) {
-                ((TemplateDirectiveModel) macroOrDirective).execute(
-                        null, null /* TODO [FM3][CF] */, out, this);
-            } else {
+            TemplateDirectiveModel nodeProcessor = getNodeProcessor(node);
+            if (nodeProcessor != null) {
+                _TemplateCallableModelUtils.executeWith0Arguments(
+                        nodeProcessor, NonTemplateCallPlace.INSTANCE, out, this);
+            } else if (nodeProcessor == null) {
                 String nodeType = node.getNodeType();
                 if (nodeType != null) {
+                    // TODO [FM3] We are supposed to be o.a.f.dom unaware in the core, plus these types can mean
+                    // something else with another wrapper. So we should encode the default behavior into the
+                    // // TemplateNodeModel somehow.
+
                     // If the node's type is 'text', we just output it.
                     if ((nodeType.equals("text") && node instanceof TemplateScalarModel)) {
-                        out.write(((TemplateScalarModel) node).getAsString());
+                        out.write(((TemplateScalarModel) node).getAsString()); // TODO [FM3] Escaping?
                     } else if (nodeType.equals("document")) {
                         recurse(node, namespaces);
                     }
@@ -637,134 +638,23 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     void fallback() throws TemplateException, IOException {
-        TemplateModel macroOrDirective = getNodeProcessor(currentNodeName, currentNodeNS, nodeNamespaceIndex);
-        if (macroOrDirective instanceof ASTDirMacro) {
-            invoke((ASTDirMacro) macroOrDirective, null, null, null, null);
-        } else if (macroOrDirective instanceof TemplateDirectiveModel) {
-            ((TemplateDirectiveModel) macroOrDirective).execute(
-                    null, null /* TODO [FM3][CF] */, out, this);
-        }
-    }
-
-    /**
-     * Calls the macro or function with the given arguments and nested block.
-     */
-    void invoke(ASTDirMacro macro,
-            Map<String, ASTExpression> namedArgs, List<ASTExpression> positionalArgs,
-            List<String> bodyParameterNames, ASTElement[] childBuffer) throws TemplateException, IOException {
-        if (macro == ASTDirMacro.DO_NOTHING_MACRO) {
-            return;
-        }
-
-        pushElement(macro);
-        try {
-            final ASTDirMacro.Context macroCtx = macro.new Context(this, childBuffer, bodyParameterNames);
-            setMacroContextLocalsFromArguments(macroCtx, macro, namedArgs, positionalArgs);
-
-            final ASTDirMacro.Context prevMacroCtx = currentMacroContext;
-            currentMacroContext = macroCtx;
-
-            final LocalContextStack prevLocalContextStack = localContextStack;
-            localContextStack = null;
-
-            final Namespace prevNamespace = currentNamespace;
-            currentNamespace = (Namespace) macroToNamespaceLookup.get(macro);
-
-            try {
-                macroCtx.sanityCheck(this);
-                visit(macro.getChildBuffer());
-            } catch (ASTDirReturn.Return re) {
-                // Not an error, just a <#return>
-            } catch (TemplateException te) {
-                handleTemplateException(te);
-            } finally {
-                currentMacroContext = prevMacroCtx;
-                localContextStack = prevLocalContextStack;
-                currentNamespace = prevNamespace;
-            }
-        } finally {
-            popElement();
-        }
-    }
-
-    /**
-     * Sets the local variables corresponding to the macro call arguments in the macro context.
-     */
-    private void setMacroContextLocalsFromArguments(
-            final ASTDirMacro.Context macroCtx,
-            final ASTDirMacro macro,
-            final Map<String, ASTExpression> namedArgs, final List<ASTExpression> positionalArgs) throws TemplateException {
-        String catchAllParamName = macro.getCatchAll();
-        if (namedArgs != null) {
-            final NativeHashEx2 catchAllParamValue;
-            if (catchAllParamName != null) {
-                catchAllParamValue = new NativeHashEx2();
-                macroCtx.setLocalVar(catchAllParamName, catchAllParamValue);
-            } else {
-                catchAllParamValue = null;
-            }
-
-             for (Map.Entry<String, ASTExpression> argNameAndValExp : namedArgs.entrySet()) {
-                final String argName = argNameAndValExp.getKey();
-                final boolean isArgNameDeclared = macro.hasArgNamed(argName);
-                if (isArgNameDeclared || catchAllParamName != null) {
-                    ASTExpression argValueExp = argNameAndValExp.getValue();
-                    TemplateModel argValue = argValueExp.eval(this);
-                    if (isArgNameDeclared) {
-                        macroCtx.setLocalVar(argName, argValue);
-                    } else {
-                        catchAllParamValue.put(argName, argValue);
-                    }
-                } else {
-                    throw new _MiscTemplateException(this,
-                            (macro.isFunction() ? "Function " : "Macro "), new _DelayedJQuote(macro.getName()),
-                            " has no parameter with name ", new _DelayedJQuote(argName), ".");
-                }
-            }
-        } else if (positionalArgs != null) {
-            final NativeSequence catchAllParamValue;
-            if (catchAllParamName != null) {
-                catchAllParamValue = new NativeSequence(8);
-                macroCtx.setLocalVar(catchAllParamName, catchAllParamValue);
-            } else {
-                catchAllParamValue = null;
-            }
-
-            String[] argNames = macro.getArgumentNamesInternal();
-            final int argsCnt = positionalArgs.size();
-            if (argNames.length < argsCnt && catchAllParamName == null) {
-                throw new _MiscTemplateException(this,
-                        (macro.isFunction() ? "Function " : "Macro "), new _DelayedJQuote(macro.getName()),
-                        " only accepts ", new _DelayedToString(argNames.length), " parameters, but got ",
-                        new _DelayedToString(argsCnt), ".");
-            }
-            for (int i = 0; i < argsCnt; i++) {
-                ASTExpression argValueExp = positionalArgs.get(i);
-                TemplateModel argValue = argValueExp.eval(this);
-                try {
-                    if (i < argNames.length) {
-                        String argName = argNames[i];
-                        macroCtx.setLocalVar(argName, argValue);
-                    } else {
-                        catchAllParamValue.add(argValue);
-                    }
-                } catch (RuntimeException re) {
-                    throw new _MiscTemplateException(re, this);
-                }
-            }
+        TemplateDirectiveModel nodeProcessor = getNodeProcessor(currentNodeName, currentNodeNS, nodeNamespaceIndex);
+        if (nodeProcessor != null) {
+            _TemplateCallableModelUtils.executeWith0Arguments(
+                    nodeProcessor, NonTemplateCallPlace.INSTANCE, out, this);
         }
     }
 
     /**
      * Defines the given macro in the current namespace (doesn't call it).
      */
-    void visitMacroDef(ASTDirMacro macro) {
-        macroToNamespaceLookup.put(macro, currentNamespace);
-        currentNamespace.put(macro.getName(), macro);
-    }
-
-    Namespace getMacroNamespace(ASTDirMacro macro) {
-        return (Namespace) macroToNamespaceLookup.get(macro);
+    void visitMacroOrFunctionDefinition(ASTDirMacroOrFunction definition) {
+        Namespace currentNamespace = this.getCurrentNamespace();
+        currentNamespace.put(
+                definition.getName(),
+                definition.isFunction()
+                        ? new TemplateLanguageFunction(definition, currentNamespace)
+                        : new TemplateLanguageDirective(definition, currentNamespace));
     }
 
     void recurse(TemplateNodeModel node, TemplateSequenceModel namespaces)
@@ -786,7 +676,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         }
     }
 
-    ASTDirMacro.Context getCurrentMacroContext() {
+    ASTDirMacroOrFunction.Context getCurrentMacroContext() {
         return currentMacroContext;
     }
 
@@ -937,7 +827,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
      * Tells if the same concrete time zone is used for SQL date-only and time-only values as for other
      * date/time/date-time values.
      */
-    boolean isSQLDateAndTimeTimeZoneSameAsNormal() {
+    private boolean isSQLDateAndTimeTimeZoneSameAsNormal() {
         if (cachedSQLDateAndTimeTimeZoneSameAsNormal == null) {
             cachedSQLDateAndTimeTimeZoneSameAsNormal = Boolean.valueOf(
                     getSQLDateAndTimeTimeZone() == null
@@ -1959,7 +1849,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
      * (Note that the misnomer is kept for backward compatibility: nested content parameters are not local variables
      * according to our terminology.)
      */
-    // TODO [FM3] Don't return nested content params anymore (see JavaDoc)? But, LocalContext does return them...
+    // TODO [FM3] Don't return nested content params anymore (see JavaDoc)
     public TemplateModel getLocalVariable(String name) throws TemplateModelException {
         if (localContextStack != null) {
             for (int i = localContextStack.size() - 1; i >= 0; i--) {
@@ -2227,7 +2117,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         sb.append(MessageUtil.shorten(stackEl.getDescription(), 40));
 
         sb.append("  [");
-        ASTDirMacro enclosingMacro = getEnclosingMacro(stackEl);
+        ASTDirMacroOrFunction enclosingMacro = getEnclosingMacro(stackEl);
         if (enclosingMacro != null) {
             sb.append(MessageUtil.formatLocationForEvaluationError(
                     enclosingMacro, stackEl.beginLine, stackEl.beginColumn));
@@ -2238,9 +2128,9 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         sb.append("]");
     }
 
-    static private ASTDirMacro getEnclosingMacro(ASTElement stackEl) {
+    static private ASTDirMacroOrFunction getEnclosingMacro(ASTElement stackEl) {
         while (stackEl != null) {
-            if (stackEl instanceof ASTDirMacro) return (ASTDirMacro) stackEl;
+            if (stackEl instanceof ASTDirMacroOrFunction) return (ASTDirMacroOrFunction) stackEl;
             stackEl = stackEl.getParent();
         }
         return null;
@@ -2420,12 +2310,12 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         currentVisitorNode = node;
     }
 
-    TemplateModel getNodeProcessor(TemplateNodeModel node) throws TemplateException {
+    TemplateDirectiveModel getNodeProcessor(TemplateNodeModel node) throws TemplateException {
         String nodeName = node.getNodeName();
         if (nodeName == null) {
-            throw new _MiscTemplateException(this, "Node name is null.");
+            throw new _MiscTemplateException(this, "Node name was null.");
         }
-        TemplateModel result = getNodeProcessor(nodeName, node.getNodeNamespace(), 0);
+        TemplateDirectiveModel result = getNodeProcessor(nodeName, node.getNodeNamespace(), 0);
 
         if (result == null) {
             String type = node.getNodeType();
@@ -2445,9 +2335,9 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         return result;
     }
 
-    private TemplateModel getNodeProcessor(final String nodeName, final String nsURI, int startIndex)
+    private TemplateDirectiveModel getNodeProcessor(final String nodeName, final String nsURI, int startIndex)
             throws TemplateException {
-        TemplateModel result = null;
+        TemplateDirectiveModel result = null;
         int i;
         for (i = startIndex; i < nodeNamespaces.size(); i++) {
             Namespace ns = null;
@@ -2470,11 +2360,12 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         return result;
     }
 
-    private TemplateModel getNodeProcessor(Namespace ns, String localName, String nsURI) throws TemplateException {
+    private TemplateDirectiveModel getNodeProcessor(Namespace ns, String localName, String nsURI)
+            throws TemplateException {
         TemplateModel result = null;
         if (nsURI == null) {
             result = ns.get(localName);
-            if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
+            if (!(result instanceof TemplateDirectiveModel)) {
                 result = null;
             }
         } else {
@@ -2487,31 +2378,31 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             }
             if (prefix.length() > 0) {
                 result = ns.get(prefix + ":" + localName);
-                if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
+                if (!(result instanceof TemplateDirectiveModel)) {
                     result = null;
                 }
             } else {
-                if (nsURI.length() == 0) {
+                if (nsURI.isEmpty()) {
                     result = ns.get(Template.NO_NS_PREFIX + ":" + localName);
-                    if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
+                    if (!(result instanceof TemplateDirectiveModel)) {
                         result = null;
                     }
                 }
                 if (nsURI.equals(template.getDefaultNS())) {
                     result = ns.get(Template.DEFAULT_NAMESPACE_PREFIX + ":" + localName);
-                    if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
+                    if (!(result instanceof TemplateDirectiveModel)) {
                         result = null;
                     }
                 }
                 if (result == null) {
                     result = ns.get(localName);
-                    if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
+                    if (!(result instanceof TemplateDirectiveModel)) {
                         result = null;
                     }
                 }
             }
         }
-        return result;
+        return (TemplateDirectiveModel) result;
     }
 
     /**
@@ -2759,7 +2650,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     void importMacros(Template template) {
         for (Object macro : template.getMacros().values()) {
-            visitMacroDef((ASTDirMacro) macro);
+            visitMacroOrFunctionDefinition((ASTDirMacroOrFunction) macro);
         }
     }
 
@@ -3034,4 +2925,158 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         return res;
     }
 
+    /**
+     * Superclass of {@link TemplateCallableModel}-s implemented in the template language.
+     */
+    abstract class TemplateLanguageCallable implements TemplateCallableModel {
+        private final ASTDirMacroOrFunction callableDefinition;
+        private final Namespace namespace;
+
+        public TemplateLanguageCallable(ASTDirMacroOrFunction callableDefinition, Namespace namespace) {
+            this.callableDefinition = callableDefinition;
+            this.namespace = namespace;
+        }
+
+        protected void genericExecute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
+                throws TemplateException, IOException {
+            pushElement(callableDefinition);
+            try {
+                final ASTDirMacroOrFunction.Context macroCtx = callableDefinition.new Context(
+                        this, callPlace, Environment.this);
+
+                final ASTDirMacroOrFunction.Context prevMacroCtx = currentMacroContext;
+                currentMacroContext = macroCtx;
+
+                final LocalContextStack prevLocalContextStack = localContextStack;
+                localContextStack = null;
+
+                final Namespace prevNamespace = currentNamespace;
+                currentNamespace = namespace;
+
+                try {
+                    // Note: Default expressions are evaluated here, so namespace, stack, etc. must be already set
+                    setLocalsFromArguments(macroCtx, args);
+
+                    visit(callableDefinition.getChildBuffer(), out);
+                } catch (ASTDirReturn.Return re) {
+                    // Not an error, just a <#return>
+                } catch (TemplateException te) {
+                    handleTemplateException(te);
+                } finally {
+                    currentMacroContext = prevMacroCtx;
+                    localContextStack = prevLocalContextStack;
+                    currentNamespace = prevNamespace;
+                }
+            } finally {
+                popElement();
+            }
+        }
+
+        private void setLocalsFromArguments(ASTDirMacroOrFunction.Context macroCtx, TemplateModel[] args)
+                throws TemplateException {
+            ASTDirMacroOrFunction.ParameterDefinition[] paramDefsByArgIdx =
+                    callableDefinition.getParameterDefinitionByArgumentArrayIndex();
+            for (int argIdx = 0; argIdx < args.length; argIdx++) {
+                TemplateModel arg = args[argIdx];
+                ASTDirMacroOrFunction.ParameterDefinition paramDef = paramDefsByArgIdx[argIdx];
+                if (arg == null) { // TODO [FM3] FM2 doesn't differentiate omitted and null, but FM3 will.
+                    ASTExpression defaultExp = paramDef.getDefaultExpression();
+                    if (defaultExp != null) {
+                        arg = defaultExp.eval(Environment.this);
+                        if (arg == null) {
+                            throw InvalidReferenceException.getInstance(defaultExp, Environment.this);
+                        }
+                    } else {
+                        // TODO [FM3] Had to give different messages depending on if the argument was omitted, or if
+                        // it was null, but this will be fixed with the null related refactoring.
+                        throw new _MiscTemplateException(Environment.this,
+                                new _ErrorDescriptionBuilder(
+                                        "When calling macro ", new _DelayedJQuote(callableDefinition.getName()),
+                                        ", required parameter ", new _DelayedJQuote(paramDef.getName()),
+                                        (argIdx < getArgumentArrayLayout().getPredefinedPositionalArgumentCount()
+                                                ? new Object[] { " (parameter #", (argIdx + 1), ")" }
+                                                : ""),
+                                        " was either not specified, or had null/missing value.")
+                                        .tip("If the parameter value expression on the caller side is known to "
+                                                + "be legally null/missing, you may want to specify a default "
+                                                + "value for it on the caller side with the \"!\" operator, like "
+                                                + "paramValue!defaultValue.")
+                                        .tip("If the parameter was omitted on the caller side, and the omission was "
+                                                + "deliberate, you may consider making the parameter optional in the macro "
+                                                + "by specifying a default value for it, like <#macro macroName "
+                                                + "paramName=defaultExpr>."
+                                        )
+                        );
+                    }
+                }
+                macroCtx.setLocalVar(paramDef.getName(), arg);
+            }
+        }
+
+        @Override
+        public ArgumentArrayLayout getArgumentArrayLayout() {
+            return callableDefinition.getArgumentArrayLayout();
+        }
+
+        ASTDirMacroOrFunction getCallableDefinition() {
+            return callableDefinition;
+        }
+
+        Namespace getNamespace() {
+            return namespace;
+        }
+    }
+
+    /**
+     * {@link TemplateDirectiveModel} implemented in the template language (such as with the {@code #macro} directive).
+     * This is the value that {@code #macro} creates on runtime, not the {@code #macro} directive itself.
+     */
+    final class TemplateLanguageDirective extends TemplateLanguageCallable implements TemplateDirectiveModel {
+
+        public TemplateLanguageDirective(ASTDirMacroOrFunction macroDef, Namespace namespace) {
+            super(macroDef, namespace);
+        }
+
+        @Override
+        public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
+                throws IOException, TemplateException {
+            if (getCallableDefinition() == ASTDirMacroOrFunction.PASS_MACRO) {
+                return;
+            }
+            genericExecute(args, callPlace, out, env);
+        }
+
+        @Override
+        public boolean isNestedContentSupported() {
+            // TODO [FM3] We should detect if #nested is called anywhere (also maybe something like
+            // `<#macro m{supportsNested[=true|false]}>`) should be added.
+            return true;
+        }
+
+    }
+
+    /**
+     * {@link TemplateFunctionModel} implemented in the template language (such as with the {@code #function}
+     * directive). This is the value that {@code #function} creates on runtime, not the {@code #macro} directive itself.
+     */
+    final class TemplateLanguageFunction extends TemplateLanguageCallable implements TemplateFunctionModel {
+
+        public TemplateLanguageFunction(ASTDirMacroOrFunction callableDefinition, Namespace namespace) {
+            super(callableDefinition, namespace);
+        }
+
+        @Override
+        public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
+                throws TemplateException {
+            env.setLastReturnValue(null);
+            try {
+                genericExecute(args, callPlace, _NullWriter.INSTANCE, env);
+            } catch (IOException e) {
+                // Should not occur
+                throw new TemplateException("Unexpected exception during function execution", e, env);
+            }
+            return env.getLastReturnValue();
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java b/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
index 42c5455..59d867d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
@@ -26,7 +26,7 @@ import org.apache.freemarker.core.model.TemplateModelException;
 
 /**
   * An interface that represents a local context. This is used as the abstraction for  
-  * the context of a ASTDirMacro invocation, a loop, or the nested block call from within 
+  * the context of a ASTDirMacroOrFunction invocation, a loop, or the nested block call from within
   * a macro.
   */
 public interface LocalContext {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
index 8820f08..a1858b1 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
@@ -71,7 +71,7 @@ class MessageUtil {
         return formatLocation("at", template, line, column);
     }
 
-    static String formatLocationForEvaluationError(ASTDirMacro macro, int line, int column) {
+    static String formatLocationForEvaluationError(ASTDirMacroOrFunction macro, int line, int column) {
         Template t = macro.getTemplate();
         return formatLocation("at", t != null ? t.getSourceOrLookupName() : null, macro.getName(), macro.isFunction(),
                 line, column);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/NonDirectiveException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NonDirectiveException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NonDirectiveException.java
new file mode 100644
index 0000000..2053361
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NonDirectiveException.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;
+
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Indicates that a {@link TemplateDirectiveModel} was expected, but the value had a different type.
+ */
+class NonDirectiveException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] {
+        TemplateDirectiveModel.class, ASTDirMacroOrFunction.class };
+    
+    public NonDirectiveException(Environment env) {
+        super(env, "Expecting user-defined directive, transform or macro value here");
+    }
+
+    public NonDirectiveException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonDirectiveException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonDirectiveException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, env);
+    }
+
+    NonDirectiveException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, tip, env);
+    }
+
+    NonDirectiveException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/NonTemplateCallPlace.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NonTemplateCallPlace.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NonTemplateCallPlace.java
new file mode 100644
index 0000000..917ccbf
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NonTemplateCallPlace.java
@@ -0,0 +1,166 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.util.CommonSupplier;
+
+/**
+ * {@link CallPlace} used when a {@link TemplateDirectiveModel} is called from outside a template (such as from user
+ * Java code).
+ */
+public final class NonTemplateCallPlace implements CallPlace {
+
+    public static final NonTemplateCallPlace INSTANCE = new NonTemplateCallPlace();
+
+    private final int firstTargetJavaParameterTypeIndex;
+    private final Class<?>[] targetJavaParameterTypes;
+
+    private NonTemplateCallPlace() {
+        this(-1, null);
+    }
+
+    /**
+     * @param firstTargetJavaParameterTypeIndex
+     *         See {@link CallPlace#getFirstTargetJavaParameterTypeIndex()}
+     * @param targetJavaParameterTypes
+     *         The target Java types of the arguments of the invocation, starting with the target Java type of the
+     *         argument at index {@code firstTargetJavaParameterTypeIndex}, and usually ending with the target type of
+     *         the last argument that has a non-{@code null} target type (although having {@code null}-s at the end is
+     *         legal too). For example, using Java-like call syntax, if the call is like
+     *         {@code m(a, b, (Foo) c, d, (Bar) e, f)}, then {@code firstTargetJavaParameterTypeIndex} is 2,
+     *         and the array will be {@code new Class<?>[] { } [ Foo.class, null, Bar.class ]}.
+     */
+    public NonTemplateCallPlace(int firstTargetJavaParameterTypeIndex, Class<?>[] targetJavaParameterTypes) {
+        this.firstTargetJavaParameterTypeIndex = firstTargetJavaParameterTypeIndex;
+        this.targetJavaParameterTypes = targetJavaParameterTypes;
+    }
+
+    /**
+     * Always returns {@code false}.
+     */
+    @Override
+    public boolean hasNestedContent() {
+        return false;
+    }
+
+    /**
+     * Always returns {@code 0}.
+     */
+    @Override
+    public int getNestedContentParameterCount() {
+        return 0;
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void executeNestedContent(TemplateModel[] nestedContentArgs, Writer out, Environment env)
+            throws TemplateException, IOException {
+        // Do nothing
+    }
+
+    /**
+     * Always returns {@code -1}.
+     */
+    @Override
+    public Template getTemplate() {
+        return null;
+    }
+
+    /**
+     * Always returns {@code -1}.
+     */
+    @Override
+    public int getBeginColumn() {
+        return -1;
+    }
+
+    /**
+     * Always returns {@code -1}.
+     */
+    @Override
+    public int getBeginLine() {
+        return -1;
+    }
+
+    /**
+     * Always returns {@code -1}.
+     */
+    @Override
+    public int getEndColumn() {
+        return -1;
+    }
+
+    /**
+     * Always returns {@code -1}.
+     */
+    @Override
+    public int getEndLine() {
+        return -1;
+    }
+
+    /**
+     * Always throws {@link UnsupportedOperationException}.
+     */
+    @Override
+    public Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> supplier)
+            throws CallPlaceCustomDataInitializationException {
+        throw new UnsupportedOperationException("Non-template call place doesn't support custom data storage");
+    }
+
+    /**
+     * Always returns {@code false}.
+     */
+    @Override
+    public boolean isCustomDataSupported() {
+        return false;
+    }
+
+    /**
+     * Always returns {@code false}.
+     */
+    @Override
+    public boolean isNestedOutputCacheable() {
+        return false;
+    }
+
+    /**
+     * Always returns {@code -1}.
+     */
+    @Override
+    public int getFirstTargetJavaParameterTypeIndex() {
+        return firstTargetJavaParameterTypeIndex;
+    }
+
+    /**
+     * Always returns {@code null}.
+     */
+    @Override
+    public Class<?> getTargetJavaParameterType(int argIndex) {
+        int idx = argIndex - firstTargetJavaParameterTypeIndex;
+        return idx >= 0 || idx < targetJavaParameterTypes.length ? targetJavaParameterTypes[idx] : null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
deleted file mode 100644
index 547d688..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
+++ /dev/null
@@ -1,65 +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 org.apache.freemarker.core.model.TemplateDirectiveModel;
-import org.apache.freemarker.core.model.TemplateModel;
-
-// TODO [FM3][CF] Review and rename this when TDM2 and TFM are in place
-/**
- * Indicates that a {@link TemplateDirectiveModel} or {@link ASTDirMacro} value was
- * expected, but the value had a different type.
- */
-class NonUserDefinedDirectiveLikeException extends UnexpectedTypeException {
-
-    private static final Class[] EXPECTED_TYPES = new Class[] {
-        TemplateDirectiveModel.class, ASTDirMacro.class };
-    
-    public NonUserDefinedDirectiveLikeException(Environment env) {
-        super(env, "Expecting user-defined directive, transform or macro value here");
-    }
-
-    public NonUserDefinedDirectiveLikeException(String description, Environment env) {
-        super(env, description);
-    }
-
-    NonUserDefinedDirectiveLikeException(Environment env, _ErrorDescriptionBuilder description) {
-        super(env, description);
-    }
-
-    NonUserDefinedDirectiveLikeException(
-            ASTExpression blamed, TemplateModel model, Environment env)
-            throws InvalidReferenceException {
-        super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, env);
-    }
-
-    NonUserDefinedDirectiveLikeException(
-            ASTExpression blamed, TemplateModel model, String tip,
-            Environment env)
-            throws InvalidReferenceException {
-        super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, tip, env);
-    }
-
-    NonUserDefinedDirectiveLikeException(
-            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
-        super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, tips, env);
-    }    
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ParameterRole.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ParameterRole.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ParameterRole.java
index ccae7b7..b9ce370 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ParameterRole.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ParameterRole.java
@@ -54,15 +54,11 @@ final class ParameterRole {
     static final ParameterRole TEMPLATE_NAME = new ParameterRole("template name");
     static final ParameterRole IGNORE_MISSING_PARAMETER = new ParameterRole("\"ignore_missing\" parameter");
     static final ParameterRole PARAMETER_NAME = new ParameterRole("parameter name");
-    static final ParameterRole PARAMETER_DEFAULT = new ParameterRole("parameter default");
-    static final ParameterRole CATCH_ALL_PARAMETER_NAME = new ParameterRole("catch-all parameter name");
+    static final ParameterRole PARAMETER_DEFINITION = new ParameterRole("parameter definition");
     static final ParameterRole ARGUMENT_NAME = new ParameterRole("argument name");
     static final ParameterRole ARGUMENT_VALUE = new ParameterRole("argument value");
     static final ParameterRole CONTENT = new ParameterRole("content");
-    static final ParameterRole EMBEDDED_TEMPLATE = new ParameterRole("embedded template");
     static final ParameterRole VALUE_PART = new ParameterRole("value part");
-    static final ParameterRole MINIMUM_DECIMALS = new ParameterRole("minimum decimals");
-    static final ParameterRole MAXIMUM_DECIMALS = new ParameterRole("maximum decimals");
     static final ParameterRole NODE = new ParameterRole("node");
     static final ParameterRole CALLEE = new ParameterRole("callee");
     static final ParameterRole MESSAGE = new ParameterRole("message");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
index 5ea4517..fde11d9 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
@@ -735,7 +735,7 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
         out.write(rootElement != null ? rootElement.getCanonicalForm() : "Unfinished template");
     }
 
-    void addMacro(ASTDirMacro macro) {
+    void addMacro(ASTDirMacroOrFunction macro) {
         macros.put(macro.getName(), macro);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java
deleted file mode 100644
index 8313d53..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.apache.freemarker.core;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-
-public class TemplateCallableModelUtils {
-
-    // TODO [FM3][CF] Add this to the other exception classes too
-    public static TemplateNumberModel castArgumentToNumber(TemplateModel[] args, int argIndex, boolean allowNull,
-            Environment env) throws TemplateException {
-        return getTemplateNumberModel(args[argIndex], argIndex, allowNull, env);
-    }
-
-    // TODO [FM3][CF] Add this to the other exception classes too
-    private static TemplateNumberModel getTemplateNumberModel(TemplateModel argValue, int argIndex, boolean allowNull,
-            Environment env) throws TemplateException {
-        if (argValue instanceof TemplateNumberModel) {
-            return (TemplateNumberModel) argValue;
-        }
-        if (argValue == null) {
-            if (allowNull) {
-                return null;
-            }
-            throw new _MiscTemplateException(env,
-                    "The ", new _DelayedOrdinal(argIndex + 1), " argument can't be null.");
-        }
-        throw new NonNumericalException(argIndex, argValue, null, env);
-    }
-
-    // TODO [FM3][CF] Add this to the other exception classes too
-    public static TemplateNumberModel castArgumentToNumber(TemplateModel argValue, String argName, boolean allowNull,
-            Environment env) throws TemplateException {
-        if (argValue instanceof TemplateNumberModel) {
-            return (TemplateNumberModel) argValue;
-        }
-        if (argValue == null) {
-            if (allowNull) {
-                return null;
-            }
-            throw new _MiscTemplateException(env,
-                    "The ", new _DelayedJQuote(argName), " argument can't be null.");
-        }
-        throw new NonNumericalException(argName, argValue, null, env);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
index 71db276..a1b60bf 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
@@ -21,7 +21,6 @@ package org.apache.freemarker.core;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.model.TemplateDateModel;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java
index b575901..c904afa 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java
@@ -61,14 +61,12 @@ public final class _CoreAPI {
         }
     }
 
-    // [FM3] Should become unnecessary as custom directive classes are reworked
-    public static boolean isMacroOrFunction(TemplateModel m) {
-        return m instanceof ASTDirMacro;
+    public static boolean isMacro(Class<? extends TemplateModel> cl) {
+        return Environment.TemplateLanguageDirective.class.isAssignableFrom(cl);
     }
 
-    // [FM3] Should become unnecessary as custom directive classes are reworked
-    public static boolean isFunction(TemplateModel m) {
-        return m instanceof ASTDirMacro && ((ASTDirMacro) m).isFunction();
+    public static boolean isFunction(Class<? extends TemplateModel> cl) {
+        return Environment.TemplateLanguageFunction.class.isAssignableFrom(cl);
     }
 
     public static void checkVersionNotNullAndSupported(Version incompatibleImprovements) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
index c548487..13cadcd 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
@@ -211,6 +211,8 @@ public class _ErrorDescriptionBuilder {
         for (Object partObj : parts) {
             if (partObj instanceof Object[]) {
                 appendParts(sb, (Object[]) partObj);
+            } else if (partObj instanceof Class<?>) {
+                sb.append(((Class<?>) partObj).getName());
             } else {
                 String partStr;
                 partStr = tryToString(partObj);
@@ -219,12 +221,13 @@ public class _ErrorDescriptionBuilder {
                 }
 
                 if (template != null) {
+                    // Translate tag syntax of the part looks like an FTL tag
                     if (partStr.length() > 4
                             && partStr.charAt(0) == '<'
                             && (
                             (partStr.charAt(1) == '#' || partStr.charAt(1) == '@')
                                     || (partStr.charAt(1) == '/') && (partStr.charAt(2) == '#' || partStr.charAt(2) == '@')
-                    )
+                            )
                             && partStr.charAt(partStr.length() - 1) == '>') {
                         if (template.getActualTagSyntax() == TagSyntax.SQUARE_BRACKET) {
                             sb.append('[');

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateCallableModelUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateCallableModelUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateCallableModelUtils.java
new file mode 100644
index 0000000..277dc1a
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateCallableModelUtils.java
@@ -0,0 +1,112 @@
+/*
+ * 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 org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.Constants;
+import org.apache.freemarker.core.model.TemplateCallableModel;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateFunctionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+/**
+ * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
+ */
+// TODO [FM3] Most functionality here should be made public on some way
+public final class _TemplateCallableModelUtils {
+
+    private _TemplateCallableModelUtils() {
+        //
+    }
+
+    public static void executeWith0Arguments(
+            TemplateDirectiveModel directive, CallPlace callPlace, Writer out, Environment env)
+            throws IOException, TemplateException {
+        directive.execute(getArgumentArrayWithNoArguments(directive), callPlace, out, env);
+    }
+
+    public static TemplateModel executeWith0Arguments(
+            TemplateFunctionModel function, CallPlace callPlace, Environment env)
+            throws TemplateException {
+        return function.execute(getArgumentArrayWithNoArguments(function), callPlace, env);
+    }
+
+    private static TemplateModel[] getArgumentArrayWithNoArguments(TemplateCallableModel callable) {
+        ArgumentArrayLayout argsLayout = callable.getArgumentArrayLayout();
+        int totalLength = argsLayout.getTotalLength();
+        if (totalLength == 0) {
+            return Constants.EMPTY_TEMPLATE_MODEL_ARRAY;
+        } else {
+            TemplateModel[] args = new TemplateModel[totalLength];
+
+            int positionalVarargsArgumentIndex = argsLayout.getPositionalVarargsArgumentIndex();
+            if (positionalVarargsArgumentIndex != -1) {
+                args[positionalVarargsArgumentIndex] = Constants.EMPTY_SEQUENCE;
+            }
+
+            int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex();
+            if (namedVarargsArgumentIndex != -1) {
+                args[namedVarargsArgumentIndex] = Constants.EMPTY_SEQUENCE;
+            }
+            
+            return args;
+        }
+    }
+
+    public static TemplateNumberModel castArgumentToNumber(TemplateModel[] args, int argIndex, boolean allowNull,
+            Environment env) throws TemplateException {
+        return getTemplateNumberModel(args[argIndex], argIndex, allowNull, env);
+    }
+
+    private static TemplateNumberModel getTemplateNumberModel(TemplateModel argValue, int argIndex, boolean allowNull,
+            Environment env) throws TemplateException {
+        if (argValue instanceof TemplateNumberModel) {
+            return (TemplateNumberModel) argValue;
+        }
+        if (argValue == null) {
+            if (allowNull) {
+                return null;
+            }
+            throw new _MiscTemplateException(env,
+                    "The ", new _DelayedOrdinal(argIndex + 1), " argument can't be null.");
+        }
+        throw new NonNumericalException(argIndex, argValue, null, env);
+    }
+
+    public static TemplateNumberModel castArgumentToNumber(TemplateModel argValue, String argName, boolean allowNull,
+            Environment env) throws TemplateException {
+        if (argValue instanceof TemplateNumberModel) {
+            return (TemplateNumberModel) argValue;
+        }
+        if (argValue == null) {
+            if (allowNull) {
+                return null;
+            }
+            throw new _MiscTemplateException(env,
+                    "The ", new _DelayedJQuote(argName), " argument can't be null.");
+        }
+        throw new NonNumericalException(argName, argValue, null, env);
+    }
+
+}