freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [1/3] incubator-freemarker git commit: FREEMARKER-65: Utilities for argument validation: Work in progress. Changes in this commit:
Date Wed, 16 Aug 2017 23:24:48 GMT
Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 e941aedc1 -> f231e64fe


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
index 0f11a18..d1019e1 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
@@ -19,487 +19,27 @@
 
 package org.apache.freemarker.core;
 
-import static org.apache.freemarker.core.util.TemplateLanguageUtils.*;
+import static org.apache.freemarker.core.util.TemplateLanguageUtils.getCallableTypeName;
 
-import java.io.IOException;
-import java.io.Writer;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 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.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelWithOriginName;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core.util.StringToIndexMap;
-import org.apache.freemarker.core.util.TemplateLanguageUtils;
 import org.apache.freemarker.core.util._CollectionUtils;
 
 /**
  * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
+ * The published part of this API is in {@link CallableUtils}.
  */
-// TODO [FM3] Most functionality here should be made public on some way. Also BuiltIn-s has some duplicates utiltity
-// methods for this functionality (checking arguments). Need to clean this up.
-public final class _CallableUtils {
-
-    public static final TemplateModel[] EMPTY_TEMPLATE_MODEL_ARRAY = new TemplateModel[0];
-
-    private _CallableUtils() {
-        //
-    }
-
-    /** Convenience method for calling {@link #newGenericExecuteException(TemplateCallableModel, boolean, String)}. */
-    public static TemplateException newGenericExecuteException(
-            TemplateFunctionModel callable, String errorDescription) {
-        return newGenericExecuteException(callable, true, errorDescription);
-    }
-
-    /** Convenience method for calling {@link #newGenericExecuteException(TemplateCallableModel, boolean, String)}. */
-    public static TemplateException newGenericExecuteException(
-            TemplateDirectiveModel callable, String errorDescription) {
-        return newGenericExecuteException(callable, false, errorDescription);
-    }
-
-    /**
-     * @param errorDescription Complete sentence describing the problem. This will be after
-     *      {@code "When calling xxx: "}.
-     */
-    public static TemplateException newGenericExecuteException(
-            TemplateCallableModel callable, boolean calledAsFunction, String errorDescription) {
-        return new TemplateException(
-                getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
-                errorDescription);
-    }
-
-    public static TemplateException newArgumentValueException(
-            int argIdx, String problemDescription,
-            TemplateDirectiveModel callable) {
-        return newArgumentValueException(
-                argIdx, problemDescription, callable, false);
-    }
-
-    public static TemplateException newArgumentValueException(
-            int argIdx, String problemDescription,
-            TemplateFunctionModel callable) {
-        return newArgumentValueException(
-                argIdx, problemDescription, callable, true);
-    }
-
-    /**
-     * @param problemDescription The continuation of a sentence like {@code "When calling xxx: The 1st argument "}, for
-     *                           example {@code "must be a positive number."}.
-     */
-    public static TemplateException newArgumentValueException(
-            int argIdx, String problemDescription,
-            TemplateCallableModel callable, boolean calledAsFunction) {
-        return new TemplateException(
-                getMessageArgumentProblem(
-                        callable, argIdx,
-                        problemDescription,
-                        calledAsFunction));
-    }
-
-    /**
-     * Convenience method to call
-     * {@link #newArgumentValueTypeException(TemplateModel, int, Class, TemplateCallableModel, boolean)}.
-     */
-    public static TemplateException newArgumentValueTypeException(
-            TemplateModel argValue, int argIdx, Class<? extends TemplateModel> expectedType,
-            TemplateDirectiveModel callable) {
-        return newArgumentValueTypeException(
-                argValue, argIdx, expectedType,
-                callable, false);
-    }
-
-    /**
-     * Convenience method to call
-     * {@link #newArgumentValueTypeException(TemplateModel, int, Class, TemplateCallableModel, boolean)}.
-     */
-    public static TemplateException newArgumentValueTypeException(
-            TemplateModel argValue, int argIdx, Class<? extends TemplateModel> expectedType,
-            TemplateFunctionModel callable) {
-        return newArgumentValueTypeException(
-                argValue, argIdx, expectedType,
-                callable, true);
-    }
-
-    public static TemplateException newArgumentValueTypeException(
-            TemplateModel argValue, int argIdx, Class<? extends TemplateModel> expectedType,
-            TemplateCallableModel callable, boolean calledAsFunction) {
-        return new TemplateException(
-                getMessageBadArgumentType(argValue, argIdx,
-                        new Class[] { expectedType },
-                        TemplateLanguageUtils.getTypeName(expectedType),
-                        callable, calledAsFunction));
-    }
-
-    /**
-     * Convenience method for calling
-     * {@link #newArgumentValueTypeException(TemplateModel, int, Class[], String, TemplateCallableModel, boolean)}.
-     */
-    public static TemplateException newArgumentValueTypeException(
-            TemplateModel argValue, int argIdx, Class[] expectedTypes, String expectedTypeDescription,
-            TemplateDirectiveModel callable) {
-        return newArgumentValueTypeException(
-                argValue, argIdx, expectedTypes, expectedTypeDescription,
-                callable, false);
-    }
-
-    /**
-     * Convenience method for calling
-     * {@link #newArgumentValueTypeException(TemplateModel, int, Class[], String, TemplateCallableModel, boolean)}.
-     */
-    public static TemplateException newArgumentValueTypeException(
-            TemplateModel argValue, int argIdx, Class[] expectedTypes, String expectedTypeDescription,
-            TemplateFunctionModel callable) {
-        return newArgumentValueTypeException(
-                argValue, argIdx, expectedTypes, expectedTypeDescription,
-                callable, true);
-    }
-
-    /**
-     * @param expectedTypeDescription Something like "string or number".
-     */
-    public static TemplateException newArgumentValueTypeException(
-            TemplateModel argValue, int argIdx, Class[] expectedTypes, String expectedTypeDescription,
-            TemplateCallableModel callable, boolean calledAsFunction) {
-        return new TemplateException(
-                getMessageBadArgumentType(argValue, argIdx,
-                        expectedTypes,
-                        expectedTypeDescription,
-                        callable, calledAsFunction));
-    }
-
-    public static TemplateException newNullOrOmittedArgumentException(int argIdx, TemplateFunctionModel callable) {
-        return newNullOrOmittedArgumentException(argIdx, callable, true);
-    }
-
-    public static TemplateException newNullOrOmittedArgumentException(int argIdx, TemplateDirectiveModel callable) {
-        return newNullOrOmittedArgumentException(argIdx, callable, false);
-    }
-
-    public static TemplateException newNullOrOmittedArgumentException(int argIdx, TemplateCallableModel callable,
-            boolean calledAsFunction) {
-        return newArgumentValueException(argIdx, "can't be omitted or null.", callable, calledAsFunction);
-    }
-
-    /**
-     * Something like {@code "When calling function \"lib.ftl:foo\": " or "When calling ?leftPad: "}
-     */
-    private static Object getMessagePartWhenCallingSomethingColon(
-            TemplateCallableModel callable, boolean calledAsFunction) {
-        return callable instanceof ASTExpBuiltIn.BuiltInCallable
-                ? new Object[] { "When calling ?", ((ASTExpBuiltIn.BuiltInCallable) callable).getBuiltInName() + ": " }
-                : new Object[] {
-                        "When calling ",
-                        getCallableTypeName(callable, calledAsFunction),
-                        " ",
-                        callable instanceof TemplateModelWithOriginName
-                                ? new _DelayedJQuote(((TemplateModelWithOriginName) callable).getOriginName())
-                                : new _DelayedShortClassName(callable.getClass()),
-                        ": "
-                };
-    }
-
-    private static Object getMessagePartsTheSomethingArgument(ArgumentArrayLayout argsLayout, int argsArrayIndex) {
-        if (argsArrayIndex < 0) {
-            throw new IllegalArgumentException("argsArrayIndex can't be negative");
-        }
-        if (argsLayout == null || argsArrayIndex < argsLayout.getPredefinedPositionalArgumentCount()) {
-            return new Object[] { "The ", new _DelayedOrdinal(argsArrayIndex + 1), " argument " };
-        } else if (argsLayout.getPositionalVarargsArgumentIndex() == argsArrayIndex) {
-            return argsLayout.getNamedVarargsArgumentIndex() != -1 ? "The positional varargs argument "
-                    : "The varargs argument ";
-        } else if (argsLayout.getNamedVarargsArgumentIndex() == argsArrayIndex) {
-            return "The named varargs argument ";
-        } else {
-            String argName = argsLayout.getPredefinedNamedArgumentsMap().getKeyOfValue(argsArrayIndex);
-                return argName != null
-                        ? new Object[] { "The ", new _DelayedJQuote(argName), " argument " }
-                        : "The argument "; // Shouldn't occur...
-        }
-    }
-
-    static Object[] getMessageArgumentProblem(TemplateCallableModel callable, int argIndex, Object
-            problemDescription, boolean calledAsFunction) {
-        return new Object[] {
-                getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
-                getMessagePartsTheSomethingArgument(
-                        calledAsFunction ? ((TemplateFunctionModel) callable).getFunctionArgumentArrayLayout()
-                                : ((TemplateDirectiveModel) callable).getDirectiveArgumentArrayLayout(),
-                        argIndex),
-                problemDescription
-        };
-    }
-
-    private static _ErrorDescriptionBuilder getMessageBadArgumentType(
-            TemplateModel argValue, int argIdx, Class<? extends TemplateModel>[] expectedTypes,
-            String expectedTypesDesc, TemplateCallableModel callable,
-            boolean calledAsFunction) {
-        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
-                getMessageArgumentProblem(
-                        callable, argIdx,
-                        new Object[]{ " should be ", new _DelayedAOrAn(expectedTypesDesc), ", but was ",
-                                new _DelayedAOrAn(new _DelayedTemplateLanguageTypeDescription(argValue)),
-                                "." },
-                        calledAsFunction));
-        if (argValue instanceof _UnexpectedTypeErrorExplainerTemplateModel) {
-            Object[] tip = ((_UnexpectedTypeErrorExplainerTemplateModel) argValue).explainTypeError(expectedTypes);
-            if (tip != null) {
-                desc.tip(tip);
-            }
-        }
-        return desc;
-    }
-
-    public static void executeWith0Arguments(
-            TemplateDirectiveModel directive, CallPlace callPlace, Writer out, Environment env)
-            throws IOException, TemplateException {
-        directive.execute(
-                getArgumentArrayWithNoArguments(directive.getDirectiveArgumentArrayLayout()), callPlace, out, env);
-    }
-
-    public static TemplateModel executeWith0Arguments(
-            TemplateFunctionModel function, CallPlace callPlace, Environment env)
-            throws TemplateException {
-        return function.execute(
-                getArgumentArrayWithNoArguments(function.getFunctionArgumentArrayLayout()), callPlace, env);
-    }
-
-    private static TemplateModel[] getArgumentArrayWithNoArguments(ArgumentArrayLayout argsLayout) {
-        int totalLength = argsLayout != null ? argsLayout.getTotalLength() : 0;
-        if (totalLength == 0) {
-            return EMPTY_TEMPLATE_MODEL_ARRAY;
-        } else {
-            TemplateModel[] args = new TemplateModel[totalLength];
-
-            int positionalVarargsArgumentIndex = argsLayout.getPositionalVarargsArgumentIndex();
-            if (positionalVarargsArgumentIndex != -1) {
-                args[positionalVarargsArgumentIndex] = TemplateSequenceModel.EMPTY_SEQUENCE;
-            }
-
-            int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex();
-            if (namedVarargsArgumentIndex != -1) {
-                args[namedVarargsArgumentIndex] = TemplateSequenceModel.EMPTY_SEQUENCE;
-            }
-            
-            return args;
-        }
-    }
-
-    // String arg:
-
-    /**
-     * Convenience method to call
-     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
-     * castArgumentValueToString(args[argIndex], argIndex, callable, true, false)}.
-     */
-    public static String getStringArgument(
-            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
-            throws TemplateException {
-        return castArgumentValueToString(args[argIndex], argIndex, callable, true, false);
-    }
-
-    /**
-     * Convenience method to call
-     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
-     * castArgumentValueToString(args[argIndex], argIndex, callable, false, false)}.
-     */
-    public static String getStringArgument(
-            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
-            throws TemplateException {
-        return castArgumentValueToString(args[argIndex], argIndex, callable, false, false);
-    }
-
-    /**
-     * Convenience method to call
-     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
-     * castArgumentValueToString(args[argIndex], argIndex, callable, true, true)}.
-     */
-    public static String getOptionalStringArgument(
-            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
-            throws TemplateException {
-        return castArgumentValueToString(args[argIndex], argIndex, callable, true, true);
-    }
-
-    /**
-     * Convenience method to call
-     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
-     * castArgumentValueToString(args[argIndex], argIndex, callable, false, true)}.
-     */
-    public static String getOptionalStringArgument(
-            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
-            throws TemplateException {
-        return castArgumentValueToString(args[argIndex], argIndex, callable, false, true);
-    }
-
-    /**
-     * Checks if the argument value is a string; it does NOT check if {@code args} is big enough.
-     *
-     * @param calledAsFunction
-     *         If {@code callable} was called as function (as opposed to called as a directive)
-     * @param optional
-     *         If we allow a {@code null} return value
-     *
-     * @return Null {@code null} if the argument was omitted or {@code null}
-     *
-     * @throws TemplateException
-     *         If the argument is not of the proper type or is non-optional yet {@code null}. The error message
-     *         describes the problem in detail.
-     */
-    public static String castArgumentValueToString(
-            TemplateModel argValue, int argIdx, TemplateCallableModel callable,
-            boolean calledAsFunction, boolean optional)
-            throws TemplateException {
-        if (argValue instanceof TemplateScalarModel) {
-            return _EvalUtils.modelToString((TemplateScalarModel) argValue, null);
-        }
-        if (argValue == null) {
-            if (optional) {
-                return null;
-            }
-            throw newNullOrOmittedArgumentException(argIdx, callable, calledAsFunction);
-        }
-        throw newArgumentValueTypeException(argValue, argIdx, TemplateScalarModel.class, callable, calledAsFunction);
-    }
-
-    // Number arg:
-    
-    public static Number getNumberArgument(
-            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
-            throws TemplateException {
-        return castArgumentValueToNumber(args[argIndex], argIndex, callable, true, false);
-    }
-
-    public static Number getNumberArgument(
-            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
-            throws TemplateException {
-        return castArgumentValueToNumber(args[argIndex], argIndex, callable, false, false);
-    }
-
-    public static Number getOptionalNumberArgument(
-            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
-            throws TemplateException {
-        return castArgumentValueToNumber(args[argIndex], argIndex, callable, true, true);
-    }
-
-    public static Number getOptionalNumberArgument(
-            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
-            throws TemplateException {
-        return castArgumentValueToNumber(args[argIndex], argIndex, callable, false, true);
-    }
-
-    public static Number castArgumentValueToNumber(
-            TemplateModel argValue, int argIdx, TemplateCallableModel callable,
-            boolean calledAsFunction, boolean optional)
-            throws TemplateException {
-        if (argValue instanceof TemplateNumberModel) {
-            return _EvalUtils.modelToNumber((TemplateNumberModel) argValue, null);
-        }
-        if (argValue == null) {
-            if (optional) {
-                return null;
-            }
-            throw newNullOrOmittedArgumentException(argIdx, callable, calledAsFunction);
-        }
-        throw newArgumentValueTypeException(
-                argValue, argIdx, TemplateNumberModel.class, callable,
-                calledAsFunction);
-    }
-
-    // TODO boolean, etc.
-
-    // Argument count
-
-    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
-    public static void checkArgumentCount(int argCnt, int expectedCnt, TemplateFunctionModel callable)
-            throws TemplateException {
-        checkArgumentCount(argCnt, expectedCnt, callable, true);
-    }
-
-    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
-    public static void checkArgumentCount(int argCnt, int expectedCnt, TemplateDirectiveModel callable)
-            throws TemplateException {
-        checkArgumentCount(argCnt, expectedCnt, callable, false);
-    }
-
-    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
-    public static void checkArgumentCount(int argCnt, int expectedCnt,
-            TemplateCallableModel callable, boolean calledAsFunction) throws TemplateException {
-        checkArgumentCount(argCnt, expectedCnt, expectedCnt, callable, calledAsFunction);
-    }
-
-    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
-    public static void checkArgumentCount(int argCnt, int minCnt, int maxCnt, TemplateFunctionModel callable)
-            throws TemplateException {
-        checkArgumentCount(argCnt, minCnt, maxCnt, callable, true);
-    }
-
-    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
-    public static void checkArgumentCount(int argCnt, int minCnt, int maxCnt, TemplateDirectiveModel callable)
-            throws TemplateException {
-        checkArgumentCount(argCnt, minCnt, maxCnt, callable, false);
-    }
-
-    /**
-     * Useful when the {@link ArgumentArrayLayout} is {@code null} and so the argument array length is not fixed,
-     * to check if the number of arguments is in the given range.
-     */
-    public static void checkArgumentCount(int argCnt, int minCnt, int maxCnt,
-        TemplateCallableModel callable, boolean calledAsFunction) throws TemplateException {
-        if (argCnt < minCnt || argCnt > maxCnt) {
-            throw new TemplateException(
-                    getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
-                    getMessagePartExpectedNArgumentButHadM(argCnt, minCnt, maxCnt));
-        }
-    }
-
-    private static Object[] getMessagePartExpectedNArgumentButHadM(int argCnt, int minCnt, int maxCnt) {
-        ArrayList<Object> desc = new ArrayList<>(20);
-
-        desc.add("Expected ");
-
-        if (minCnt == maxCnt) {
-            if (maxCnt == 0) {
-                desc.add("no");
-            } else {
-                desc.add(maxCnt);
-            }
-        } else if (maxCnt - minCnt == 1) {
-            desc.add(minCnt);
-            desc.add(" or ");
-            desc.add(maxCnt);
-        } else {
-            desc.add(minCnt);
-            if (maxCnt != Integer.MAX_VALUE) {
-                desc.add(" to ");
-                desc.add(maxCnt);
-            } else {
-                desc.add(" or more (unlimited)");
-            }
-        }
-        desc.add(" argument");
-        if (maxCnt > 1) desc.add("s");
-
-        desc.add(" but has received ");
-        if (argCnt == 0) {
-            desc.add("none");
-        } else {
-            desc.add(argCnt);
-        }
-        desc.add(".");
-
-        return desc.toArray();
-    }
-
-    //
-
+public class _CallableUtils {
     static TemplateModel[] getExecuteArgs(
             ASTExpression[] positionalArgs, NamedArgument[] namedArgs, ArgumentArrayLayout argsLayout,
             TemplateCallableModel callable, boolean calledAsFunction,
@@ -528,7 +68,7 @@ public final class _CallableUtils {
                 execArgs[i] = positionalArg.eval(env);
             }
         } else {
-            execArgs = EMPTY_TEMPLATE_MODEL_ARRAY;
+            execArgs = CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY;
         }
         return execArgs;
     }
@@ -638,7 +178,7 @@ public final class _CallableUtils {
     }
 
     private static Object[] getNamedArgumentsNotSupportedMessage(TemplateCallableModel callable,
-            NamedArgument namedArg, boolean calledAsFunction) {
+            _CallableUtils.NamedArgument namedArg, boolean calledAsFunction) {
         return new Object[] {
                 getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
                 "This ", getCallableTypeName(callable, calledAsFunction),
@@ -663,6 +203,24 @@ public final class _CallableUtils {
         }
     }
 
+    /**
+     * Something like {@code "When calling function \"lib.ftl:foo\": " or "When calling ?leftPad: "}
+     */
+    public static Object getMessagePartWhenCallingSomethingColon(
+            TemplateCallableModel callable, boolean calledAsFunction) {
+        return callable instanceof ASTExpBuiltIn.BuiltInCallable
+                ? new Object[] { "When calling ?", ((ASTExpBuiltIn.BuiltInCallable) callable).getBuiltInName() + ": " }
+                : new Object[] {
+                        "When calling ",
+                        getCallableTypeName(callable, calledAsFunction),
+                        " ",
+                        callable instanceof TemplateModelWithOriginName
+                                ? new _DelayedJQuote(((TemplateModelWithOriginName) callable).getOriginName())
+                                : new _DelayedShortClassName(callable.getClass()),
+                        ": "
+                };
+    }
+
     static final class NamedArgument {
         private final String name;
         private final ASTExpression value;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/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 c904afa..df7042b 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
@@ -69,6 +69,10 @@ public final class _CoreAPI {
         return Environment.TemplateLanguageFunction.class.isAssignableFrom(cl);
     }
 
+    public static boolean isTemplateLanguageCallable(Class<? extends TemplateModel> cl) {
+        return Environment.TemplateLanguageCallable.class.isAssignableFrom(cl);
+    }
+
     public static void checkVersionNotNullAndSupported(Version incompatibleImprovements) {
         _NullArgumentException.check("incompatibleImprovements", incompatibleImprovements);
         int iciV = incompatibleImprovements.intValue();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
index e9a7967..17f6459 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
@@ -19,6 +19,8 @@
 
 package org.apache.freemarker.core;
 
+import static org.apache.freemarker.core.MessageUtils.*;
+
 import java.util.Date;
 
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
@@ -57,7 +59,7 @@ public class _EvalUtils {
     /**
      * @param expr {@code null} is allowed, but may results in less helpful error messages
      */
-    static String modelToString(TemplateScalarModel model, ASTExpression expr)
+    public static String modelToString(TemplateScalarModel model, ASTExpression expr)
     throws TemplateModelException {
         String value = model.getAsString();
         if (value == null) {
@@ -69,7 +71,7 @@ public class _EvalUtils {
     /**
      * @param expr {@code null} is allowed, but may results in less helpful error messages
      */
-    static Number modelToNumber(TemplateNumberModel model, ASTExpression expr)
+    public static Number modelToNumber(TemplateNumberModel model, ASTExpression expr)
         throws TemplateModelException {
         Number value = model.getAsNumber();
         if (value == null) throw newModelHasStoredNullException(Number.class, model, expr);
@@ -79,7 +81,7 @@ public class _EvalUtils {
     /**
      * @param expr {@code null} is allowed, but may results in less helpful error messages
      */
-    static Date modelToDate(TemplateDateModel model, ASTExpression expr)
+    public static Date modelToDate(TemplateDateModel model, ASTExpression expr)
         throws TemplateModelException {
         Date value = model.getAsDate();
         if (value == null) throw newModelHasStoredNullException(Date.class, model, expr);
@@ -447,7 +449,7 @@ public class _EvalUtils {
                 throw InvalidReferenceException.getInstance(exp, env);
             } else {
                 throw new InvalidReferenceException(
-                        "Null/missing value (no more informatoin avilable)",
+                        "Null/missing value (no more information available)",
                         env);
             }
         } else if (tm instanceof TemplateBooleanModel) {
@@ -459,37 +461,31 @@ public class _EvalUtils {
             if (returnNullOnNonCoercableType) {
                 return null;
             }
-            if (seqHint != null && (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)) {
-                if (supportsTOM) {
-                    throw new NonStringOrTemplateOutputException(exp, tm, seqHint, env);
-                } else {
-                    throw new NonStringException(exp, tm, seqHint, env);
-                }
-            } else {
-                if (supportsTOM) {
-                    throw new NonStringOrTemplateOutputException(exp, tm, env);
-                } else {
-                    throw new NonStringException(exp, tm, env);
-                }
-            }
+
+            throw newUnexpectedOperandTypeException(
+                    exp, tm,
+                    supportsTOM ? STRING_COERCABLE_TYPES_OR_TOM_DESC : STRING_COERCABLE_TYPES_DESC,
+                    supportsTOM ? EXPECTED_TYPES_STRING_COERCABLE_TYPES_AND_TOM : EXPECTED_TYPES_STRING_COERCABLE,
+                    seqHint != null && (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)
+                            ? new Object[] { seqHint }
+                            : null,
+                    env);
         }
     }
 
     private static String ensureFormatResultString(Object formatResult, ASTExpression exp, Environment env)
-            throws NonStringException {
+            throws TemplateException {
         if (formatResult instanceof String) { 
             return (String) formatResult;
         }
         
         assertFormatResultNotNull(formatResult);
         
-        TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) formatResult;
-        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+        throw new TemplateException(env, new _ErrorDescriptionBuilder(
                 "Value was formatted to convert it to string, but the result was markup of ouput format ",
-                new _DelayedJQuote(mo.getOutputFormat()), ".")
+                new _DelayedJQuote(((TemplateMarkupOutputModel) formatResult).getOutputFormat()), ".")
                 .tip("Use value?string to force formatting to plain text.")
-                .blame(exp);
-        throw new NonStringException(null, desc);
+                .blame(exp));
     }
 
     static String assertFormatResultNotNull(String r) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
index 285021c..ecd4ebb 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
@@ -34,7 +34,11 @@ implements TemplateBooleanModel, TemplateScalarModel, TemplateSequenceModel, Tem
         TemplateFunctionModel {
 
     public static final TemplateModel INSTANCE = new GeneralPurposeNothing();
-      
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(
+            0, true,
+            null, true);
+
     private GeneralPurposeNothing() {
     }
 
@@ -75,9 +79,7 @@ implements TemplateBooleanModel, TemplateScalarModel, TemplateSequenceModel, Tem
 
     @Override
     public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
-        return ArgumentArrayLayout.create(
-                0, true,
-                null, true);
+        return ARGS_LAYOUT;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java
index 2f1a506..7f57c68 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java
@@ -18,7 +18,7 @@
  */
 package org.apache.freemarker.core.model.impl;
 
-import org.apache.freemarker.core._CallableUtils;
+import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
@@ -47,7 +47,7 @@ class OverloadedFixArgsMethods extends OverloadedMethodsSubset {
     throws TemplateModelException {
         if (tmArgs == null) {
             // null is treated as empty args
-            tmArgs = _CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY;
+            tmArgs = CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY;
         }
         final int argCount = tmArgs.length;
         final Class<?>[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
index 8b325ed..fd5c5ee 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
@@ -20,7 +20,7 @@ package org.apache.freemarker.core.model.impl;
 
 import java.lang.reflect.Array;
 
-import org.apache.freemarker.core._CallableUtils;
+import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
@@ -139,7 +139,7 @@ class OverloadedVarArgsMethods extends OverloadedMethodsSubset {
     throws TemplateModelException {
         if (tmArgs == null) {
             // null is treated as empty args
-            tmArgs = _CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY;
+            tmArgs = CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY;
         }
         final int argsLen = tmArgs.length;
         final Class<?>[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
index f794083..b5fb41c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
@@ -30,7 +30,7 @@ import java.util.Set;
 import org.apache.freemarker.core.CallPlace;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core._CallableUtils;
+import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core._DelayedJQuote;
 import org.apache.freemarker.core._TemplateModelException;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
@@ -112,7 +112,7 @@ public class ResourceBundleModel extends BeanModel implements TemplateFunctionMo
         if (args.length < 1)
             throw new TemplateException("No message key was specified", env);
         // Read it
-        String key = _CallableUtils.getStringArgument(args, 0, this);
+        String key = CallableUtils.getStringArgument(args, 0, this);
         try {
             if (args.length == 1) {
                 return wrap(((ResourceBundle) object).getObject(key));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java
index c1786af..78c624c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java
@@ -21,7 +21,7 @@ package org.apache.freemarker.core.model.impl;
 import java.lang.reflect.Array;
 import java.lang.reflect.Member;
 
-import org.apache.freemarker.core._CallableUtils;
+import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core._DelayedTemplateLanguageTypeDescription;
 import org.apache.freemarker.core._DelayedOrdinal;
 import org.apache.freemarker.core._ErrorDescriptionBuilder;
@@ -52,7 +52,7 @@ class SimpleMethod {
     
     Object[] unwrapArguments(TemplateModel[] args, DefaultObjectWrapper wrapper) throws TemplateModelException {
         if (args == null) {
-            args = _CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY;
+            args = CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY;
         }
         boolean isVarArg = _MethodUtils.isVarargs(member);
         int typesLen = argTypes.length;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java
new file mode 100644
index 0000000..a208612
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java
@@ -0,0 +1,496 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+
+import org.apache.freemarker.core.CallPlace;
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core._CallableUtils;
+import org.apache.freemarker.core._DelayedAOrAn;
+import org.apache.freemarker.core._DelayedJQuote;
+import org.apache.freemarker.core._DelayedOrdinal;
+import org.apache.freemarker.core._DelayedTemplateLanguageTypeDescription;
+import org.apache.freemarker.core._ErrorDescriptionBuilder;
+import org.apache.freemarker.core._EvalUtils;
+import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.TemplateCallableModel;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateFunctionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+
+/**
+ * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
+ */
+public final class CallableUtils {
+
+    public static final TemplateModel[] EMPTY_TEMPLATE_MODEL_ARRAY = new TemplateModel[0];
+
+    private CallableUtils() {
+        //
+    }
+
+    /** Convenience method for calling {@link #newGenericExecuteException(TemplateCallableModel, boolean, String)}. */
+    public static TemplateException newGenericExecuteException(
+            TemplateFunctionModel callable, String errorDescription) {
+        return newGenericExecuteException(callable, true, errorDescription);
+    }
+
+    /** Convenience method for calling {@link #newGenericExecuteException(TemplateCallableModel, boolean, String)}. */
+    public static TemplateException newGenericExecuteException(
+            TemplateDirectiveModel callable, String errorDescription) {
+        return newGenericExecuteException(callable, false, errorDescription);
+    }
+
+    /**
+     * @param errorDescription Complete sentence describing the problem. This will be after
+     *      {@code "When calling xxx: "}.
+     */
+    public static TemplateException newGenericExecuteException(
+            TemplateCallableModel callable, boolean calledAsFunction, String errorDescription) {
+        return new TemplateException(
+                _CallableUtils.getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                errorDescription);
+    }
+
+    public static TemplateException newArgumentValueException(
+            int argIdx, String problemDescription,
+            TemplateDirectiveModel callable) {
+        return newArgumentValueException(
+                argIdx, problemDescription, callable, false);
+    }
+
+    public static TemplateException newArgumentValueException(
+            int argIdx, String problemDescription,
+            TemplateFunctionModel callable) {
+        return newArgumentValueException(
+                argIdx, problemDescription, callable, true);
+    }
+
+    /**
+     * @param problemDescription The continuation of a sentence like {@code "When calling xxx: The 1st argument "}, for
+     *                           example {@code "must be a positive number."}.
+     */
+    public static TemplateException newArgumentValueException(
+            int argIdx, String problemDescription,
+            TemplateCallableModel callable, boolean calledAsFunction) {
+        return _newArgumentValueException(argIdx, problemDescription, callable, calledAsFunction, null);
+    }
+
+    // TODO [FM3] How to expose tips API?
+    public static TemplateException _newArgumentValueException(
+            int argIdx, String problemDescription,
+            TemplateCallableModel callable, boolean calledAsFunction,
+            Object[] tips) {
+        return new TemplateException(
+                new _ErrorDescriptionBuilder(
+                        getMessageArgumentProblem(callable, argIdx, problemDescription, calledAsFunction)
+                        ).tips(tips));
+    }
+
+    /**
+     * Convenience method to call
+     * {@link #newArgumentValueTypeException(TemplateModel, int, Class, TemplateCallableModel, boolean)}.
+     */
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class<? extends TemplateModel> expectedType,
+            TemplateDirectiveModel callable) {
+        return newArgumentValueTypeException(
+                argValue, argIdx, expectedType,
+                callable, false);
+    }
+
+    /**
+     * Convenience method to call
+     * {@link #newArgumentValueTypeException(TemplateModel, int, Class, TemplateCallableModel, boolean)}.
+     */
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class<? extends TemplateModel> expectedType,
+            TemplateFunctionModel callable) {
+        return newArgumentValueTypeException(
+                argValue, argIdx, expectedType,
+                callable, true);
+    }
+
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class<? extends TemplateModel> expectedType,
+            TemplateCallableModel callable, boolean calledAsFunction) {
+        return new TemplateException(
+                getMessageBadArgumentType(argValue, argIdx,
+                        new Class[] { expectedType },
+                        TemplateLanguageUtils.getTypeName(expectedType),
+                        callable, calledAsFunction));
+    }
+
+    /**
+     * Convenience method for calling
+     * {@link #newArgumentValueTypeException(TemplateModel, int, Class[], String, TemplateCallableModel, boolean)}.
+     */
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class[] expectedTypes, String expectedTypeDescription,
+            TemplateDirectiveModel callable) {
+        return newArgumentValueTypeException(
+                argValue, argIdx, expectedTypes, expectedTypeDescription,
+                callable, false);
+    }
+
+    /**
+     * Convenience method for calling
+     * {@link #newArgumentValueTypeException(TemplateModel, int, Class[], String, TemplateCallableModel, boolean)}.
+     */
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class[] expectedTypes, String expectedTypeDescription,
+            TemplateFunctionModel callable) {
+        return newArgumentValueTypeException(
+                argValue, argIdx, expectedTypes, expectedTypeDescription,
+                callable, true);
+    }
+
+    /**
+     * @param expectedTypeDescription Something like "string or number".
+     */
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class[] expectedTypes, String expectedTypeDescription,
+            TemplateCallableModel callable, boolean calledAsFunction) {
+        return new TemplateException(
+                getMessageBadArgumentType(argValue, argIdx,
+                        expectedTypes,
+                        expectedTypeDescription,
+                        callable, calledAsFunction));
+    }
+
+    public static TemplateException newNullOrOmittedArgumentException(int argIdx, TemplateFunctionModel callable) {
+        return newNullOrOmittedArgumentException(argIdx, callable, true);
+    }
+
+    public static TemplateException newNullOrOmittedArgumentException(int argIdx, TemplateDirectiveModel callable) {
+        return newNullOrOmittedArgumentException(argIdx, callable, false);
+    }
+
+    public static TemplateException newNullOrOmittedArgumentException(int argIdx, TemplateCallableModel callable,
+            boolean calledAsFunction) {
+        return _newNullOrOmittedArgumentException(argIdx, callable, calledAsFunction, null);
+    }
+
+    // TODO [FM3] How to expose tips API?
+    public static TemplateException _newNullOrOmittedArgumentException(int argIdx, TemplateCallableModel callable,
+            boolean calledAsFunction, Object[] tips) {
+        return _newArgumentValueException(argIdx, "can't be omitted or null.", callable, calledAsFunction, tips);
+    }
+
+    private static Object getMessagePartsTheSomethingArgument(ArgumentArrayLayout argsLayout, int argsArrayIndex) {
+        if (argsArrayIndex < 0) {
+            throw new IllegalArgumentException("argsArrayIndex can't be negative");
+        }
+        if (argsLayout == null || argsArrayIndex < argsLayout.getPredefinedPositionalArgumentCount()) {
+            return new Object[] { "The ", new _DelayedOrdinal(argsArrayIndex + 1), " argument " };
+        } else if (argsLayout.getPositionalVarargsArgumentIndex() == argsArrayIndex) {
+            return argsLayout.getNamedVarargsArgumentIndex() != -1 ? "The positional varargs argument "
+                    : "The varargs argument ";
+        } else if (argsLayout.getNamedVarargsArgumentIndex() == argsArrayIndex) {
+            return "The named varargs argument ";
+        } else {
+            String argName = argsLayout.getPredefinedNamedArgumentsMap().getKeyOfValue(argsArrayIndex);
+                return argName != null
+                        ? new Object[] { "The ", new _DelayedJQuote(argName), " argument " }
+                        : "The argument "; // Shouldn't occur...
+        }
+    }
+
+    static Object[] getMessageArgumentProblem(TemplateCallableModel callable, int argIndex, Object
+            problemDescription, boolean calledAsFunction) {
+        return new Object[] {
+                _CallableUtils.getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                getMessagePartsTheSomethingArgument(
+                        calledAsFunction ? ((TemplateFunctionModel) callable).getFunctionArgumentArrayLayout()
+                                : ((TemplateDirectiveModel) callable).getDirectiveArgumentArrayLayout(),
+                        argIndex),
+                problemDescription
+        };
+    }
+
+    private static _ErrorDescriptionBuilder getMessageBadArgumentType(
+            TemplateModel argValue, int argIdx, Class<? extends TemplateModel>[] expectedTypes,
+            String expectedTypesDesc, TemplateCallableModel callable,
+            boolean calledAsFunction) {
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                getMessageArgumentProblem(
+                        callable, argIdx,
+                        new Object[]{ " should be ", new _DelayedAOrAn(expectedTypesDesc), ", but was ",
+                                new _DelayedAOrAn(new _DelayedTemplateLanguageTypeDescription(argValue)),
+                                "." },
+                        calledAsFunction));
+        if (argValue instanceof _UnexpectedTypeErrorExplainerTemplateModel) {
+            Object[] tip = ((_UnexpectedTypeErrorExplainerTemplateModel) argValue).explainTypeError(expectedTypes);
+            if (tip != null) {
+                desc.tip(tip);
+            }
+        }
+        return desc;
+    }
+
+    public static void executeWith0Arguments(
+            TemplateDirectiveModel directive, CallPlace callPlace, Writer out, Environment env)
+            throws IOException, TemplateException {
+        directive.execute(
+                getArgumentArrayWithNoArguments(directive.getDirectiveArgumentArrayLayout()), callPlace, out, env);
+    }
+
+    public static TemplateModel executeWith0Arguments(
+            TemplateFunctionModel function, CallPlace callPlace, Environment env)
+            throws TemplateException {
+        return function.execute(
+                getArgumentArrayWithNoArguments(function.getFunctionArgumentArrayLayout()), callPlace, env);
+    }
+
+    private static TemplateModel[] getArgumentArrayWithNoArguments(ArgumentArrayLayout argsLayout) {
+        int totalLength = argsLayout != null ? argsLayout.getTotalLength() : 0;
+        if (totalLength == 0) {
+            return EMPTY_TEMPLATE_MODEL_ARRAY;
+        } else {
+            TemplateModel[] args = new TemplateModel[totalLength];
+
+            int positionalVarargsArgumentIndex = argsLayout.getPositionalVarargsArgumentIndex();
+            if (positionalVarargsArgumentIndex != -1) {
+                args[positionalVarargsArgumentIndex] = TemplateSequenceModel.EMPTY_SEQUENCE;
+            }
+
+            int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex();
+            if (namedVarargsArgumentIndex != -1) {
+                args[namedVarargsArgumentIndex] = TemplateSequenceModel.EMPTY_SEQUENCE;
+            }
+            
+            return args;
+        }
+    }
+
+    // String arg:
+
+    /**
+     * Convenience method to call
+     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
+     * castArgumentValueToString(args[argIndex], argIndex, callable, true, false)}.
+     */
+    public static String getStringArgument(
+            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
+            throws TemplateException {
+        return castArgumentValueToString(args[argIndex], argIndex, callable, true, false);
+    }
+
+    /**
+     * Convenience method to call
+     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
+     * castArgumentValueToString(args[argIndex], argIndex, callable, false, false)}.
+     */
+    public static String getStringArgument(
+            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
+            throws TemplateException {
+        return castArgumentValueToString(args[argIndex], argIndex, callable, false, false);
+    }
+
+    /**
+     * Convenience method to call
+     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
+     * castArgumentValueToString(args[argIndex], argIndex, callable, true, true)}.
+     */
+    public static String getOptionalStringArgument(
+            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
+            throws TemplateException {
+        return castArgumentValueToString(args[argIndex], argIndex, callable, true, true);
+    }
+
+    /**
+     * Convenience method to call
+     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
+     * castArgumentValueToString(args[argIndex], argIndex, callable, false, true)}.
+     */
+    public static String getOptionalStringArgument(
+            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
+            throws TemplateException {
+        return castArgumentValueToString(args[argIndex], argIndex, callable, false, true);
+    }
+
+    /**
+     * Checks if the argument value is a string; it does NOT check if {@code args} is big enough.
+     *
+     * @param calledAsFunction
+     *         If {@code callable} was called as function (as opposed to called as a directive)
+     * @param optional
+     *         If we allow a {@code null} return value
+     *
+     * @return Null {@code null} if the argument was omitted or {@code null}
+     *
+     * @throws TemplateException
+     *         If the argument is not of the proper type or is non-optional yet {@code null}. The error message
+     *         describes the problem in detail.
+     */
+    public static String castArgumentValueToString(
+            TemplateModel argValue, int argIdx, TemplateCallableModel callable,
+            boolean calledAsFunction, boolean optional)
+            throws TemplateException {
+        if (argValue instanceof TemplateScalarModel) {
+            return _EvalUtils.modelToString((TemplateScalarModel) argValue, null);
+        }
+        if (argValue == null) {
+            if (optional) {
+                return null;
+            }
+            throw newNullOrOmittedArgumentException(argIdx, callable, calledAsFunction);
+        }
+        throw newArgumentValueTypeException(argValue, argIdx, TemplateScalarModel.class, callable, calledAsFunction);
+    }
+
+    // Number arg:
+    
+    public static Number getNumberArgument(
+            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
+            throws TemplateException {
+        return castArgumentValueToNumber(args[argIndex], argIndex, callable, true, false);
+    }
+
+    public static Number getNumberArgument(
+            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
+            throws TemplateException {
+        return castArgumentValueToNumber(args[argIndex], argIndex, callable, false, false);
+    }
+
+    public static Number getOptionalNumberArgument(
+            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
+            throws TemplateException {
+        return castArgumentValueToNumber(args[argIndex], argIndex, callable, true, true);
+    }
+
+    public static Number getOptionalNumberArgument(
+            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
+            throws TemplateException {
+        return castArgumentValueToNumber(args[argIndex], argIndex, callable, false, true);
+    }
+
+    public static Number castArgumentValueToNumber(
+            TemplateModel argValue, int argIdx, TemplateCallableModel callable,
+            boolean calledAsFunction, boolean optional)
+            throws TemplateException {
+        if (argValue instanceof TemplateNumberModel) {
+            return _EvalUtils.modelToNumber((TemplateNumberModel) argValue, null);
+        }
+        if (argValue == null) {
+            if (optional) {
+                return null;
+            }
+            throw newNullOrOmittedArgumentException(argIdx, callable, calledAsFunction);
+        }
+        throw newArgumentValueTypeException(
+                argValue, argIdx, TemplateNumberModel.class, callable,
+                calledAsFunction);
+    }
+
+    // TODO boolean, etc.
+
+    // Argument count
+
+    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
+    public static void checkArgumentCount(int argCnt, int expectedCnt, TemplateFunctionModel callable)
+            throws TemplateException {
+        checkArgumentCount(argCnt, expectedCnt, callable, true);
+    }
+
+    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
+    public static void checkArgumentCount(int argCnt, int expectedCnt, TemplateDirectiveModel callable)
+            throws TemplateException {
+        checkArgumentCount(argCnt, expectedCnt, callable, false);
+    }
+
+    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
+    public static void checkArgumentCount(int argCnt, int expectedCnt,
+            TemplateCallableModel callable, boolean calledAsFunction) throws TemplateException {
+        checkArgumentCount(argCnt, expectedCnt, expectedCnt, callable, calledAsFunction);
+    }
+
+    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
+    public static void checkArgumentCount(int argCnt, int minCnt, int maxCnt, TemplateFunctionModel callable)
+            throws TemplateException {
+        checkArgumentCount(argCnt, minCnt, maxCnt, callable, true);
+    }
+
+    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
+    public static void checkArgumentCount(int argCnt, int minCnt, int maxCnt, TemplateDirectiveModel callable)
+            throws TemplateException {
+        checkArgumentCount(argCnt, minCnt, maxCnt, callable, false);
+    }
+
+    /**
+     * Useful when the {@link ArgumentArrayLayout} is {@code null} and so the argument array length is not fixed,
+     * to check if the number of arguments is in the given range.
+     */
+    public static void checkArgumentCount(int argCnt, int minCnt, int maxCnt,
+        TemplateCallableModel callable, boolean calledAsFunction) throws TemplateException {
+        if (argCnt < minCnt || argCnt > maxCnt) {
+            throw new TemplateException(
+                    _CallableUtils.getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                    getMessagePartExpectedNArgumentButHadM(argCnt, minCnt, maxCnt));
+        }
+    }
+
+    private static Object[] getMessagePartExpectedNArgumentButHadM(int argCnt, int minCnt, int maxCnt) {
+        ArrayList<Object> desc = new ArrayList<>(20);
+
+        desc.add("Expected ");
+
+        if (minCnt == maxCnt) {
+            if (maxCnt == 0) {
+                desc.add("no");
+            } else {
+                desc.add(maxCnt);
+            }
+        } else if (maxCnt - minCnt == 1) {
+            desc.add(minCnt);
+            desc.add(" or ");
+            desc.add(maxCnt);
+        } else {
+            desc.add(minCnt);
+            if (maxCnt != Integer.MAX_VALUE) {
+                desc.add(" to ");
+                desc.add(maxCnt);
+            } else {
+                desc.add(" or more (unlimited)");
+            }
+        }
+        desc.add(" argument");
+        if (maxCnt > 1) desc.add("s");
+
+        desc.add(" but has received ");
+        if (argCnt == 0) {
+            desc.add("none");
+        } else {
+            desc.add(argCnt);
+        }
+        desc.add(".");
+
+        return desc.toArray();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/util/TemplateLanguageUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/TemplateLanguageUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/TemplateLanguageUtils.java
index 749ccd5..9b980d9 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/TemplateLanguageUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/TemplateLanguageUtils.java
@@ -840,12 +840,18 @@ public final class TemplateLanguageUtils {
         }
 
         if (TemplateCallableModel.class.isAssignableFrom(cl)) {
+            boolean recognized = false;
             if (TemplateDirectiveModel.class.isAssignableFrom(cl)) {
                 appendTypeName(sb, typeNamesAppended, _CoreAPI.isMacro(cl) ? "macro" : "directive");
+                recognized = true;
             }
             if (TemplateFunctionModel.class.isAssignableFrom(cl)) {
                 appendTypeName(sb, typeNamesAppended,
                         JavaMethodModel.class.isAssignableFrom(cl) ? "method" : "function");
+                recognized = true;
+            }
+            if (!recognized && _CoreAPI.isTemplateLanguageCallable(cl)) {
+                appendTypeName(sb, typeNamesAppended, "macro or function defined with template language");
             }
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/javacc/FTL.jj b/freemarker-core/src/main/javacc/FTL.jj
index 613b252..4a3295f 100644
--- a/freemarker-core/src/main/javacc/FTL.jj
+++ b/freemarker-core/src/main/javacc/FTL.jj
@@ -2248,8 +2248,8 @@ ASTDollarInterpolation ASTDollarInterpolation() :
     begin = <DOLLAR_INTERPOLATION_OPENING>
     exp = ASTExpression()
     {
-        notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
-        notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
+        notHashLiteral(exp, MessageUtils.STRING_COERCABLE_TYPES_DESC);
+        notListLiteral(exp, MessageUtils.STRING_COERCABLE_TYPES_DESC);
     }
     end = <CLOSING_CURLY_BRACKET>
     {


Mime
View raw message