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 2018/06/16 11:42:30 UTC

[1/3] freemarker git commit: Some ground work to support dialects later. Added classes like Dialect and StaticallyLinkedNamespaceEntry, but these are non-public for now, and just drafts, and aren't actually used by the templates. The final ones will need

Repository: freemarker
Updated Branches:
  refs/heads/3 056635cd6 -> e8e58ffa5


http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java
index 644ab25..df61532 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java
@@ -41,29 +41,37 @@ public final class DefaultTemplateLanguage extends TemplateLanguage {
      * from the {@link Configuration} (or {@link TemplateConfiguration}). Avoid it, as it's problematic for tooling.
      */
     public static final DefaultTemplateLanguage F3AC = new DefaultTemplateLanguage("f3ac", true,
+            DefaultDialect.INSTANCE,
             null, null,
             TagSyntax.ANGLE_BRACKET, InterpolationSyntax.DOLLAR);
     public static final DefaultTemplateLanguage F3SC = new DefaultTemplateLanguage("f3sc", true,
+            DefaultDialect.INSTANCE,
             null, null,
             TagSyntax.SQUARE_BRACKET, InterpolationSyntax.SQUARE_BRACKET);
 
     public static final DefaultTemplateLanguage F3AH = new DefaultTemplateLanguage("f3ah", true,
+            DefaultDialect.INSTANCE,
             HTMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT,
             TagSyntax.ANGLE_BRACKET, InterpolationSyntax.DOLLAR);
     public static final DefaultTemplateLanguage F3AX = new DefaultTemplateLanguage("f3ax", true,
+            DefaultDialect.INSTANCE,
             XMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT,
             TagSyntax.ANGLE_BRACKET, InterpolationSyntax.DOLLAR);
     public static final DefaultTemplateLanguage F3AU = new DefaultTemplateLanguage("f3au", true,
+            DefaultDialect.INSTANCE,
             UndefinedOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT,
             TagSyntax.ANGLE_BRACKET, InterpolationSyntax.DOLLAR);
 
     public static final DefaultTemplateLanguage F3SH = new DefaultTemplateLanguage("f3sh", true,
+            DefaultDialect.INSTANCE,
             HTMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT,
             TagSyntax.SQUARE_BRACKET, InterpolationSyntax.SQUARE_BRACKET);
     public static final DefaultTemplateLanguage F3SX = new DefaultTemplateLanguage("f3sx", true,
+            DefaultDialect.INSTANCE,
             XMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT,
             TagSyntax.SQUARE_BRACKET, InterpolationSyntax.SQUARE_BRACKET);
     public static final DefaultTemplateLanguage F3SU = new DefaultTemplateLanguage("f3su", true,
+            DefaultDialect.INSTANCE,
             UndefinedOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT,
             TagSyntax.SQUARE_BRACKET, InterpolationSyntax.SQUARE_BRACKET);
 
@@ -81,11 +89,11 @@ public final class DefaultTemplateLanguage extends TemplateLanguage {
      * call this yourself; use constants like {@link #F3AH} instead when possible.
      * 
      * @param fileExtension
-     *            See in {@link TemplateLanguage#TemplateLanguage(String, OutputFormat, AutoEscapingPolicy)}
+     *            See in {@link TemplateLanguage#TemplateLanguage(String, Dialect, OutputFormat, AutoEscapingPolicy)}
      * @param outputFormat
-     *            See in {@link TemplateLanguage#TemplateLanguage(String, OutputFormat, AutoEscapingPolicy)}
+     *            See in {@link TemplateLanguage#TemplateLanguage(String, Dialect, OutputFormat, AutoEscapingPolicy)}
      * @param autoEscapingPolicy
-     *            See in {@link TemplateLanguage#TemplateLanguage(String, OutputFormat, AutoEscapingPolicy)}
+     *            See in {@link TemplateLanguage#TemplateLanguage(String, Dialect, OutputFormat, AutoEscapingPolicy)}
      * @param tagSyntax
      *            The tag syntax used; not {@code null}.
      * @param interpolationSyntax
@@ -93,20 +101,21 @@ public final class DefaultTemplateLanguage extends TemplateLanguage {
      */
     public DefaultTemplateLanguage(
             String fileExtension,
+            Dialect dialect,
             OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy,
             TagSyntax tagSyntax, InterpolationSyntax interpolationSyntax) {
-        this(fileExtension, false, outputFormat, autoEscapingPolicy, tagSyntax, interpolationSyntax);
+        this(fileExtension, false, dialect, outputFormat, autoEscapingPolicy, tagSyntax, interpolationSyntax);
     }
 
     /**
-     * Used internally to allow extensions starting with "f" 
+     * Used internally to allow extensions starting with "f" (as those are reserved for FreeMarker) 
      */
     DefaultTemplateLanguage(
-            String fileExtension,
-            boolean allowExtensionStartingWithF,
+            String fileExtension, boolean allowExtensionStartingWithF,
+            Dialect dialect,
             OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy,
             TagSyntax tagSyntax, InterpolationSyntax interpolationSyntax) {
-        super(fileExtension, allowExtensionStartingWithF, outputFormat, autoEscapingPolicy);
+        super(fileExtension, allowExtensionStartingWithF, dialect, outputFormat, autoEscapingPolicy);
         _NullArgumentException.check("tagSyntax", tagSyntax);
         _NullArgumentException.check("interpolationSyntax", interpolationSyntax);
         this.tagSyntax = tagSyntax;

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/Dialect.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Dialect.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Dialect.java
new file mode 100644
index 0000000..056bce5
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Dialect.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+
+/**
+ * A {@linkplain TemplateLanguage template language} dialect; specifies what predefined callables (like directives,
+ * built-ins) are available in the template language, but it doesn't specify the syntax. Unlike callables exposed to
+ * templates otherwise, callables that are part of the dialect use the same syntax as the callables traditionally
+ * predefined ones (like {@code <#if ...>}, {@code ?upperCase}", etc.), and they are linked statically (see
+ * {@link StaticallyLinkedNamespaceEntry} for the advantages).
+ * 
+ * <p>
+ * A {@link Dialect} object is usually a static singleton. If you need internal state that's bound to a
+ * {@link Configuration} instance, you can do that in the {@link ConfiguredDialect} created by
+ * {@link Dialect#createConfiguredDialect(Configuration)} object.
+ * 
+ * @see StaticallyLinkedNamespaceEntry
+ */
+//TODO [FM3] will be public. Also, then move it into core.dialect?
+abstract class Dialect {
+    
+    private final String name;
+    private final Version version;
+    
+    public Dialect(String name, Version version) {
+        this.name = name;
+        this.version = version;
+    }
+
+    /**
+     * The informal name of this dialect (maybe used in error messages)
+     */
+    public String getName() {
+        return name;
+    }
+    
+    /**
+     * The version number of this dialect (maybe used in error messages).
+     */
+    public Version getVersion() {
+        return version;
+    }
+
+    /**
+     * Creates a {@link ConfiguredDialect} object that's bound to the parameter {@link Configuration} object. FreeMarker
+     * calls this method of each {@link Dialect} that will be used with the {@link Configuration} when the
+     * {@link Configuration} object is constructed.
+     * 
+     * @param cfg
+     *            The fully initialized {@link Configuration}, except that some {@link Dialect}-s in it may not have an
+     *            associated {@link ConfiguredDialect} yet. This {@link Configuration} is not yet "safely published"
+     *            (see in the Java Memory Model), so it must not be exposed to other threads by this method.
+     * 
+     * @throws ConfigurationException
+     *             If any problem occurs, it should be wrapped into this.
+     */
+    public abstract ConfiguredDialect createConfiguredDialect(Configuration cfg) throws ConfigurationException;
+    
+    /**
+     * A {@link Dialect} that is bound to a {@link Configuration}. While a dialect is in principle a static singleton,
+     * and so is independent of any particular {@link Configuration} object, some dialects may have internal state
+     * initialized depending on some {@link Configuration} settings.
+     * 
+     * <p>
+     * Instances should be created by {@link Dialect#createConfiguredDialect(Configuration)}. The concrete
+     * {@link ConfiguredDialect} is usually private and is a nested class in the concrete {@link Dialect}, which
+     * overrides {@link Dialect#createConfiguredDialect(Configuration)} to return the concrete instance
+     * 
+     * <p>
+     * The main function of a {@link ConfiguredDialect} is to expose the namespace of the dialect. The dialect namespace
+     * contains the entries that are statically linked, and can usually can be accessed without any namespace prefix (
+     * usually, as the exact details depend on the template language). For example, for the default dialect the
+     * namespace contains entries like "if", "list", "upperCase", and so on.
+     */
+    //TODO [FM3] will be public. Also, then move it into core.dialect?
+    abstract class ConfiguredDialect {
+        
+        /**
+         * Returns a namespace entry of this dialect. or {@code null} if there's no match. 
+         */
+        public abstract StaticallyLinkedNamespaceEntry getNamespaceEntry(String name);
+        
+        /**
+         * To iterate through the namespace entries of this dialect.
+         */
+        public abstract Iterable<StaticallyLinkedNamespaceEntry> getNamespaceEntries();
+        
+        /**
+         * Returns the {@link Dialect} whose {@link Dialect#createConfiguredDialect(Configuration)} method has created this
+         * instance.
+         */
+        public final Dialect getDialect() {
+            return Dialect.this;
+        }
+        
+    }
+
+    // Final implementation, as {@link Dialect}-s maybe used as hash keys.
+    @Override
+    public final int hashCode() {
+        return super.hashCode();
+    }
+
+    // Final implementation, as {@link Dialect}-s maybe used as hash keys.
+    @Override
+    public final boolean equals(Object obj) {
+        return super.equals(obj);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 38a4f17..637901e 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
@@ -63,6 +63,8 @@ import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.model.impl.SimpleHash;
+import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
+import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
 import org.apache.freemarker.core.templateresolver.TemplateResolver;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
@@ -70,6 +72,7 @@ import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core.util.StringToIndexMap;
 import org.apache.freemarker.core.util._DateUtils;
 import org.apache.freemarker.core.util._DateUtils.DateToISO8601CalendarFactory;
+import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.util._NullWriter;
 import org.apache.freemarker.core.util._StringUtils;
 import org.apache.freemarker.core.valueformat.TemplateDateFormat;
@@ -280,7 +283,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             clearCachedValues();
             try {
                 doAutoImportsAndIncludes(this);
-                visit(getMainTemplate().getRootASTNode());
+                executeElement(getMainTemplate().getRootASTNode());
                 // It's here as we must not flush if there was an exception.
                 if (getAutoFlush()) {
                     out.flush();
@@ -366,17 +369,18 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     /**
      * "Visit" the template element.
      */
-    void visit(ASTElement element) throws IOException, TemplateException {
+    // TODO [FM3] will be public
+    void executeElement(ASTElement element) throws IOException, TemplateException {
         // ATTENTION: This method body is manually "inlined" into visit(ASTElement[]); keep them in sync!
         pushElement(element);
         try {
-            ASTElement[] templateElementsToVisit = element.accept(this);
+            ASTElement[] templateElementsToVisit = element.execute(this);
             if (templateElementsToVisit != null) {
                 for (ASTElement el : templateElementsToVisit) {
                     if (el == null) {
                         break;  // Skip unused trailing buffer capacity 
                     }
-                    visit(el);
+                    executeElement(el);
                 }
             }
         } catch (TemplateException te) {
@@ -386,12 +390,20 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         }
         // ATTENTION: This method body above is manually "inlined" into visit(ASTElement[]); keep them in sync!
     }
+
+    // TODO [FM3] will be public
+    final void executeNestedContent(ASTDirective parentCall) throws IOException, TemplateException {
+        executeElements(parentCall.getChildBuffer());
+    }
     
     /**
+     * Executes the elements passed in (which is usually the return value of {@link ASTElement#getChildBuffer()}).
+     * 
      * @param elementBuffer
      *            The elements to visit; might contains trailing {@code null}-s. Can be {@code null}.
      */
-    final void visit(ASTElement[] elementBuffer) throws IOException, TemplateException {
+    // TODO [FM3] will be public
+    final void executeElements(ASTElement[] elementBuffer) throws IOException, TemplateException {
         if (elementBuffer == null) {
             return;
         }
@@ -404,13 +416,13 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             // We don't just let Hotspot to do it, as we want a hard guarantee regarding maximum stack usage. 
             pushElement(element);
             try {
-                ASTElement[] templateElementsToVisit = element.accept(this);
+                ASTElement[] templateElementsToVisit = element.execute(this);
                 if (templateElementsToVisit != null) {
                     for (ASTElement el : templateElementsToVisit) {
                         if (el == null) {
                             break;  // Skip unused trailing buffer capacity 
                         }
-                        visit(el);
+                        executeElement(el);
                     }
                 }
             } catch (TemplateException te) {
@@ -429,13 +441,27 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     private static final TemplateModel[] NO_OUT_ARGS = new TemplateModel[0];
 
-    void visit(
-            ASTElement[] childBuffer,
-            final StringToIndexMap nestedContentParamNames, final TemplateModel[] nestedContentParamValues,
-            Writer out)
-        throws IOException, TemplateException {
+    /**
+     * Executes the nested content of a {@link ASTDirective}.
+     * 
+     * @param directiveCall
+     *            This is the directive call AST node whose nested content we will execute.
+     * @param nestedContentParamNames
+     *            The names of the nested content parameters as they were declared by the directive call. For example
+     *            {@code code <#list m as k, v>...</#list>}, a call of the {@code list} directive, declares "k" at index
+     *            0, and "v" at index 1.
+     * @param nestedContentParamValues
+     *            The actual values of the nested content parameters, which will be visible for the nested content as
+     *            variables. The caller of this method <em>must</em> ensure that {@code nestedContentParamNames} doesn't
+     *            return an index that's out of bounds in {@code nestedContentParamValues}!
+     */
+    // TODO [FM3] will be public
+    void executeNestedContent(
+            ASTDirective directiveCall,
+            final StringToIndexMap nestedContentParamNames, final TemplateModel[] nestedContentParamValues) throws IOException, TemplateException {
+        ASTElement[] childBuffer = directiveCall.getChildBuffer();
         if (nestedContentParamNames == null) { // This is by far the most frequent case
-            visit(childBuffer, out);
+            executeElements(childBuffer);
         } else {
             pushLocalContext(new LocalContext() {
                 @Override
@@ -450,24 +476,13 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
                 }
             });
             try {
-                visit(childBuffer, out);
+                executeElements(childBuffer);
             } finally {
                 popLocalContext();
             }
         }
     }
 
-    void visit(ASTElement[] childBuffer, Writer out) throws IOException, TemplateException {
-        // TODO [FM][CF] The plan is that `out` will be the root read only sink, so then it will work differently
-        Writer prevOut = this.out;
-        this.out = out;
-        try {
-            visit(childBuffer);
-        } finally {
-            this.out = prevOut;
-        }
-    }
-
     /**
      * Visit a block using buffering/recovery
      */
@@ -482,7 +497,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         boolean lastInAttemptBlock = inAttemptBlock;
         try {
             inAttemptBlock = true;
-            visit(attemptedSection);
+            executeElement(attemptedSection);
         } catch (TemplateException te) {
             thrownException = te;
         } finally {
@@ -493,7 +508,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         if (thrownException != null) {
             try {
                 recoveredErrorStack.add(thrownException);
-                visit(recoverySection);
+                executeElement(recoverySection);
             } finally {
                 recoveredErrorStack.remove(recoveredErrorStack.size() - 1);
             }
@@ -988,11 +1003,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         return _EvalUtils.compare(leftValue, _EvalUtils.CMP_OP_GREATER_THAN_EQUALS, rightValue, this);
     }
 
-    public void setOut(Writer out) {
+    public final void setOut(Writer out) {
         this.out = out;
     }
 
-    public Writer getOut() {
+    public final Writer getOut() {
         return out;
     }
 
@@ -1049,6 +1064,67 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         return _EvalUtils.coerceModelToPlainText(tm, null, null, this);
     }
     
+    /**
+     * Evaluates the expression and prints its value as if it was printed with an interpolation (like
+     * <code>${exp}</code>).
+     * 
+     * @param outputFormat
+     *            Acts like the output format in a template where an interpolation is called. Not {@code null}.
+     * @param autoEscapingPolicy
+     *            Acts like the auto escaping policy in a template where an interpolation is called. Not {@code null}.
+     */
+    // TODO [FM3] will be public
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    void interpolate(
+            ASTExpression exp,
+            OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy,
+            Environment env) throws IOException, TemplateException {
+        interpolate(exp.eval(env), exp, outputFormat, autoEscapingPolicy, env);
+    }
+    
+    /**
+     * Same as {@link #interpolate(ASTExpression, OutputFormat, AutoEscapingPolicy, Environment)}, but is used in the
+     * rare case where you have already evaluated the expression.
+     * 
+     * @param value
+     *            The value resulting from evaluating {@code exp}
+     * @param exp
+     *            The expression whose evaluation was resulted in {@code value}. It won't be evaluated again, but is
+     *            still used for error messages. {@code null} is tolerated, but should be avoided due to the resulting
+     *            poorer quality error messages.
+     */
+    void interpolate(
+            TemplateModel value, ASTExpression exp,
+            OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy,
+            Environment env) throws IOException, TemplateException {
+        _NullArgumentException.check("outputFormat", outputFormat);
+        _NullArgumentException.check("autoEscapingPolicy", autoEscapingPolicy);
+        
+        final Writer out = env.getOut();
+        
+        final Object moOrStr = _EvalUtils.coerceModelToPlainTextOrMarkup(value, exp, null, env);
+        if (moOrStr instanceof String) {
+            final String s = (String) moOrStr;
+            
+            MarkupOutputFormat markupOutputFormat;
+            if (outputFormat instanceof MarkupOutputFormat) {
+                markupOutputFormat = (MarkupOutputFormat) outputFormat;
+                if (autoEscapingPolicy == AutoEscapingPolicy.ENABLE_IF_SUPPORTED
+                        || autoEscapingPolicy == AutoEscapingPolicy.ENABLE_IF_DEFAULT
+                                && markupOutputFormat.isAutoEscapedByDefault()) {
+                    markupOutputFormat.output(s, out);
+                } else {
+                    out.write(s);
+                }
+            } else {
+                out.write(s);
+            }
+        } else {
+            final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr;
+            _EvalUtils.printTemplateMarkupOutputModel(mo, outputFormat, out, exp);
+        }
+    }
+    
     String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws TemplateException {
         TemplateBooleanFormat templateBooleanFormat = getTemplateBooleanFormat();
         if (value) {
@@ -2135,7 +2211,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     static void appendInstructionStackItem(ASTElement stackEl, StringBuilder sb) {
-        sb.append(MessageUtils.shorten(stackEl.getDescription(), 40));
+        sb.append(MessageUtils.shorten(stackEl.getLabelWithParameters(), 40));
 
         sb.append("  [");
         ASTDirMacroOrFunction enclosingMacro = getEnclosingMacro(stackEl);
@@ -2286,7 +2362,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         };
     }
 
-    private void pushElement(ASTElement element) {
+    void pushElement(ASTElement element) {
         final int newSize = ++instructionStackSize;
         ASTElement[] instructionStack = this.instructionStack;
         if (newSize > instructionStack.length) {
@@ -2300,7 +2376,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         instructionStack[newSize - 1] = element;
     }
 
-    private void popElement() {
+    void popElement() {
         instructionStackSize--;
     }
 
@@ -2481,7 +2557,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         final Template prevTemplate;
 
         importMacros(includedTemplate);
-        visit(includedTemplate.getRootASTNode());
+        executeElement(includedTemplate.getRootASTNode());
     }
 
     /**
@@ -2976,7 +3052,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
                     : callableDefinition.getName();
         }
 
-        protected void genericExecute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
+        protected void genericExecute(TemplateModel[] args, CallPlace callPlace, Environment env)
                 throws TemplateException, IOException {
             pushElement(callableDefinition);
             try {
@@ -2996,7 +3072,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
                     // Note: Default expressions are evaluated here, so namespace, stack, etc. must be already set
                     setLocalsFromArguments(macroCtx, args);
 
-                    visit(callableDefinition.getChildBuffer(), out);
+                    executeElements(callableDefinition.getChildBuffer());
                 } catch (ASTDirReturn.Return re) {
                     // Not an error, just a <#return>
                 } catch (TemplateException te) {
@@ -3079,7 +3155,14 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             if (getCallableDefinition() == ASTDirMacroOrFunction.PASS_MACRO) {
                 return;
             }
-            genericExecute(args, callPlace, out, env);
+            
+            Writer prevOut = env.getOut();
+            try {
+                env.setOut(out);
+                genericExecute(args, callPlace, env);
+            } finally {
+                env.setOut(prevOut);
+            }
         }
 
         @Override
@@ -3115,11 +3198,15 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
                 throws TemplateException {
             env.setLastReturnValue(null);
+            Writer prevOut = env.getOut();
             try {
-                genericExecute(args, callPlace, _NullWriter.INSTANCE, env);
+                env.setOut(_NullWriter.INSTANCE);
+                genericExecute(args, callPlace, env);
             } catch (IOException e) {
                 // Should not occur
                 throw new TemplateException("Unexpected exception during function execution", e, env);
+            } finally {
+                env.setOut(prevOut);
             }
             return env.getLastReturnValue();
         }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java
index e79bed2..a139a0a 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java
@@ -31,6 +31,7 @@ import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util.StringToIndexMap;
 import org.apache.freemarker.core.util.TemplateLanguageUtils;
 import org.apache.freemarker.core.util._StringUtils;
 import org.apache.freemarker.core.valueformat.TemplateDateFormat;
@@ -146,6 +147,9 @@ class MessageUtils {
      * The truncation is always signaled with a a {@code "..."} at the end of the result string.
      */
     static String shorten(String s, int maxLength) {
+        if (s == null) {
+            return null;
+        }
         if (maxLength < 5) maxLength = 5;
 
         boolean isTruncated = false;
@@ -359,4 +363,19 @@ class MessageUtils {
         return errorDescBuilder;
     }
 
+    static _ErrorDescriptionBuilder newBadNumberOfNestedContentParameterPassedMessage(
+            StringToIndexMap paramNamesOnCallPlace,
+            int numberOfParamsAtCallee) {
+        int numberOfParamsAtCallPlace = paramNamesOnCallPlace != null ? paramNamesOnCallPlace.size() : 0;
+        return new _ErrorDescriptionBuilder(
+            "The invocation declares ", (numberOfParamsAtCallPlace != 0 ? numberOfParamsAtCallPlace : "no"),
+            " nested content parameter(s)",
+            (numberOfParamsAtCallPlace != 0
+                    ? new Object[] { " (", new _DelayedJQuotedListing(paramNamesOnCallPlace.getKeys()), ")", }
+                    : ""),
+            ", but the called object intends to pass ",
+            numberOfParamsAtCallee, " parameters. You need to declare ", numberOfParamsAtCallee,
+            " nested content parameters.");
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
index 983c05d..f17b778 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
@@ -50,7 +50,10 @@ public interface ParsingConfiguration {
      * {@link TemplateLanguage#getAutoEscapingPolicy() autoEscapingPolicy}, that overrides the value of
      * the {@linkplain #getOutputFormat() outputFormat} and {@link #getAutoEscapingPolicy() autoEscapingPolicy}
      * settings that are coming from {@link Configuration#getTemplateConfigurations templateConfigurations}, from the
-     * {@link Configuration}, or from any other {@link ParsingConfiguration}.
+     * {@link Configuration}, or from any other {@link ParsingConfiguration}. Most {@link TemplateLanguage}-s should
+     * have non-{@code null} for those settings, to prevent confusion on the template author side. There can be
+     * exceptions from this though, like {@link DefaultTemplateLanguage#F3AC} (where the "C" at the end stands for
+     * "configurable") has {@code null} for these settings.
      * 
      * @see ParsingConfiguration#getRecognizeStandardFileExtensions()
      */

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/StaticLinkingCheckException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/StaticLinkingCheckException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/StaticLinkingCheckException.java
new file mode 100644
index 0000000..50587b8
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/StaticLinkingCheckException.java
@@ -0,0 +1,74 @@
+/*
+ * 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.util._NullArgumentException;
+
+/**
+ * Exception thrown when a {@link Dialect} detects a problem during parsing (such a required parameter to a directive is
+ * missing). The exception will be converted to a {@link ParseException} by the parser, where the error message and
+ * cause exception will be the same as of the {@link StaticLinkingCheckException}. 
+ */
+//TODO [FM3][DIALECTS] will be public
+@SuppressWarnings("serial")
+final class StaticLinkingCheckException extends Exception {
+    
+    private final _ErrorDescriptionBuilder messageBuilder;
+    private String builtMessage; 
+    private final String simpleMessage; 
+
+    public StaticLinkingCheckException(_ErrorDescriptionBuilder messageBuilder) {
+        this(messageBuilder, null);
+    }
+    
+    public StaticLinkingCheckException(_ErrorDescriptionBuilder messageBuilder, Throwable cause) {
+        _NullArgumentException.check("messageBuilder", messageBuilder);
+        this.messageBuilder = messageBuilder;
+        simpleMessage = null;
+    }
+
+    public StaticLinkingCheckException(String message) {
+        this(message, null);
+    }
+    
+    public StaticLinkingCheckException(String message, Throwable cause) {
+        _NullArgumentException.check("message", message);
+        this.messageBuilder = null;
+        simpleMessage = message;
+    }
+
+    @Override
+    public String getMessage() {
+        if (simpleMessage != null) {
+            return simpleMessage;
+        }
+        
+        String builtDescription = this.builtMessage;
+        if (builtDescription != null) {
+            return builtDescription;
+        }
+        synchronized (this) {
+            builtDescription = messageBuilder.toString();
+            this.builtMessage = builtDescription;
+        }
+        return builtDescription;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/StaticallyLinkedNamespaceEntry.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/StaticallyLinkedNamespaceEntry.java b/freemarker-core/src/main/java/org/apache/freemarker/core/StaticallyLinkedNamespaceEntry.java
new file mode 100644
index 0000000..87ba7f7
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/StaticallyLinkedNamespaceEntry.java
@@ -0,0 +1,117 @@
+package org.apache.freemarker.core;
+
+/*
+ * 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.
+ */
+
+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.util.CommonSupplier;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+/**
+ * A template language namespace entry that's linked statically to the referring template (TODO [FM3] for planned
+ * Dialects feature, currently unused).
+ * Static linking allows detecting mistakes during template parsing (as opposed to during the later template
+ * processing), can make accessing the value faster (by avoiding runtime lookup), and allows callables to have
+ * parse-time effects. Currently used in {@link Dialect}. 
+ */
+//TODO [FM3][FREEMARKER-99][DIALECTS] will be public. Also move it into core.dialect?
+final class StaticallyLinkedNamespaceEntry {
+    
+    private final String name;
+    private final TemplateModel value;
+    private final CommonSupplier<ASTDirective> directiveCallNodeFactory;
+    private final CommonSupplier<ASTExpFunctionCall> functionCallNodeFactory;
+    
+    /**
+     * @param name
+     *            Variable name in the namespace of the dialect
+     * @param value
+     *            The runtime value of the entry. Not {@code null}.
+     *            If this entry is a {@link TemplateCallableModel} that's not callable dynamically (as it has
+     *            parse-time effects or such dependencies), then the corresponding {@code execute} method should to
+     *            throw exception explaining that, but the {@link TemplateCallableModel} will be still used by the
+     *            parser to retrieve meta-data such as
+     *            {@link TemplateDirectiveModel#getDirectiveArgumentArrayLayout()},
+     *            {@link TemplateDirectiveModel#isNestedContentSupported()},
+     *            {@link TemplateFunctionModel#getFunctionArgumentArrayLayout()}.
+     * @param directiveCallNodeFactory
+     *            If this entry is usable as directive, the factory for the corresponding {@linkplain ASTNode AST node}.
+     *            At least one of {@code directiveCallNodeFactory} and {@code functionCallNodeFactory} must be
+     *            non-{@code null}. It must be ensured that the behavior of this node is consistent with argument array
+     *            layout and other meta-data present in the entry {@code value}.
+     * @param functionCallNodeFactory
+     *            If this entry is usable as function (or like an FM2 "built-in"), the factory for the corresponding
+     *            {@linkplain ASTNode AST node}. Not {@code null}. It must be ensured that the behavior of this node is consistent with argument array
+     *            layout and other meta-data present in the entry {@code value}.
+     */
+    public StaticallyLinkedNamespaceEntry(
+            String name, TemplateModel value,
+            CommonSupplier<ASTDirective> directiveCallNodeFactory,
+            CommonSupplier<ASTExpFunctionCall> functionCallNodeFactory) {
+        _NullArgumentException.check("name", name);
+        this.name = name;
+        if (directiveCallNodeFactory == null && functionCallNodeFactory == null) {
+            throw new IllegalArgumentException(
+                    "At least one of \"directiveCallNodeFactory\" and \"functionCallNodeFactory\" must be non-null.");
+        }
+        this.directiveCallNodeFactory = directiveCallNodeFactory;
+        this.functionCallNodeFactory = functionCallNodeFactory;
+        this.value = value;
+    }
+
+    /**
+     * See similarly named
+     * {@linkplain #StaticallyLinkedNamespaceEntry(String, TemplateModel, CommonSupplier, CommonSupplier) constructor}
+     * parameter.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * See similarly named
+     * {@linkplain #StaticallyLinkedNamespaceEntry(String, TemplateModel, CommonSupplier, CommonSupplier) constructor}
+     * parameter.
+     */
+    public CommonSupplier<ASTDirective> getDirectiveCallNodeFactory() {
+        return directiveCallNodeFactory;
+    }
+
+    /**
+     * See similarly named
+     * {@linkplain #StaticallyLinkedNamespaceEntry(String, TemplateModel, CommonSupplier, CommonSupplier) constructor}
+     * parameter.
+     */
+    public CommonSupplier<ASTExpFunctionCall> getFunctionCallNodeFactory() {
+        return functionCallNodeFactory;
+    }
+
+    /**
+     * See similarly named
+     * {@linkplain #StaticallyLinkedNamespaceEntry(String, TemplateModel, CommonSupplier, CommonSupplier) constructor}
+     * parameter.
+     */
+    public TemplateModel getValue() {
+        return value;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 42e2686..c184281 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
@@ -122,7 +122,7 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
     private AutoEscapingPolicy autoEscapingPolicy;
     // Values from template content that are detected automatically:
     private Charset actualSourceEncoding;
-    TagSyntax actualTagSyntax; // TODO [FM3][CF] Should be private
+    TagSyntax actualTagSyntax; // TODO [FM3][FREEMARKER-99] Should be private
     private InterpolationSyntax interpolationSyntax;
 
     // Custom state:

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
index 8627059..1b1b9a9 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -36,10 +36,11 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 /**
  * A partial set of configuration settings used for customizing the {@link Configuration}-level settings for individual
  * {@link Template}-s (or rather, for a group of templates). That it's partial means that you should call the
- * corresponding {@code isXxxSet()} before getting a settings, or else you may cause
- * {@link CoreSettingValueNotSetException}. (There's no fallback to the {@link Configuration}-level settings to keep the
- * dependency graph of configuration related beans non-cyclic. As user code seldom reads settings directly from
- * {@link TemplateConfiguration}-s anyway, this compromise was chosen.)
+ * corresponding {@code isXxxSet()} before getting a setting, or else you may cause
+ * {@link CoreSettingValueNotSetException}. (There's no fallback to the {@link Configuration}-level settings implemented
+ * in this class, to keep the dependency graph of configuration related beans non-cyclic. Instead, the caller of this
+ * API must implement the fallback itself. As user code hardly ever reads directly from {@link TemplateConfiguration}-s,
+ * this compromise was chosen.)
  * <p>
  * Note on the {@code locale} setting: When used with the standard template loading/caching mechanism ({@link
  * Configuration#getTemplate(String)} and its overloads), localized template lookup happens before the {@code locale}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java
index 4bc9a4f..734622e 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java
@@ -83,18 +83,5 @@ class TemplateElements {
             }
         }
     }
-    
-    /**
-     * Used for some backward compatibility hacks.
-     */
-    ASTImplicitParent asMixedContent() {
-        ASTImplicitParent mixedContent = new ASTImplicitParent();
-        if (count != 0) {
-            ASTElement first = buffer[0];
-            mixedContent.setChildren(this);
-            mixedContent.setLocation(first.getTemplate(), first, getLast());
-        }
-        return mixedContent;
-    }
 
 }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java
index 145916c..defcf5d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java
@@ -22,9 +22,9 @@ import java.util.Collection;
 import java.util.Collections;
 
 /**
- * Used as the return value of {@link ASTElement#accept(Environment)} when the invoked element has nested elements
+ * Used as the return value of {@link ASTElement#execute(Environment)} when the invoked element has nested elements
  * to invoke. It would be more natural to invoke child elements before returning from
- * {@link ASTElement#accept(Environment)}, however, if there's nothing to do after the child elements were invoked,
+ * {@link ASTElement#execute(Environment)}, however, if there's nothing to do after the child elements were invoked,
  * that would mean wasting stack space.
  */
 class TemplateElementsToVisit {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
index 3b1a812..98002c9 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
@@ -29,9 +29,8 @@ import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.util._StringUtils;
 
 /**
- * Represents a template language; a template language specifies the syntax, and usually also the {@link OutputFormat}
- * and {@link AutoEscapingPolicy} of the template. In the future (TODO [FM3]) custom template languages may also
- * specify the dialect (which is the set of core directives and functions).  
+ * Represents a template language; a template language specifies the syntax, the {@link Dialect}, and usually also the
+ * {@link OutputFormat} and {@link AutoEscapingPolicy}.  
  * 
  * <p><em>Currently this class is not mature, so it can't be implemented outside FreeMarker,
  * also its methods shouldn't be called from outside FreeMarker.</em>
@@ -48,6 +47,7 @@ public abstract class TemplateLanguage {
     private final String fileExtension;
     private final OutputFormat outputFormat;
     private final AutoEscapingPolicy autoEscapingPolicy;
+    private final Dialect dialect;
     
     // Package visibility to prevent user implementations until this API is mature.
 
@@ -70,14 +70,14 @@ public abstract class TemplateLanguage {
      *             If the {@code #fileExtension} argument contains upper case letter or dot, or if it starts with "f"
      *             and the language isn't defined by the FreeMarker project.
      */
-    public TemplateLanguage(String fileExtension, OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy) {
-        this(fileExtension, false, outputFormat, autoEscapingPolicy);
+    public TemplateLanguage(String fileExtension, Dialect dialect, OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy) {
+        this(fileExtension, false, dialect, outputFormat, autoEscapingPolicy);
     }
     
     /**
-     * Non-public constructor used for languages defined by the FreeMarker project.
+     * Used internally to allow extensions starting with "f" (as those are reserved for FreeMarker) 
      */
-    TemplateLanguage(String fileExtension, boolean allowExtensionStartingWithF,
+    TemplateLanguage(String fileExtension, boolean allowExtensionStartingWithF, Dialect dialect,
             OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy) {
         _NullArgumentException.check("fileExtension", fileExtension);
         for (int i = 0; i < fileExtension.length(); i++) {
@@ -94,6 +94,10 @@ public abstract class TemplateLanguage {
                     "The \"fileExtension\" argument can't start with 'f' for an user-defined language.");
         }
         this.fileExtension = fileExtension;
+        
+        _NullArgumentException.check("dialect", dialect);
+        this.dialect = dialect;
+        
         this.outputFormat = outputFormat;
         this.autoEscapingPolicy = autoEscapingPolicy;
     }
@@ -123,13 +127,15 @@ public abstract class TemplateLanguage {
     public String getFileExtension() {
         return fileExtension;
     }
-
-    @Override
-    public final String toString() {
-        return "TemplateLanguage(" + _StringUtils.jQuote(fileExtension) + ")";
-    }
     
     /**
+     * Returns the {@link Dialect} used by this template language; not {@code null}.
+     */
+    public Dialect getDialect() {
+        return dialect;
+    }
+
+    /**
      * The {@link OutputFormat} that this language enforces, or else {@code null} (and it comes from the
      * {@link ParsingConfiguration}).
      * 
@@ -151,4 +157,9 @@ public abstract class TemplateLanguage {
         return autoEscapingPolicy;
     }
 
+    @Override
+    public final String toString() {
+        return "TemplateLanguage(" + _StringUtils.jQuote(fileExtension) + ")";
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 be7877c..874a896 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
@@ -56,7 +56,7 @@ class ThreadInterruptionSupportTemplatePostProcessor extends TemplatePostProcess
         
         final int childCount = te.getChildCount();
         for (int i = 0; i < childCount; i++) {
-            addInterruptionChecks(te.getChild(i));
+            addInterruptionChecks(te.fastGetChild(i));
         }
         
         if (te.isNestedBlockRepeater()) {
@@ -79,7 +79,7 @@ class ThreadInterruptionSupportTemplatePostProcessor extends TemplatePostProcess
         }
 
         @Override
-        ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        ASTElement[] execute(Environment env) throws TemplateException, IOException {
             // As the API doesn't allow throwing InterruptedException here (nor anywhere else, most importantly,
             // Template.process can't throw it), we must not clear the "interrupted" flag of the thread.
             if (Thread.currentThread().isInterrupted()) {
@@ -89,12 +89,12 @@ class ThreadInterruptionSupportTemplatePostProcessor extends TemplatePostProcess
         }
 
         @Override
-        protected String dump(boolean canonical) {
-            return canonical ? "" : "<#--" + getASTNodeDescriptor() + "--#>";
+        String dump(boolean canonical) {
+            return canonical ? "" : "<#--" + getLabelWithoutParameters() + "--#>";
         }
 
         @Override
-        String getASTNodeDescriptor() {
+        public String getLabelWithoutParameters() {
             return "##threadInterruptionCheck";
         }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/UnparsedTemplateLanguage.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/UnparsedTemplateLanguage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/UnparsedTemplateLanguage.java
index 2119ee3..6fe58f5 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/UnparsedTemplateLanguage.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/UnparsedTemplateLanguage.java
@@ -45,7 +45,8 @@ public final class UnparsedTemplateLanguage extends TemplateLanguage {
     
     private UnparsedTemplateLanguage(String fileExtension, boolean allowExtensionStartingWithF,
             OutputFormat outputFormat) {
-        super(fileExtension, allowExtensionStartingWithF, outputFormat, AutoEscapingPolicy.ENABLE_IF_DEFAULT);
+        super(fileExtension, allowExtensionStartingWithF, DefaultDialect.INSTANCE,
+                outputFormat, AutoEscapingPolicy.ENABLE_IF_DEFAULT);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/UnsupportedFM2TemplateLanguage.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/UnsupportedFM2TemplateLanguage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/UnsupportedFM2TemplateLanguage.java
index 158a35a..e67091d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/UnsupportedFM2TemplateLanguage.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/UnsupportedFM2TemplateLanguage.java
@@ -41,7 +41,7 @@ class UnsupportedFM2TemplateLanguage extends TemplateLanguage {
     
     private UnsupportedFM2TemplateLanguage(String fileExtension, OutputFormat outputFormat,
             AutoEscapingPolicy autoEscapingPolicy) {
-        super(fileExtension, true, outputFormat, autoEscapingPolicy);
+        super(fileExtension, true, DefaultDialect.INSTANCE, outputFormat, autoEscapingPolicy);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 542a2f2..a4132c0 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
@@ -57,7 +57,7 @@ public class _CallableUtils {
             throws TemplateException {
         if (namedArgs != null) {
             throw new TemplateException(env,
-                    getNamedArgumentsNotSupportedMessage(callable, namedArgs[0], calledAsFunction));
+                    getNamedArgumentsNotSupportedMessage(callable, namedArgs[0].name, calledAsFunction));
         }
 
         TemplateModel[] execArgs;
@@ -77,8 +77,8 @@ public class _CallableUtils {
             ASTExpression[] positionalArgs, NamedArgument[] namedArgs, ArgumentArrayLayout argsLayout,
             TemplateCallableModel callable, boolean calledAsFunction,
             Environment env) throws TemplateException {
-        int predefPosArgCnt = argsLayout.getPredefinedPositionalArgumentCount();
-        int posVarargsArgIdx = argsLayout.getPositionalVarargsArgumentIndex();
+        final int predefPosArgCnt = argsLayout.getPredefinedPositionalArgumentCount();
+        final int posVarargsArgIdx = argsLayout.getPositionalVarargsArgumentIndex();
 
         TemplateModel[] execArgs = new TemplateModel[argsLayout.getTotalLength()];
 
@@ -104,44 +104,14 @@ public class _CallableUtils {
             }
             execArgs[posVarargsArgIdx] = varargsSeq;
         } else if (positionalArgs != null && positionalArgs.length > predefPosArgCnt) {
-            checkSupportsAnyParameters(callable, argsLayout, calledAsFunction);
-            List<String> validPredefNames = argsLayout.getPredefinedNamedArgumentsMap().getKeys();
-            _ErrorDescriptionBuilder errorDesc = new _ErrorDescriptionBuilder(
-                    getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
-                    "This ", getCallableTypeName(callable, calledAsFunction),
-                    " ",
-                    (predefPosArgCnt != 0
-                            ? new Object[]{ "can only have ", predefPosArgCnt }
-                            : "can't have"
-                    ),
-                    " arguments passed by position, but the invocation has ",
-                    positionalArgs.length, " such arguments.",
-                    (!argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported() ?
-                            new Object[] {
-                                    " Try to pass arguments by name (as in ",
-                                    (callable instanceof TemplateDirectiveModel
-                                            ? "<@example x=1 y=2 />"
-                                            : "example(x=1, y=2)"),
-                                    ")",
-                                    (!validPredefNames.isEmpty()
-                                            ? new Object[] { " The supported parameter names are: ",
-                                            new _DelayedJQuotedListing(validPredefNames)}
-                                            : _CollectionUtils.EMPTY_OBJECT_ARRAY)}
-                            : "")
-            );
-            if (callable instanceof Environment.TemplateLanguageDirective
-                    && !argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported()) {
-                errorDesc.tip("You can pass a parameter by position (i.e., without specifying its name, as you"
-                        + " have tried now) when the macro has defined that parameter to be a positional parameter. "
-                        + "See in the documentation how, and when that's a good practice.");
-            }
-            throw new TemplateException(env, errorDesc);
+            throw new TemplateException(env,
+                    newPositionalArgDoesNotFitArgLayoutErrorDesc(argsLayout, callable, calledAsFunction));
         }
 
         int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex();
         NativeHashEx namedVarargsHash = null;
         if (namedArgs != null) {
-            StringToIndexMap predefNamedArgsMap = argsLayout.getPredefinedNamedArgumentsMap();
+            final StringToIndexMap predefNamedArgsMap = argsLayout.getPredefinedNamedArgumentsMap();
             for (NamedArgument namedArg : namedArgs) {
                 int argIdx = predefNamedArgsMap.get(namedArg.name);
                 if (argIdx != -1) {
@@ -149,20 +119,9 @@ public class _CallableUtils {
                 } else {
                     if (namedVarargsHash == null) {
                         if (namedVarargsArgumentIndex == -1) {
-                            checkSupportsAnyParameters(callable, argsLayout, calledAsFunction);
-                            Collection<String> validNames = predefNamedArgsMap.getKeys();
                             throw new TemplateException(env,
-                                    validNames == null || validNames.isEmpty()
-                                            ? getNamedArgumentsNotSupportedMessage(
-                                                    callable, namedArg, calledAsFunction)
-                                            : new Object[] {
-                                                    getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
-                                                    "This ", getCallableTypeName(callable, calledAsFunction),
-                                                    " has no parameter that's passed by name and is called ",
-                                                    new _DelayedJQuote(namedArg.name),
-                                                    ". The supported parameter names are:\n",
-                                                    new _DelayedJQuotedListing(validNames)
-                                            });
+                                    newNamedArgumentDoesNotArgLayoutErrorDesc(
+                                            argsLayout, namedArg.name, callable, calledAsFunction));
                         }
 
                         namedVarargsHash = new NativeHashEx();
@@ -177,13 +136,88 @@ public class _CallableUtils {
         return execArgs;
     }
 
+    static _ErrorDescriptionBuilder newPositionalArgDoesNotFitArgLayoutErrorDesc(
+            ArgumentArrayLayout argsLayout, TemplateCallableModel callable, boolean calledAsFunction) {
+        _ErrorDescriptionBuilder noParamsED = checkSupportsAnyParameters(callable, argsLayout, calledAsFunction);
+        if (noParamsED != null) {
+            return noParamsED;
+        }
+        
+        List<String> validPredefNames;
+        _ErrorDescriptionBuilder errorDesc = new _ErrorDescriptionBuilder(
+                getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                "This ", getCallableTypeName(callable, calledAsFunction),
+                " ",
+                (argsLayout.getPredefinedPositionalArgumentCount() != 0
+                        ? new Object[]{ "can only have ", argsLayout.getPredefinedPositionalArgumentCount() }
+                        : "can't have"
+                ),
+                " arguments passed by position, but the invocation tries to pass in more.",
+                (!argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported() ?
+                        new Object[] {
+                                " Try to pass arguments by name (as in ",
+                                (callable instanceof TemplateDirectiveModel
+                                        ? "<@example x=1 y=2 />"
+                                        : "example(x=1, y=2)"),
+                                ")",
+                                (!(validPredefNames = argsLayout.getPredefinedNamedArgumentsMap().getKeys())
+                                        .isEmpty()
+                                        ? new Object[] {
+                                                " The supported parameter names are: ",
+                                                new _DelayedJQuotedListing(validPredefNames)
+                                        }
+                                        : _CollectionUtils.EMPTY_OBJECT_ARRAY)}
+                        : "")
+        );
+        if (callable instanceof Environment.TemplateLanguageDirective
+                && !argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported()) {
+            errorDesc.tip("You can pass a parameter by position (i.e., without specifying its name, as you"
+                    + " have tried now) when the directuve (the macro) has defined that parameter to be a "
+                    + "positional parameter. See in the documentation how, and when that's a good practice.");
+        }
+        return errorDesc;
+    }
+    
+    static _ErrorDescriptionBuilder newNamedArgumentDoesNotArgLayoutErrorDesc(ArgumentArrayLayout argsLayout,
+            String argName, TemplateCallableModel callable, boolean calledAsFunction) {
+        _ErrorDescriptionBuilder noParamsED = checkSupportsAnyParameters(callable, argsLayout, calledAsFunction);
+        if (noParamsED != null) {
+            return noParamsED;
+        }
+        
+        Collection<String> validNames = argsLayout.getPredefinedNamedArgumentsMap().getKeys();
+        _ErrorDescriptionBuilder errorDesc = new _ErrorDescriptionBuilder(
+                validNames == null || validNames.isEmpty()
+                ? getNamedArgumentsNotSupportedMessage(
+                        callable, argName, calledAsFunction)
+                : new Object[] {
+                        getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                        "This ", getCallableTypeName(callable, calledAsFunction),
+                        " has no parameter that's passed by name and is called ",
+                        new _DelayedJQuote(argName),
+                        ". The supported parameter names are:\n",
+                        new _DelayedJQuotedListing(validNames)
+                });
+        return errorDesc;
+    }
+    
+    static private _ErrorDescriptionBuilder checkSupportsAnyParameters(
+            TemplateCallableModel callable, ArgumentArrayLayout argsLayout, boolean calledAsFunction)  {
+        return argsLayout.getTotalLength() == 0
+                ? new _ErrorDescriptionBuilder(
+                            getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                            "This ", getCallableTypeName(callable, calledAsFunction),
+                            " doesn't support any parameters.")
+                : null;
+    }
+
     private static Object[] getNamedArgumentsNotSupportedMessage(TemplateCallableModel callable,
-            _CallableUtils.NamedArgument namedArg, boolean calledAsFunction) {
+            String argName, boolean calledAsFunction) {
         return new Object[] {
                 getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
                 "This ", getCallableTypeName(callable, calledAsFunction),
                 " can't have arguments that are passed by name (like ",
-                new _DelayedJQuote(namedArg.name), "). Try to pass arguments by position "
+                new _DelayedJQuote(argName), "). Try to pass arguments by position "
                 + "(i.e, without name, as in ",
                 (callable instanceof TemplateDirectiveModel
                         ? "<@example arg1, arg2, arg3 />"
@@ -192,17 +226,6 @@ public class _CallableUtils {
         };
     }
 
-    static private void checkSupportsAnyParameters(
-            TemplateCallableModel callable, ArgumentArrayLayout argsLayout, boolean calledAsFunction)
-            throws TemplateException {
-        if (argsLayout.getTotalLength() == 0) {
-            throw new TemplateException(
-                    getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
-                    "This ", getCallableTypeName(callable, calledAsFunction),
-                    " doesn't support any parameters.");
-        }
-    }
-
     /**
      * Something like {@code "When calling function \"lib.f3ah:foo\": " or "When calling ?leftPad: "}
      */

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java
index e15374b..c94e369 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java
@@ -19,7 +19,6 @@
 package org.apache.freemarker.core;
 
 import java.util.ArrayList;
-import java.util.Enumeration;
 import java.util.List;
 
 /**
@@ -42,7 +41,7 @@ public final class _Debug {
         // TODO: Ensure there always is a parent by making sure
         // that the root element in the template is always a ASTImplicitParent
         // Also make sure it doesn't conflict with anyone's code.
-        parent.setChildAt(parent.getIndex(te), db);
+        parent.setChildAt(indexOfChild(parent, te), db);
     }
 
     public static void removeDebugBreak(Template t, int line) {
@@ -62,7 +61,16 @@ public final class _Debug {
             return;
         }
         ASTElement parent = db.getParent();
-        parent.setChildAt(parent.getIndex(db), db.getChild(0));
+        parent.setChildAt(indexOfChild(parent, db), db.fastGetChild(0));
+    }
+    
+    private static final int indexOfChild(ASTElement parent, ASTElement node) {
+        for (int i = 0; i < parent.getChildCount(); i++) {
+            if (parent.getChild(i) == node) {
+                return i;
+            }
+        }
+        return -1;
     }
 
     private static ASTElement findTemplateElement(ASTElement te, int line) {
@@ -70,9 +78,8 @@ public final class _Debug {
             return null;
         }
         // Find the narrowest match
-        List childMatches = new ArrayList();
-        for (Enumeration children = te.children(); children.hasMoreElements(); ) {
-            ASTElement child = (ASTElement) children.nextElement();
+        List<ASTElement> childMatches = new ArrayList<>();
+        for (ASTElement child : te.getChildren()) {
             ASTElement childmatch = findTemplateElement(child, line);
             if (childmatch != null) {
                 childMatches.add(childmatch);
@@ -81,7 +88,7 @@ public final class _Debug {
         //find a match that exactly matches the begin/end line
         ASTElement bestMatch = null;
         for (int i = 0; i < childMatches.size(); i++) {
-            ASTElement e = (ASTElement) childMatches.get(i);
+            ASTElement e = childMatches.get(i);
 
             if ( bestMatch == null ) {
                 bestMatch = e;
@@ -110,9 +117,9 @@ public final class _Debug {
     private static void removeDebugBreaks(ASTElement te) {
         int count = te.getChildCount();
         for (int i = 0; i < count; ++i) {
-            ASTElement child = te.getChild(i);
+            ASTElement child = te.fastGetChild(i);
             while (child instanceof ASTDebugBreak) {
-                ASTElement dbchild = child.getChild(0);
+                ASTElement dbchild = child.fastGetChild(0);
                 te.setChildAt(i, dbchild);
                 child = dbchild;
             }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 7d96e3d..f00da1a 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
@@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory;
  * Note that this class isn't serializable, thus the containing exception should render the message before it's
  * serialized.
  */
+//TODO [FM3] will be public
 public class _ErrorDescriptionBuilder {
 
     private static final Logger LOG = LoggerFactory.getLogger(_ErrorDescriptionBuilder.class);
@@ -77,7 +78,7 @@ public class _ErrorDescriptionBuilder {
                 Blaming blaming = findBlaming(parentElement, blamed, 0);
                 if (blaming != null) {
                     sb.append("For ");
-                    String nss = blaming.blamer.getASTNodeDescriptor();
+                    String nss = blaming.blamer.getLabelWithoutParameters();
                     char q = nss.indexOf('"') == -1 ? '\"' : '`';
                     sb.append(q).append(nss).append(q);
                     sb.append(" ").append(blaming.roleOfblamed).append(": ");
@@ -117,7 +118,8 @@ public class _ErrorDescriptionBuilder {
             }
             
             sb.append("  [");
-            sb.append(blamed.getStartLocation());
+            sb.append(MessageUtils.formatLocationForEvaluationError(
+                    blamed.getTemplate(), blamed.getBeginLine(), blamed.getEndLine()));
             sb.append(']');
             
             

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 525ed82..150bb6e 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
@@ -21,6 +21,8 @@ package org.apache.freemarker.core;
 
 import static org.apache.freemarker.core.MessageUtils.*;
 
+import java.io.IOException;
+import java.io.Writer;
 import java.util.Date;
 
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
@@ -33,6 +35,7 @@ import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
+import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._ClassUtils;
 import org.apache.freemarker.core.valueformat.TemplateDateFormat;
@@ -338,6 +341,9 @@ public class _EvalUtils {
      * 
      * @param seqTip
      *            Tip to display if the value type is not coercable, but it's iterable.
+     * @param exp
+     *            The expression that was evaluated to {@code tm}. This can be {@code null}, however that may results
+     *            in poor quality error messages.
      * 
      * @return Never {@code null}
      */
@@ -540,5 +546,29 @@ public class _EvalUtils {
                 ? env.getArithmeticEngine()
                 : tObj.getTemplate().getParsingConfiguration().getArithmeticEngine();
     }
+
+    public static void printTemplateMarkupOutputModel(final TemplateMarkupOutputModel mo, OutputFormat outputFormat,
+            final Writer out, ASTExpression exp) throws TemplateException, IOException {
+        final MarkupOutputFormat moOF = mo.getOutputFormat();
+        // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!
+        if (moOF != outputFormat && !outputFormat.isOutputFormatMixingAllowed()) {
+            final String srcPlainText;
+            // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!
+            srcPlainText = moOF.getSourcePlainText(mo);
+            if (srcPlainText == null) {
+                throw new TemplateException(exp,
+                        "The value to print is in ", new _DelayedToString(moOF),
+                        " format, which differs from the current output format, ",
+                        new _DelayedToString(outputFormat), ". Format conversion wasn't possible.");
+            }
+            if (outputFormat instanceof MarkupOutputFormat) {
+                ((MarkupOutputFormat) outputFormat).output(srcPlainText, out);
+            } else {
+                out.write(srcPlainText);
+            }
+        } else {
+            moOF.output(mo, out);
+        }
+    }
     
 }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java
index b653f7f..95c06bc 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java
@@ -51,7 +51,7 @@ public class _ArrayAdapterList<E> extends AbstractList<E> {
 
     @Override
     public Iterator<E> iterator() {
-        return new _ArrayIterator(array);
+        return new _ArrayIterator<E>(array);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayEnumeration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayEnumeration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayEnumeration.java
deleted file mode 100644
index 1c82658..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayEnumeration.java
+++ /dev/null
@@ -1,51 +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.util;
-
-import java.util.Enumeration;
-import java.util.NoSuchElementException;
-
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
-public class _ArrayEnumeration implements Enumeration {
-
-    private final Object[] array;
-    private final int size;
-    private int nextIndex;
-
-    public _ArrayEnumeration(Object[] array, int size) {
-        this.array = array;
-        this.size = size;
-        nextIndex = 0;
-    }
-
-    @Override
-    public boolean hasMoreElements() {
-        return nextIndex < size;
-    }
-
-    @Override
-    public Object nextElement() {
-        if (nextIndex >= size) {
-            throw new NoSuchElementException();
-        }
-        return array[nextIndex++];
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayIterator.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayIterator.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayIterator.java
index 7e02449..0ff9241 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayIterator.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayIterator.java
@@ -23,12 +23,12 @@ import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 /** Don't use this; used internally by FreeMarker, might changes without notice. */
-public class _ArrayIterator implements Iterator {
+public class _ArrayIterator<T> implements Iterator<T> {
 
-    private final Object[] array;
+    private final T[] array;
     private int nextIndex;
 
-    public _ArrayIterator(Object[] array) {
+    public _ArrayIterator(T[] array) {
         this.array = array;
         nextIndex = 0;
     }
@@ -39,7 +39,7 @@ public class _ArrayIterator implements Iterator {
     }
 
     @Override
-    public Object next() {
+    public T next() {
         if (nextIndex >= array.length) {
             throw new NoSuchElementException();
         }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/util/_NullArgumentException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_NullArgumentException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_NullArgumentException.java
index 4e7f777..d9cdad9 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_NullArgumentException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_NullArgumentException.java
@@ -22,6 +22,7 @@ package org.apache.freemarker.core.util;
 /**
  * Indicates that an argument that must be non-{@code null} was {@code null}. 
  */
+@SuppressWarnings("serial")
 public class _NullArgumentException extends IllegalArgumentException {
 
     public _NullArgumentException() {
@@ -46,6 +47,7 @@ public class _NullArgumentException extends IllegalArgumentException {
     }
 
     /**
+     * Convenience method to protect against a {@code null} argument.
      */
     public static void check(Object argumentValue) {
         if (argumentValue == null) {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 aff3750..11e7ed2 100644
--- a/freemarker-core/src/main/javacc/FTL.jj
+++ b/freemarker-core/src/main/javacc/FTL.jj
@@ -30,6 +30,7 @@ PARSER_BEGIN(FMParser)
 package org.apache.freemarker.core;
 
 import org.apache.freemarker.core.*;
+import org.apache.freemarker.core.Dialect.ConfiguredDialect;
 import org.apache.freemarker.core.outputformat.*;
 import org.apache.freemarker.core.outputformat.impl.*;
 import org.apache.freemarker.core.model.*;
@@ -84,6 +85,7 @@ public class FMParser {
     private boolean autoEscaping;
     private ParsingConfiguration pCfg;
     DefaultTemplateLanguage templateLanguage;
+    ConfiguredDialect configuredDialect;
     private InputStream streamToUnmarkWhenEncEstabd;
 
     /** Keeps track of #list nesting. */
@@ -113,6 +115,7 @@ public class FMParser {
     private StringToIndexMap.Entry[] topNestedContentParamNamesBuffer;
     private int topNestedContentParamNamesLength;
 
+
     FMParser(Template template, Reader reader,
             ParsingConfiguration pCfg,
             InputStream streamToUnmarkWhenEncEstabd) {
@@ -143,6 +146,7 @@ public class FMParser {
         token_source.incompatibleImprovements = incompatibleImprovements;
         this.incompatibleImprovements = incompatibleImprovements;
 
+        // We know that it's a DefaultTemplateLanguage, as this is the parser of the DefaultTemplateLanguage. 
         templateLanguage = (DefaultTemplateLanguage) pCfg.getTemplateLanguage();
 
         outputFormat = pCfg.getOutputFormat();
@@ -165,6 +169,8 @@ public class FMParser {
         token_source.interpolationSyntax = templateLanguage.getInterpolationSyntax();
 
         this.stripWhitespace = pCfg.getWhitespaceStripping();
+
+        configuredDialect = template.getConfiguration().getConfiguredDialect(templateLanguage.getDialect());
     }
     
     void setupStringLiteralMode(FMParserTokenManager parentTokenSource, OutputFormat outputFormat) {
@@ -3315,7 +3321,7 @@ ASTElement DynamicTopLevelCall() :
         )
         |
         (
-            // This could be part of the positional paramter choice, but we can give better error messages this way.
+            // This could be part of the positional parameter choice, but we can give better error messages this way.
             t = <COMMA>
             {
                 if (prevChoiceComma != null) {


[2/3] freemarker git commit: Some ground work to support dialects later. Added classes like Dialect and StaticallyLinkedNamespaceEntry, but these are non-public for now, and just drafts, and aren't actually used by the templates. The final ones will need

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSep.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSep.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSep.java
index 9482f9c..1624732 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSep.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSep.java
@@ -32,12 +32,12 @@ class ASTDirSep extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         final IterationContext iterCtx = ASTDirList.findEnclosingIterationContext(env, null);
         if (iterCtx == null) {
             // The parser should prevent this situation
             throw new TemplateException(env,
-                    getASTNodeDescriptor(), " without iteration in context");
+                    getLabelWithoutParameters(), " without iteration in context");
         }
         
         if (iterCtx.hasNext()) {
@@ -52,22 +52,22 @@ class ASTDirSep extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         if (canonical) {
             sb.append('>');
             sb.append(getChildrenCanonicalForm());
             sb.append("</");
-            sb.append(getASTNodeDescriptor());
+            sb.append(getLabelWithoutParameters());
             sb.append('>');
         }
         return sb.toString();
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#sep";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
index 953fa7f..9d76aef 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
@@ -104,7 +104,7 @@ final class ASTDirSetting extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException {
+    ASTElement[] execute(Environment env) throws TemplateException {
         TemplateModel mval = value.eval(env);
         String strval;
         if (mval instanceof TemplateStringModel) {
@@ -125,10 +125,10 @@ final class ASTDirSetting extends ASTDirective {
     }
     
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         sb.append(' ');
         sb.append(_StringUtils.toFTLTopLevelTragetIdentifier(key));
         sb.append('=');
@@ -138,7 +138,7 @@ final class ASTDirSetting extends ASTDirective {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#setting";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirStop.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirStop.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirStop.java
index bef9654..dcee928 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirStop.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirStop.java
@@ -31,7 +31,7 @@ final class ASTDirStop extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException {
+    ASTElement[] execute(Environment env) throws TemplateException {
         if (exp == null) {
             throw new StopException(env);
         }
@@ -39,10 +39,10 @@ final class ASTDirStop extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         if (exp != null) {
             sb.append(' ');
             sb.append(exp.getCanonicalForm());
@@ -52,7 +52,7 @@ final class ASTDirStop extends ASTDirective {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#stop";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java
index 296dd48..909e88d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java
@@ -39,7 +39,7 @@ final class ASTDirSwitch extends ASTDirective {
         int ignoredCnt = ignoredSectionBeforeFirstCase != null ? ignoredSectionBeforeFirstCase.getChildCount() : 0;
         setChildBufferCapacity(ignoredCnt + 4);
         for (int i = 0; i < ignoredCnt; i++) {
-            addChild(ignoredSectionBeforeFirstCase.getChild(i));
+            addChild(ignoredSectionBeforeFirstCase.fastGetChild(i));
         }
         firstCaseIndex = ignoredCnt; // Note that normally postParseCleanup will overwrite this
     }
@@ -52,13 +52,13 @@ final class ASTDirSwitch extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env)
+    ASTElement[] execute(Environment env)
         throws TemplateException, IOException {
         boolean processedCase = false;
         int ln = getChildCount();
         try {
             for (int i = firstCaseIndex; i < ln; i++) {
-                ASTDirCase cas = (ASTDirCase) getChild(i);
+                ASTDirCase cas = (ASTDirCase) fastGetChild(i);
                 boolean processCase = false;
 
                 // Fall through if a previous case tested true.
@@ -71,7 +71,7 @@ final class ASTDirSwitch extends ASTDirective {
                             _EvalUtils.CMP_OP_EQUALS, "case==", cas.condition, cas.condition, env);
                 }
                 if (processCase) {
-                    env.visit(cas);
+                    env.executeElement(cas);
                     processedCase = true;
                 }
             }
@@ -79,7 +79,7 @@ final class ASTDirSwitch extends ASTDirective {
             // If we didn't process any nestedElements, and we have a default,
             // process it.
             if (!processedCase && defaultCase != null) {
-                env.visit(defaultCase);
+                env.executeElement(defaultCase);
             }
         } catch (BreakOrContinueException br) {
             // #break was called
@@ -88,25 +88,25 @@ final class ASTDirSwitch extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder buf = new StringBuilder();
         if (canonical) buf.append('<');
-        buf.append(getASTNodeDescriptor());
+        buf.append(getLabelWithoutParameters());
         buf.append(' ');
         buf.append(searched.getCanonicalForm());
         if (canonical) {
             buf.append('>');
             int ln = getChildCount();
             for (int i = 0; i < ln; i++) {
-                buf.append(getChild(i).getCanonicalForm());
+                buf.append(fastGetChild(i).getCanonicalForm());
             }
-            buf.append("</").append(getASTNodeDescriptor()).append('>');
+            buf.append("</").append(getLabelWithoutParameters()).append('>');
         }
         return buf.toString();
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#switch";
     }
 
@@ -139,7 +139,7 @@ final class ASTDirSwitch extends ASTDirective {
         // The first #case might have shifted in the child array, so we have to find it again:
         int ln = getChildCount();
         int i = 0;
-        while (i < ln && !(getChild(i) instanceof ASTDirCase)) {
+        while (i < ln && !(fastGetChild(i) instanceof ASTDirCase)) {
             i++;
         }
         firstCaseIndex = i;

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirTOrRtOrLtOrNt.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirTOrRtOrLtOrNt.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirTOrRtOrLtOrNt.java
index 5b79e30..102d1c9 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirTOrRtOrLtOrNt.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirTOrRtOrLtOrNt.java
@@ -37,22 +37,22 @@ final class ASTDirTOrRtOrLtOrNt extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) {
+    ASTElement[] execute(Environment env) {
         // This instruction does nothing at render-time, only parse-time.
         return null;
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         if (canonical) sb.append("/>");
         return sb.toString();
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         if (left && right) {
             return "#t";
         } else if (left) {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirVisit.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirVisit.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirVisit.java
index d2a6e4d..181bb00 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirVisit.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirVisit.java
@@ -40,7 +40,7 @@ final class ASTDirVisit extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws IOException, TemplateException {
+    ASTElement[] execute(Environment env) throws IOException, TemplateException {
         TemplateModel node = targetNode.eval(env);
         if (!(node instanceof TemplateNodeModel)) {
             throw MessageUtils.newUnexpectedOperandTypeException(targetNode, node, TemplateNodeModel.class, env);
@@ -72,10 +72,10 @@ final class ASTDirVisit extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         sb.append(' ');
         sb.append(targetNode.getCanonicalForm());
         if (namespaces != null) {
@@ -87,7 +87,7 @@ final class ASTDirVisit extends ASTDirective {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#visit";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java
index 6015376..40cdc90 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java
@@ -22,9 +22,13 @@ import java.util.Collections;
 import java.util.Set;
 import java.util.TreeSet;
 
+import org.apache.freemarker.core.util.StringToIndexMap;
+
 /**
  * AST directive node superclass.
+ * Concrete instances are normally created using {@link StaticallyLinkedNamespaceEntry#getDirectiveCallNodeFactory()}/
  */
+// TODO [FM3] will be public
 abstract class ASTDirective extends ASTElement {
 
     static final Set<String> BUILT_IN_DIRECTIVE_NAMES;
@@ -75,5 +79,64 @@ abstract class ASTDirective extends ASTElement {
         BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(names);
 
     }
+    
+    /**
+     * Called by the parser to when it has parsed a parameter value expression for a positional parameter.
+     * 
+     * <p>
+     * It's guaranteed that either {@link #setPositionalArgument(int, ASTExpression)} or
+     * {@link #setNamedArgument(String, ASTExpression)} is called for each parameter in the source code exactly once, in
+     * the order as the corresponding parameters occur in the source code. (While {@link DefaultTemplateLanguage}
+     * guarantees that positional parameters are before the named parameters, other {@link TemplateLanguage}-s may don't
+     * have such restriction.)
+     * 
+     * @param position
+     *            The 0-based position of the parameter among the positional parameters.
+     */
+    public void setPositionalArgument(int position, ASTExpression valueExp)
+            throws StaticLinkingCheckException {
+        // TODO [FM3][FREEMARKER-99] Will be abstract
+    }
+
+    /**
+     * Called by the parser when it has parsed a parameter expression.
+     * 
+     * <p>See guarantees regarding the call order and the number of calls in the description of
+     * {@link #setPositionalArgument(int, ASTExpression)}.
+     */
+    public void setNamedArgument(String name, ASTExpression valueExp)
+            throws StaticLinkingCheckException {
+        // TODO [FM3][FREEMARKER-99] Will be abstract
+    }
+    
+    /**
+     * Called by the parser when it has already passed in all arguments (via
+     * {@link #setPositionalArgument(int, ASTExpression)} and such). This allows the directive to check if all required
+     * arguments were provided, and some more. It also sets the nested content parameter names (like {@code "i", "j"} in
+     * {@code <#list m as i, j>}). (These two operations are packed into this method together as optimization, though
+     * admittedly the gain is very small.)
+     * 
+     * @param nestedContentParamNames
+     *            Will be {@code null} exactly if there are 0 nested content parameters.
+     */
+    public void checkArgumentsAndSetNestedContentParameters(StringToIndexMap nestedContentParamNames)
+            throws StaticLinkingCheckException {
+        // TODO [FM3][FREEMARKER-99] Will be abstract
+    }
+
+    /**
+     * Tells if this directive can have nested content; the parser may need this information.
+     */
+    public boolean isNestedContentSupported() {
+        // TODO [FM3][FREEMARKER-99] Will be abstract
+        return true;
+    }
+    
+    /**
+     * @return {@code null} if there was no nested content parameter
+     */
+    public StringToIndexMap getNestedContentParamNames() {
+        return null;
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
index dd8394f..a84460f 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
@@ -83,7 +83,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace  {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         TemplateCallableModel callableValue;
         TemplateDirectiveModel directive;
         TemplateFunctionModel function;
@@ -157,7 +157,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace  {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
         sb.append('@');
@@ -211,7 +211,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace  {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "@";
     }
 
@@ -286,7 +286,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace  {
     @Override
     public boolean hasNestedContent() {
         int childCount = getChildCount();
-        return childCount != 0 && (childCount > 1 || !(getChild(0) instanceof ASTThreadInterruptionCheck));
+        return childCount != 0 && (childCount > 1 || !(fastGetChild(0) instanceof ASTThreadInterruptionCheck));
     }
 
     @Override
@@ -301,16 +301,16 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace  {
         int nestedContentParamValuesSize = nestedContentArgs != null ? nestedContentArgs.length : 0;
         if (nestedContentParamValuesSize != nestedContentParamNamesSize) {
             throw new TemplateException(env,
-                    "The invocation declares ", (nestedContentParamNamesSize != 0 ? nestedContentParamNamesSize : "no"),
-                    " nested content parameter(s)",
-                    (nestedContentParamNamesSize != 0
-                            ? new Object[] { " (", new _DelayedJQuotedListing(nestedContentParamNames.getKeys()), ")", }
-                            : ""),
-                    ", but the called object intends to pass ",
-                    nestedContentParamValuesSize, " parameters. You need to declare ", nestedContentParamValuesSize,
-                    " nested content parameters.");
+                    MessageUtils.newBadNumberOfNestedContentParameterPassedMessage(
+                            nestedContentParamNames, nestedContentParamValuesSize));
+        }
+        Writer prevOut = env.getOut();
+        try {
+            env.setOut(out);
+            env.executeNestedContent(this, nestedContentParamNames, nestedContentArgs);
+        } finally {
+            env.setOut(prevOut);
         }
-        env.visit(getChildBuffer(), nestedContentParamNames, nestedContentArgs, out);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 5bc68e0..5309177 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
@@ -21,14 +21,14 @@ package org.apache.freemarker.core;
 
 import java.io.IOException;
 import java.util.Collections;
-import java.util.Enumeration;
-
-import org.apache.freemarker.core.util._ArrayEnumeration;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
 
 /**
  * AST non-expression node superclass: Superclass of directive calls, interpolations, static text, top-level comments,
  * or other such non-expression node in the parsed template.
  */
+//TODO [FM3] will be public
 abstract class ASTElement extends ASTNode {
 
     private static final int INITIAL_CHILD_BUFFER_CAPACITY = 6;
@@ -51,10 +51,13 @@ abstract class ASTElement extends ASTNode {
      * The index of the element in the parent's {@link #childBuffer} array.
      */
     private int index;
+    
+    // Package visible constructor to prevent instantiating outside FreeMarker 
+    ASTElement() { }
 
     /**
      * Executes this {@link ASTElement}. Usually should not be called directly, but through
-     * {@link Environment#visit(ASTElement)} or a similar {@link Environment} method.
+     * {@link Environment#executeElement(ASTElement)} or a similar {@link Environment} method.
      *
      * @param env
      *            The runtime environment
@@ -64,42 +67,32 @@ abstract class ASTElement extends ASTNode {
      *         executing them inside this method is a trick used for decreasing stack usage when there's nothing to do
      *         after the children was processed anyway.
      */
-    abstract ASTElement[] accept(Environment env) throws TemplateException, IOException;
+    abstract ASTElement[] execute(Environment env) throws TemplateException, IOException;
 
     /**
-     * One-line description of the element, that contain all the information that is used in {@link #getCanonicalForm()}
-     * , except the nested content (elements) of the element. The expressions inside the element (the parameters) has to
-     * be shown. Meant to be used for stack traces, also for tree views that don't go down to the expression-level.
-     * There are no backward-compatibility guarantees regarding the format used ATM, but it must be regular enough to be
-     * machine-parseable, and it must contain all information necessary for restoring an AST equivalent to the original.
-     * 
-     * This final implementation calls {@link #dump(boolean) dump(false)}.
+     * Single line description of the element, which contain information about what kind of element it is, what are its
+     * parameters, but doesn't contain the child nodes. Meant to be used for stack traces, also for tree views (that
+     * don't want to show the parameters as spearate nodes). There are no backward-compatibility guarantees regarding
+     * the format used at the moment.
      * 
+     * @see #getLabelWithoutParameters()
      * @see #getCanonicalForm()
-     * @see #getASTNodeDescriptor()
      */
-    public final String getDescription() {
+    public final String getLabelWithParameters() {
         return dump(false);
     }
 
-    /**
-     * This final implementation calls {@link #dump(boolean) dump(false)}.
-     */
     @Override
     public final String getCanonicalForm() {
         return dump(true);
     }
 
     final String getChildrenCanonicalForm() {
-        return getChildrenCanonicalForm(childBuffer);
-    }
-    
-    static String getChildrenCanonicalForm(ASTElement[] children) {
-        if (children == null) {
+        if (childBuffer == null) {
             return "";
         }
         StringBuilder sb = new StringBuilder();
-        for (ASTElement child : children) {
+        for (ASTElement child : childBuffer) {
             if (child == null) {
                 break;
             }
@@ -107,7 +100,7 @@ abstract class ASTElement extends ASTNode {
         }
         return sb.toString();
     }
-
+    
     /**
      * Tells if the element should show up in error stack traces. Note that this will be ignored for the top (current)
      * element of a stack trace, as that's always shown.
@@ -125,53 +118,51 @@ abstract class ASTElement extends ASTNode {
     abstract boolean isNestedBlockRepeater();
 
     /**
-     * Brings the implementation of {@link #getCanonicalForm()} and {@link #getDescription()} to a single place. Don't
+     * Brings the implementation of {@link #getCanonicalForm()} and {@link #getLabelWithParameters()} to a single place. Don't
      * call those methods in method on {@code this}, because that will result in infinite recursion!
      * 
      * @param canonical
      *            if {@code true}, it calculates the return value of {@link #getCanonicalForm()}, otherwise of
-     *            {@link #getDescription()}.
+     *            {@link #getLabelWithParameters()}.
      */
-    abstract protected String dump(boolean canonical);
-
-    // Methods to implement TemplateNodeModel
-
-    public String getNodeName() {
-        String className = getClass().getName();
-        int shortNameOffset = className.lastIndexOf('.') + 1;
-        return className.substring(shortNameOffset);
-    }
-
-    // Methods so that we can implement the Swing TreeNode API.
-
-    public boolean isLeaf() {
-        return childCount == 0;
-    }
+    abstract String dump(boolean canonical);
 
-    public int getIndex(ASTElement node) {
-        for (int i = 0; i < childCount; i++) {
-            if (childBuffer[i].equals(node)) {
-                return i;
+    /**
+     * Note: For element with {@code #nested}, this will hide the {@code #nested} when that's an
+     * {@link ASTImplicitParent}.
+     */
+    public final Iterable<ASTElement> getChildren() {
+        return childBuffer != null ? new Iterable<ASTElement>() {
+            @Override
+            public Iterator<ASTElement> iterator() {
+                return new _ChildIterator();
             }
-        }
-        return -1;
+        } : Collections.<ASTElement>emptyList();
     }
 
-    public int getChildCount() {
+    public final int getChildCount() {
         return childCount;
     }
 
     /**
-     * Note: For element with {@code #nestedBlock}, this will hide the {@code #nestedBlock} when that's a
-     * {@link ASTImplicitParent}.
+     * Return the child node at the given index.
+     * 
+     * @throws IndexOutOfBoundsException
+     *             if the index is out of range, such as not less than {@link #getChildCount()}.
      */
-    public Enumeration children() {
-        return childBuffer != null
-                ? new _ArrayEnumeration(childBuffer, childCount)
-                : Collections.enumeration(Collections.EMPTY_LIST);
+    public final ASTElement getChild(int index) {
+        if (index >= childCount) {
+            throw new IndexOutOfBoundsException("Index " + index + " is out of bounds. There are " + childCount
+                    + " child node(s).");
+        }
+        return fastGetChild(index);
+    }
+
+    final ASTElement fastGetChild(int index) {
+        return childBuffer[index];
     }
 
-    public void setChildAt(int index, ASTElement element) {
+    final void setChildAt(int index, ASTElement element) {
         if (index < childCount && index >= 0) {
             childBuffer[index] = element;
             element.index = index;
@@ -230,11 +221,7 @@ abstract class ASTElement extends ASTNode {
         lChildBuffer[index] = nestedElement;
         childCount = lChildCount + 1;
     }
-
-    final ASTElement getChild(int index) {
-        return childBuffer[index];
-    }
-
+    
     /**
      * @return Array containing 1 or more nested elements with optional trailing {@code null}-s, or is {@code null}
      *         exactly if there are no nested elements.
@@ -386,7 +373,8 @@ abstract class ASTElement extends ASTNode {
 
     private ASTElement getFirstLeaf() {
         ASTElement te = this;
-        while (!te.isLeaf() && !(te instanceof ASTDirMacroOrFunction) && !(te instanceof ASTDirCapturingAssignment)) {
+        while (te.getChildCount() != 0  && !(te instanceof ASTDirMacroOrFunction)
+                && !(te instanceof ASTDirCapturingAssignment)) {
             // A macro or macro invocation is treated as a leaf here for special reasons
             te = te.getFirstChild();
         }
@@ -395,7 +383,8 @@ abstract class ASTElement extends ASTNode {
 
     private ASTElement getLastLeaf() {
         ASTElement te = this;
-        while (!te.isLeaf() && !(te instanceof ASTDirMacroOrFunction) && !(te instanceof ASTDirCapturingAssignment)) {
+        while (te.getChildCount() != 0 && !(te instanceof ASTDirMacroOrFunction)
+                && !(te instanceof ASTDirCapturingAssignment)) {
             // A macro or macro invocation is treated as a leaf here for special reasons
             te = te.getLastChild();
         }
@@ -435,4 +424,29 @@ abstract class ASTElement extends ASTNode {
     boolean heedsTrailingWhitespace() {
         return false;
     }
+    
+    private class _ChildIterator implements Iterator<ASTElement> {
+
+        private int nextIndex;
+
+        @Override
+        public boolean hasNext() {
+            return nextIndex < childCount;
+        }
+
+        @Override
+        public ASTElement next() {
+            if (nextIndex >= childCount) {
+                throw new NoSuchElementException();
+            }
+            return childBuffer[nextIndex++];
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
index 38ed36d..040235b 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
@@ -152,7 +152,7 @@ final class ASTExpAddOrConcat extends ASTExpression {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	return new ASTExpAddOrConcat(
     	left.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),
@@ -165,7 +165,7 @@ final class ASTExpAddOrConcat extends ASTExpression {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "+";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAnd.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAnd.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAnd.java
index ec60b26..e6958cf 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAnd.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAnd.java
@@ -45,7 +45,7 @@ final class ASTExpAnd extends ASTExpBoolean {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "&&";
     }
     
@@ -55,7 +55,7 @@ final class ASTExpAnd extends ASTExpBoolean {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	return new ASTExpAnd(
     	        lho.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpArithmetic.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpArithmetic.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpArithmetic.java
index 632baca..579a96b 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpArithmetic.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpArithmetic.java
@@ -85,7 +85,7 @@ final class ASTExpArithmetic extends ASTExpression {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return String.valueOf(getOperatorSymbol(operator));
     }
 
@@ -99,7 +99,7 @@ final class ASTExpArithmetic extends ASTExpression {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	return new ASTExpArithmetic(
     	        lho.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBooleanLiteral.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBooleanLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBooleanLiteral.java
index 0cb9c50..bb2bada 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBooleanLiteral.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBooleanLiteral.java
@@ -48,16 +48,11 @@ final class ASTExpBooleanLiteral extends ASTExpression {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return getCanonicalForm();
     }
     
     @Override
-    public String toString() {
-        return val ? TemplateBooleanFormat.C_TRUE : TemplateBooleanFormat.C_FALSE;
-    }
-
-    @Override
     TemplateModel _eval(Environment env) {
         return val ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
     }
@@ -68,7 +63,7 @@ final class ASTExpBooleanLiteral extends ASTExpression {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	return new ASTExpBooleanLiteral(val);
     }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
index 3ff5c0a..6324cf4 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
@@ -384,7 +384,7 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "?" + key;
     }
 
@@ -394,7 +394,7 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable {
     }
     
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	try {
 	    	ASTExpBuiltIn clone = (ASTExpBuiltIn) clone();

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 08b9218..42fe3f1 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
@@ -220,17 +220,12 @@ final class ASTExpBuiltInVariable extends ASTExpression {
     }
 
     @Override
-    public String toString() {
-        return "." + name;
-    }
-
-    @Override
     public String getCanonicalForm() {
         return "." + name;
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return getCanonicalForm();
     }
 
@@ -240,7 +235,7 @@ final class ASTExpBuiltInVariable extends ASTExpression {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
         return this;
     }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpComparison.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpComparison.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpComparison.java
index c7d92f0..9b6beac 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpComparison.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpComparison.java
@@ -68,7 +68,7 @@ final class ASTExpComparison extends ASTExpBoolean {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return opString;
     }
 
@@ -78,7 +78,7 @@ final class ASTExpComparison extends ASTExpBoolean {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	return new ASTExpComparison(
     	        left.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
index 04c1afc..0aac874 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
@@ -169,7 +169,7 @@ class ASTExpDefault extends ASTExpression {
 	}
 
 	@Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
+    ASTExpression deepCloneWithIdentifierReplaced_inner(String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
         return new ASTExpDefault(
                 lho.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),
                 rho != null
@@ -186,7 +186,7 @@ class ASTExpDefault extends ASTExpression {
 	}
 	
 	@Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "...!...";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDot.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDot.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDot.java
index 1e67fad..a041909 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDot.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDot.java
@@ -46,11 +46,11 @@ final class ASTExpDot extends ASTExpression {
 
     @Override
     public String getCanonicalForm() {
-        return target.getCanonicalForm() + getASTNodeDescriptor() + _StringUtils.toFTLIdentifierReferenceAfterDot(key);
+        return target.getCanonicalForm() + getLabelWithoutParameters() + _StringUtils.toFTLIdentifierReferenceAfterDot(key);
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return ".";
     }
     
@@ -60,7 +60,7 @@ final class ASTExpDot extends ASTExpression {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	return new ASTExpDot(
     	        target.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java
index e14e009..29e902a 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java
@@ -237,7 +237,7 @@ final class ASTExpDynamicKeyName extends ASTExpression {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "...[...]";
     }
     
@@ -262,7 +262,7 @@ final class ASTExpDynamicKeyName extends ASTExpression {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	return new ASTExpDynamicKeyName(
     	        target.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpExists.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpExists.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpExists.java
index 24f6e19..ce96532 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpExists.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpExists.java
@@ -58,18 +58,18 @@ class ASTExpExists extends ASTExpression {
 	}
 
 	@Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
+    ASTExpression deepCloneWithIdentifierReplaced_inner(String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
 		return new ASTExpExists(
 		        exp.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState));
 	}
 
 	@Override
     public String getCanonicalForm() {
-		return exp.getCanonicalForm() + getASTNodeDescriptor();
+		return exp.getCanonicalForm() + getLabelWithoutParameters();
 	}
 	
 	@Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "??";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpFunctionCall.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpFunctionCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpFunctionCall.java
index 07985c8..8621085 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpFunctionCall.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpFunctionCall.java
@@ -36,7 +36,7 @@ import org.apache.freemarker.core.util._StringUtils;
 /**
  * AST expression node: {@code exp(args)}.
  */
-final class ASTExpFunctionCall extends ASTExpression implements CallPlace {
+public final class ASTExpFunctionCall extends ASTExpression implements CallPlace {
 
     private final ASTExpression functionExp;
     private final ASTExpression[] positionalArgs;
@@ -110,7 +110,7 @@ final class ASTExpFunctionCall extends ASTExpression implements CallPlace {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "...(...)";
     }
     
@@ -124,7 +124,7 @@ final class ASTExpFunctionCall extends ASTExpression implements CallPlace {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
         ASTExpression[] positionalArgsClone;
         if (positionalArgs != null) {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
index 6c337dd..e2df12f 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
@@ -68,7 +68,7 @@ final class ASTExpHashLiteral extends ASTExpression {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "{...}";
     }
 
@@ -89,7 +89,7 @@ final class ASTExpHashLiteral extends ASTExpression {
 
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
 		ArrayList clonedKeys = (ArrayList) keys.clone();
 		for (ListIterator iter = clonedKeys.listIterator(); iter.hasNext(); ) {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 1d3f8d9..42ef089 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
@@ -70,7 +70,7 @@ final class ASTExpListLiteral extends ASTExpression {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "[...]";
     }
     
@@ -113,7 +113,7 @@ final class ASTExpListLiteral extends ASTExpression {
     
     @SuppressWarnings("unchecked")
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
         ArrayList<ASTExpression> clonedValues = (ArrayList<ASTExpression>) items.clone();
 		for (ListIterator<ASTExpression> iter = clonedValues.listIterator(); iter.hasNext(); ) {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNegateOrPlus.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNegateOrPlus.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNegateOrPlus.java
index 820e2e7..f0a0bf4 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNegateOrPlus.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNegateOrPlus.java
@@ -67,7 +67,7 @@ final class ASTExpNegateOrPlus extends ASTExpression {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return isMinus ? "-..." : "+...";
     }
     
@@ -77,7 +77,7 @@ final class ASTExpNegateOrPlus extends ASTExpression {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	return new ASTExpNegateOrPlus(
     	        target.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNot.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNot.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNot.java
index 934c58e..6344624 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNot.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNot.java
@@ -41,7 +41,7 @@ final class ASTExpNot extends ASTExpBoolean {
     }
  
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "!";
     }
     
@@ -51,7 +51,7 @@ final class ASTExpNot extends ASTExpBoolean {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	return new ASTExpNot(
     	        target.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState));

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNumberLiteral.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNumberLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNumberLiteral.java
index a61629d..a98fc78 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNumberLiteral.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpNumberLiteral.java
@@ -59,7 +59,7 @@ final class ASTExpNumberLiteral extends ASTExpression implements TemplateNumberM
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return getCanonicalForm();
     }
     
@@ -69,7 +69,7 @@ final class ASTExpNumberLiteral extends ASTExpression implements TemplateNumberM
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
         return new ASTExpNumberLiteral(value);
     }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpOr.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpOr.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpOr.java
index e9d64a0..5222b50 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpOr.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpOr.java
@@ -45,7 +45,7 @@ final class ASTExpOr extends ASTExpBoolean {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "||";
     }
 
@@ -55,7 +55,7 @@ final class ASTExpOr extends ASTExpBoolean {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
     	return new ASTExpOr(
     	        lho.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpParenthesis.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpParenthesis.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpParenthesis.java
index 9f64b3d..b7d82b7 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpParenthesis.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpParenthesis.java
@@ -43,7 +43,7 @@ final class ASTExpParenthesis extends ASTExpression {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "(...)";
     }
     
@@ -62,7 +62,7 @@ final class ASTExpParenthesis extends ASTExpression {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
         return new ASTExpParenthesis(
                 nested.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState));

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java
index edd80a5..2037fbc 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java
@@ -62,11 +62,11 @@ final class ASTExpRange extends ASTExpression {
     @Override
     public String getCanonicalForm() {
         String rhs = rho != null ? rho.getCanonicalForm() : "";
-        return lho.getCanonicalForm() + getASTNodeDescriptor() + rhs;
+        return lho.getCanonicalForm() + getLabelWithoutParameters() + rhs;
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         switch (endType) {
         case END_EXCLUSIVE: return "..<";
         case END_INCLUSIVE: return "..";
@@ -83,7 +83,7 @@ final class ASTExpRange extends ASTExpression {
     }
     
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
         return new ASTExpRange(
                 lho.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java
index c20de82..6c8d222 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java
@@ -171,7 +171,7 @@ final class ASTExpStringLiteral extends ASTExpression implements TemplateStringM
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return dynamicValue == null ? getCanonicalForm() : "dynamic \"...\"";
     }
     
@@ -181,7 +181,7 @@ final class ASTExpStringLiteral extends ASTExpression implements TemplateStringM
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
         ASTExpStringLiteral cloned = new ASTExpStringLiteral(value);
         // FIXME: replacedIdentifier should be searched inside interpolatedOutput too:

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpVariable.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpVariable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpVariable.java
index 1044403..b261c26 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpVariable.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpVariable.java
@@ -61,7 +61,7 @@ final class ASTExpVariable extends ASTExpression {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return getCanonicalForm();
     }
 
@@ -86,7 +86,7 @@ final class ASTExpVariable extends ASTExpression {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
         if (name.equals(replacedIdentifier)) {
             if (replacementState.replacementAlreadyInUse) {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java
index 31c855e..b15d1d0 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java
@@ -34,6 +34,7 @@ import org.apache.freemarker.core.model.impl.BeanModel;
 /**
  * AST expression node superclass
  */
+//TODO [FM3] will be public
 abstract class ASTExpression extends ASTNode {
 
     /**
@@ -51,6 +52,9 @@ abstract class ASTExpression extends ASTNode {
 
     // Hook in here to set the constant value if possible.
     
+    // Package visible constructor to prevent extending this class outside FreeMarker 
+    ASTExpression () { }
+    
     @Override
     void setLocation(Template template, int beginColumn, int beginLine, int endColumn, int endLine) {
         super.setLocation(template, beginColumn, beginLine, endColumn, endLine);
@@ -63,11 +67,10 @@ abstract class ASTExpression extends ASTNode {
         }
     }
 
-    final TemplateModel getAsTemplateModel(Environment env) throws TemplateException {
-        return eval(env);
-    }
-    
-    final TemplateModel eval(Environment env) throws TemplateException {
+    /**
+     * Evaluates the expression, returning its current value.
+     */
+    public final TemplateModel eval(Environment env) throws TemplateException {
         try {
             return constantValue != null ? constantValue : _eval(env);
         } catch (FlowControlException | TemplateException e) {
@@ -179,7 +182,7 @@ abstract class ASTExpression extends ASTNode {
      * This should return an equivalent new expression object (or an identifier replacement expression).
      * The position need not be filled, unless it will be different from the position of what we were cloning. 
      */
-    protected abstract ASTExpression deepCloneWithIdentifierReplaced_inner(
+    abstract ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState);
 
     static boolean isEmpty(TemplateModel model) throws TemplateException {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTImplicitParent.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTImplicitParent.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTImplicitParent.java
index 97991a7..f87da42 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTImplicitParent.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTImplicitParent.java
@@ -25,15 +25,17 @@ import java.io.IOException;
  * AST directive-like node, used where there's no other parent for a list of {@link ASTElement}-s. Most often occurs as
  * the root node of the AST.
  */
+//TODO [FM3] will be public
 final class ASTImplicitParent extends ASTElement {
 
+    // Package visible constructor to prevent instantiating outside FreeMarker 
     ASTImplicitParent() { }
     
     @Override
     ASTElement postParseCleanup(boolean stripWhitespace)
         throws ParseException {
         super.postParseCleanup(stripWhitespace);
-        return getChildCount() == 1 ? getChild(0) : this;
+        return getChildCount() == 1 ? fastGetChild(0) : this;
     }
 
     /**
@@ -41,20 +43,19 @@ final class ASTImplicitParent extends ASTElement {
      * and outputs the resulting text.
      */
     @Override
-    ASTElement[] accept(Environment env)
-        throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         return getChildBuffer();
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
             return getChildrenCanonicalForm();
         } else {
             if (getParent() == null) {
                 return "root";
             }
-            return getASTNodeDescriptor(); // ASTImplicitParent is uninteresting in a stack trace.
+            return getLabelWithoutParameters(); // ASTImplicitParent is uninteresting in a stack trace.
         }
     }
 
@@ -62,7 +63,7 @@ final class ASTImplicitParent extends ASTElement {
     protected boolean isOutputCacheable() {
         int ln = getChildCount();
         for (int i = 0; i < ln; i++) {
-            if (!getChild(i).isOutputCacheable()) {
+            if (!fastGetChild(i).isOutputCacheable()) {
                 return false;
             }
         }
@@ -70,7 +71,7 @@ final class ASTImplicitParent extends ASTElement {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#mixedContent";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTInterpolation.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTInterpolation.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTInterpolation.java
index 6ce0b7e..56805d2 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTInterpolation.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTInterpolation.java
@@ -31,6 +31,7 @@ import org.apache.freemarker.core.util.TemplateLanguageUtils;
 /**
  * AST interpolation node: <tt>${exp}</tt>
  */
+//TODO [FM3] will be public
 final class ASTInterpolation extends ASTElement {
 
     private final ASTExpression expression;
@@ -42,7 +43,7 @@ final class ASTInterpolation extends ASTElement {
     private final OutputFormat outputFormat;
     private final MarkupOutputFormat markupOutputFormat;
     private final boolean autoEscape;
-
+    
     ASTInterpolation(
             ASTExpression expression, ASTExpression escapedExpression,
             OutputFormat outputFormat, boolean autoEscape) {
@@ -58,9 +59,21 @@ final class ASTInterpolation extends ASTElement {
      * Outputs the string value of the enclosed expression.
      */
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
-        final Object moOrStr = calculateInterpolatedStringOrMarkup(env);
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
+        printStringOrTemplateOutputModel(
+                escapedExpression, outputFormat, markupOutputFormat, autoEscape, env);
+        return null;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    static void printStringOrTemplateOutputModel(
+            ASTExpression exp,
+            OutputFormat outputFormat,
+            MarkupOutputFormat markupOutputFormat, boolean autoEscape, 
+            Environment env) throws IOException, TemplateException {
         final Writer out = env.getOut();
+        
+        final Object moOrStr = _EvalUtils.coerceModelToPlainTextOrMarkup(exp.eval(env), exp, null, env);
         if (moOrStr instanceof String) {
             final String s = (String) moOrStr;
             if (autoEscape) {
@@ -70,28 +83,8 @@ final class ASTInterpolation extends ASTElement {
             }
         } else {
             final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr;
-            final MarkupOutputFormat moOF = mo.getOutputFormat();
-            // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!
-            if (moOF != outputFormat && !outputFormat.isOutputFormatMixingAllowed()) {
-                final String srcPlainText;
-                // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!
-                srcPlainText = moOF.getSourcePlainText(mo);
-                if (srcPlainText == null) {
-                    throw new TemplateException(escapedExpression,
-                            "The value to print is in ", new _DelayedToString(moOF),
-                            " format, which differs from the current output format, ",
-                            new _DelayedToString(outputFormat), ". Format conversion wasn't possible.");
-                }
-                if (outputFormat instanceof MarkupOutputFormat) {
-                    ((MarkupOutputFormat) outputFormat).output(srcPlainText, out);
-                } else {
-                    out.write(srcPlainText);
-                }
-            } else {
-                moOF.output(mo, out);
-            }
+            _EvalUtils.printTemplateMarkupOutputModel(mo, outputFormat, out, exp);
         }
-        return null;
     }
 
     /**
@@ -104,7 +97,7 @@ final class ASTInterpolation extends ASTElement {
     }
 
     @Override
-    protected final String dump(boolean canonical) {
+    final String dump(boolean canonical) {
         return dump(canonical, false);
     }
     
@@ -137,7 +130,7 @@ final class ASTInterpolation extends ASTElement {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "${...}";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTNode.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTNode.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTNode.java
index 4ca9b7f..bcce5de 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTNode.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTNode.java
@@ -20,8 +20,21 @@
 package org.apache.freemarker.core;
 
 /**
- * AST node: The superclass of all AST nodes
+ * Superclass of all AST (Abstract Syntax Tree) nodes.
+ * <p>
+ * The AST is a tree data structure that represent the complete content of a template (static content, directive calls,
+ * interpolations and the expressions inside them, possibly comments as well), without the complexities of syntactical
+ * details. The AST is generated from the source code (which is text) by the parser of the {@link TemplateLanguage},
+ * and focuses on the meaning of the template. Thus, if the same template is rewritten in different template languages
+ * (like F3AH is converted to F3SH), the resulting AST-s will remain practically identical.
+ * <p>
+ * When a {@link Template} is processed, FreeMarker executes the AST directly. (In theory the AST could be translated
+ * further to byte code, FreeMarker doesn't try to do that, at least currently.)
+ * <p>
+ * The AST can also be used to analyze the content of templates, such as to discover its dependencies (on data-model
+ * variables, on other templates). 
  */
+//TODO [FM3] will be public
 abstract class ASTNode {
     
     private Template template;
@@ -67,51 +80,46 @@ abstract class ASTNode {
         this.endLine = endLine;
     }
     
+    // Package visible constructor to prevent extending this class outside FreeMarker 
+    ASTNode() { }
+    
+    /**
+     * The template that contains this node.
+     */
+    public Template getTemplate() {
+        return template;
+    }
+    
+    /**
+     * 1-based column number of the last character of this node in the source code. 0 if not available.
+     */
     public final int getBeginColumn() {
         return beginColumn;
     }
 
+    /**
+     * 1-based line number of the first character of this node in the source code. 0 if not available.
+     */
+    // TODO [FM3] No negative number hack in ?eval and such.
     public final int getBeginLine() {
         return beginLine;
     }
 
-    public final int getEndColumn() {
-        return endColumn;
-    }
-
-    public final int getEndLine() {
-        return endLine;
-    }
-
     /**
-     * Returns a string that indicates
-     * where in the template source, this object is.
+     * 1-based column number of the first character of this node in the source code. 0 if not available.
      */
-    public String getStartLocation() {
-        return MessageUtils.formatLocationForEvaluationError(template, beginLine, beginColumn);
+    public final int getEndColumn() {
+        return endColumn;
     }
 
     /**
-     * As of 2.3.20. the same as {@link #getStartLocation}. Meant to be used where there's a risk of XSS
-     * when viewing error messages.
+     * 1-based line number of the last character of this node in the source code. 0 if not available.
      */
-    public String getStartLocationQuoted() {
-        return getStartLocation();
-    }
-
-    public String getEndLocation() {
-        return MessageUtils.formatLocationForEvaluationError(template, endLine, endColumn);
+    public final int getEndLine() {
+        return endLine;
     }
 
-    /**
-     * As of 2.3.20. the same as {@link #getEndLocation}. Meant to be used where there's a risk of XSS
-     * when viewing error messages.
-     */
-    public String getEndLocationQuoted() {
-        return getEndLocation();
-    }
-    
-    public final String getSource() {
+    final String getSource() {
         String s;
         if (template != null) {
             s = template.getSource(beginColumn, beginLine, endColumn, endLine);
@@ -124,14 +132,9 @@ abstract class ASTNode {
     }
 
     @Override
-    public String toString() {
+    public final String toString() {
         String s;
-    	try {
-    		s = getSource();
-    	} catch (Exception e) { // REVISIT: A bit of a hack? (JR)
-    	    s = null;
-    	}
-    	return s != null ? s : getCanonicalForm();
+    	return (s = getSource()) != null ? s : getCanonicalForm();
     }
 
     /**
@@ -155,10 +158,6 @@ abstract class ASTNode {
         return true;
     }
 
-    public Template getTemplate() {
-        return template;
-    }
-    
     ASTNode copyLocationFrom(ASTNode from) {
         template = from.template;
         beginColumn = from.beginColumn;
@@ -169,12 +168,14 @@ abstract class ASTNode {
     }    
 
     /**
-     * FTL generated from the AST of the node, which must be parseable to an AST that does the same as the original
-     * source, assuming we turn off automatic white-space removal when parsing the canonical form.
+     * Template source code generated from the AST of this node.
+     * When parsed, it should result in a practically identical AST that does the same as the original
+     * source, assuming that you turn off automatic white-space removal when parsing the canonical form.
      * 
-     * @see ASTElement#getDescription()
-     * @see #getASTNodeDescriptor()
+     * @see ASTElement#getLabelWithParameters()
+     * @see #getLabelWithoutParameters()
      */
+    // TODO [FM3] The whitespace problem isn't OK; do pretty-formatting, outside core if too big.
     abstract public String getCanonicalForm();
     
     /**
@@ -185,9 +186,9 @@ abstract class ASTNode {
      * leaf nodes the symbols should be the canonical form of value.
      *
      * @see #getCanonicalForm()
-     * @see ASTElement#getDescription()
+     * @see ASTElement#getLabelWithParameters()
      */
-    abstract String getASTNodeDescriptor();
+    public abstract String getLabelWithoutParameters();
     
     /**
      * Returns highest valid parameter index + 1. So one should scan indexes with {@link #getParameterValue(int)}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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 76f1019..4e8ec95 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
@@ -27,6 +27,7 @@ import org.apache.freemarker.core.util._StringUtils;
 /**
  * AST node representing static text.
  */
+//TODO [FM3] will be public
 final class ASTStaticText extends ASTElement {
     
     // We're using char[] instead of String for storing the text block because
@@ -37,11 +38,11 @@ final class ASTStaticText extends ASTElement {
     private char[] text;
     private final boolean unparsed;
 
-    public ASTStaticText(String text) {
+    ASTStaticText(String text) {
         this(text, false);
     }
 
-    public ASTStaticText(String text, boolean unparsed) {
+    ASTStaticText(String text, boolean unparsed) {
         this(text.toCharArray(), unparsed);
     }
 
@@ -56,19 +57,15 @@ final class ASTStaticText extends ASTElement {
 
     /**
      * Simply outputs the text.
-     * 
-     * @deprecated This is an internal API; don't call or override it.
      */
-    @Deprecated
     @Override
-    public ASTElement[] accept(Environment env)
-    throws IOException {
+    ASTElement[] execute(Environment env) throws IOException {
         env.getOut().write(text);
         return null;
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
             String text = new String(this.text);
             if (unparsed) {
@@ -81,7 +78,7 @@ final class ASTStaticText extends ASTElement {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#text";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java
index 47a37bd..31aeb00 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java
@@ -48,8 +48,8 @@ abstract class BuiltInWithParseTimeParameters extends SpecialBuiltIn {
     }
     
     @Override
-    String getASTNodeDescriptor() {
-        return super.getASTNodeDescriptor() + "(...)";
+    public String getLabelWithoutParameters() {
+        return super.getLabelWithoutParameters() + "(...)";
     }        
     
     @Override
@@ -90,7 +90,7 @@ abstract class BuiltInWithParseTimeParameters extends SpecialBuiltIn {
     }
 
     @Override
-    protected ASTExpression deepCloneWithIdentifierReplaced_inner(
+    ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
         final ASTExpression clone = super.deepCloneWithIdentifierReplaced_inner(replacedIdentifier, replacement, replacementState);
         cloneArguments(clone, replacedIdentifier, replacement, replacementState);

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/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
index 6b9ba34..6135d3a 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlace.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlace.java
@@ -65,6 +65,8 @@ public interface CallPlace {
      *         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()}.
+     * @param out
+     *         The {@link Writer} to print. 
      */
     void executeNestedContent(TemplateModel[] nestedContentArgs, Writer out, Environment env)
             throws TemplateException, IOException;

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
index be9f802..9a9d13b 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -43,6 +43,7 @@ import java.util.TimeZone;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.freemarker.core.Dialect.ConfiguredDialect;
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
@@ -175,6 +176,7 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc
     private final Long templateUpdateDelayMilliseconds;
     private final Boolean localizedTemplateLookup;
     private final List<OutputFormat> registeredCustomOutputFormats;
+    private final Map<Dialect, ConfiguredDialect> configuredDialects;
     private final Map<String, OutputFormat> registeredCustomOutputFormatsByName;
     private final Map<String, Object> sharedVariables;
     private final Map<String, TemplateModel> wrappedSharedVariables;
@@ -441,6 +443,12 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc
         this.templateConfigurations = templateConfigurations;
 
         templateResolver.setDependencies(new TemplateResolverDependenciesImpl(this, templateResolver));
+        
+        // ATTENTION! Creating the configuredDialects must be the last thing, as here the user code can already access
+        // this Configuration!
+        configuredDialects = new HashMap<>();
+        // TODO [FM3] This is hard-code for now, but later we need to add the "dialects" configuration setting
+        configuredDialects.put(DefaultDialect.INSTANCE, DefaultDialect.INSTANCE.createConfiguredDialect(this));
     }
 
     private void checkSettingIsNullForThisTemplateResolver(
@@ -671,8 +679,8 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc
                 throw new IllegalArgumentException("Missing opening '{' in: " + name);
             }
             
-            MarkupOutputFormat outerOF = getMarkupOutputFormatForCombined(name.substring(0, openBrcIdx));
-            MarkupOutputFormat innerOF = getMarkupOutputFormatForCombined(
+            MarkupOutputFormat<?> outerOF = getMarkupOutputFormatForCombined(name.substring(0, openBrcIdx));
+            MarkupOutputFormat<?> innerOF = getMarkupOutputFormatForCombined(
                     name.substring(openBrcIdx + 1, name.length() - 1));
             
             return new CombinedMarkupOutputFormat(name, outerOF, innerOF);
@@ -708,6 +716,21 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc
             return stdOF;
         }
     }
+
+    /**
+     * Same as {@link #getOutputFormat(String)}, but also throws {@link UnregisteredOutputFormatException} of the
+     * output format doesn't extend {@link MarkupOutputFormat}. 
+     */
+    public MarkupOutputFormat<?> getMarkupOutputFormat(String name) throws UnregisteredOutputFormatException {
+        OutputFormat outputFormat = getOutputFormat(name);
+        if (!(outputFormat instanceof MarkupOutputFormat<?>)) {
+            // TODO [FM3] It's kind of silly, but so is introducing a subclass exception just for this...
+            throw new UnregisteredOutputFormatException(
+                    "The " + _StringUtils.jQuote(name) + " output format (class " + outputFormat.getClass().getName()
+                    + " is registered, but doesn't implement " + MarkupOutputFormat.class.getName() + ".");
+        }
+        return (MarkupOutputFormat<?>) outputFormat;
+    }
     
     /**
      * Returns the argument {@link OutputFormat} as is, unless a {@link #getRegisteredCustomOutputFormats()
@@ -815,7 +838,7 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc
     public TemplateLanguage getTemplateLanguage() {
         return templateLanguage;
     }
-
+    
     /**
      * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
      * value in the {@link Configuration}.
@@ -824,6 +847,30 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc
     public boolean isTemplateLanguageSet() {
         return true;
     }
+    
+    /**
+     * Returns the {@link ConfiguredDialect} for a {@link Dialect} that's known by this {@link Configuration}.
+     * 
+     * @param dialect
+     *            Not {@code null}.
+     * 
+     * @return Never {@code null}. When invoked on the same {@link Configuration} with the same {@link Dialect}
+     *         instance, it always returns the same {@link ConfiguredDialect} instance.
+     * 
+     * @throws IllegalStateException
+     *             If the {@link Dialect} is not know by this {@link Configuration}. (TODO [FM3]: For now it only knows
+     *             {@link DefaultDialect#INSTANCE}, but that will change later when custom {@link TemplateLanguage}-s
+     *             can be added to the {@link Configuration}.)
+     */
+    public ConfiguredDialect getConfiguredDialect(Dialect dialect) {
+        _NullArgumentException.check("dialect", dialect);
+        ConfiguredDialect configuredDialect = configuredDialects.get(dialect);
+        if (configuredDialect == null) {
+            throw new IllegalStateException("No such " + Dialect.class.getName() + "  was added to this Configuration: "
+                        + dialect);
+        }
+        return configuredDialect;
+    }
 
     @Override
     public int getTabSize() {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultDialect.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultDialect.java b/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultDialect.java
new file mode 100644
index 0000000..b04351a
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultDialect.java
@@ -0,0 +1,82 @@
+package org.apache.freemarker.core;
+
+/*
+ * 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.
+ */
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
+import org.apache.freemarker.core.outputformat.UnregisteredOutputFormatException;
+import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+
+/**
+ * The default {@link Dialect} for FreeMarker. Most applications are expected to use this. 
+ */
+//TODO [FM3][DIALECTS] will be public. Also, then move it into core.dialect?
+final class DefaultDialect extends Dialect {
+    
+    public static final DefaultDialect INSTANCE = new DefaultDialect(); 
+
+    private DefaultDialect() {
+        super("Default FreeMarker 3 Dialect", Configuration.getVersion());
+    }
+
+    @Override
+    public ConfiguredDialect createConfiguredDialect(Configuration cfg) {
+        return new ConfiguredDefaultDialect(cfg);
+    }
+
+    private class ConfiguredDefaultDialect extends ConfiguredDialect {
+        private final Map<String, StaticallyLinkedNamespaceEntry> namespaceEntriesByName;
+
+        ConfiguredDefaultDialect(Configuration cfg) {
+            MarkupOutputFormat<?> htmlOutputFormat;
+            try {
+                htmlOutputFormat = cfg.getMarkupOutputFormat(HTMLOutputFormat.INSTANCE.getName());
+            } catch (UnregisteredOutputFormatException e) {
+                throw new ConfigurationException("Couldn't get HTML output format.", e);
+            }
+            namespaceEntriesByName = new HashMap<>(16, 0.5f); // The speed of get(key) is important
+            //!!T Test entries until FREEMARKER-99 is finished: 
+            addNamespaceEntry(
+                    new StaticallyLinkedNamespaceEntry("adhocTest1", ASTDirAdhocTest1.VALUE,
+                            new ASTDirAdhocTest1.Factory(htmlOutputFormat), null));
+            addNamespaceEntry(
+                    new StaticallyLinkedNamespaceEntry("adhocTest2", ASTDirAdhocTest2.VALUE,
+                    ASTDirAdhocTest2.FACTORY, null));
+        }
+
+        @Override
+        public StaticallyLinkedNamespaceEntry getNamespaceEntry(String name) {
+            return namespaceEntriesByName.get(name);
+        }
+
+        @Override
+        public Iterable<StaticallyLinkedNamespaceEntry> getNamespaceEntries() {
+            return namespaceEntriesByName.values();
+        }
+        
+        private void addNamespaceEntry(StaticallyLinkedNamespaceEntry entry) {
+            namespaceEntriesByName.put(entry.getName(), entry);
+        }
+        
+    }
+
+}


[3/3] freemarker git commit: Some ground work to support dialects later. Added classes like Dialect and StaticallyLinkedNamespaceEntry, but these are non-public for now, and just drafts, and aren't actually used by the templates. The final ones will need

Posted by dd...@apache.org.
Some ground work to support dialects later. Added classes like Dialect and StaticallyLinkedNamespaceEntry, but these are non-public for now, and just drafts, and aren't actually used by the templates. The final ones will need more features to be able to replace the trickier built-in directives. Note that some early version (not a public API) of dialects will be needed for FREEMARKER-99, which is why I started this.

Along the way, some internal renaming in the ASTNode related API-s. Most notably visit(...) to execute(...), as we don't use the visitor pattern.


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

Branch: refs/heads/3
Commit: e8e58ffa52937ae4e67d03edb81dd76c84ac9118
Parents: 056635c
Author: ddekany <dd...@apache.org>
Authored: Wed Apr 18 19:19:08 2018 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Jun 16 13:42:15 2018 +0200

----------------------------------------------------------------------
 README.md                                       |   3 +
 .../org/apache/freemarker/core/ASTPrinter.java  |  10 +-
 .../core/GetOptionalTemplateTest.java           |   2 +-
 .../core/InterpolationSyntaxTest.java           |   2 +
 .../core/MistakenlyPublicImportAPIsTest.java    |   9 +-
 .../core/TemplateCallableModelTest.java         |   2 +-
 .../freemarker/core/TemplateLanguageTest.java   |   5 +-
 .../org/apache/freemarker/core/ASTComment.java  |   8 +-
 .../apache/freemarker/core/ASTDebugBreak.java   |  15 +-
 .../freemarker/core/ASTDirAdhocTest1.java       | 211 +++++++++++++++++++
 .../freemarker/core/ASTDirAdhocTest2.java       | 165 +++++++++++++++
 .../freemarker/core/ASTDirAssignment.java       |   8 +-
 .../core/ASTDirAssignmentsContainer.java        |  10 +-
 .../core/ASTDirAttemptRecoverContainer.java     |  12 +-
 .../apache/freemarker/core/ASTDirAutoEsc.java   |  10 +-
 .../org/apache/freemarker/core/ASTDirBreak.java |   8 +-
 .../core/ASTDirCapturingAssignment.java         |  19 +-
 .../org/apache/freemarker/core/ASTDirCase.java  |   8 +-
 .../apache/freemarker/core/ASTDirCompress.java  |  15 +-
 .../apache/freemarker/core/ASTDirContinue.java  |   8 +-
 .../freemarker/core/ASTDirElseOfList.java       |  10 +-
 .../apache/freemarker/core/ASTDirEscape.java    |  10 +-
 .../apache/freemarker/core/ASTDirFallback.java  |   8 +-
 .../org/apache/freemarker/core/ASTDirFlush.java |   8 +-
 .../core/ASTDirIfElseIfElseContainer.java       |  14 +-
 .../freemarker/core/ASTDirIfOrElseOrElseIf.java |   8 +-
 .../apache/freemarker/core/ASTDirImport.java    |   8 +-
 .../apache/freemarker/core/ASTDirInclude.java   |   8 +-
 .../org/apache/freemarker/core/ASTDirItems.java |  12 +-
 .../org/apache/freemarker/core/ASTDirList.java  |  18 +-
 .../core/ASTDirListElseContainer.java           |  12 +-
 .../freemarker/core/ASTDirMacroOrFunction.java  |  10 +-
 .../apache/freemarker/core/ASTDirNested.java    |   8 +-
 .../apache/freemarker/core/ASTDirNoAutoEsc.java |  10 +-
 .../apache/freemarker/core/ASTDirNoEscape.java  |  12 +-
 .../freemarker/core/ASTDirOutputFormat.java     |  12 +-
 .../apache/freemarker/core/ASTDirRecover.java   |  10 +-
 .../apache/freemarker/core/ASTDirRecurse.java   |   8 +-
 .../apache/freemarker/core/ASTDirReturn.java    |   8 +-
 .../org/apache/freemarker/core/ASTDirSep.java   |  12 +-
 .../apache/freemarker/core/ASTDirSetting.java   |   8 +-
 .../org/apache/freemarker/core/ASTDirStop.java  |   8 +-
 .../apache/freemarker/core/ASTDirSwitch.java    |  22 +-
 .../freemarker/core/ASTDirTOrRtOrLtOrNt.java    |   8 +-
 .../org/apache/freemarker/core/ASTDirVisit.java |   8 +-
 .../apache/freemarker/core/ASTDirective.java    |  63 ++++++
 .../freemarker/core/ASTDynamicTopLevelCall.java |  26 +--
 .../org/apache/freemarker/core/ASTElement.java  | 138 ++++++------
 .../freemarker/core/ASTExpAddOrConcat.java      |   4 +-
 .../org/apache/freemarker/core/ASTExpAnd.java   |   4 +-
 .../freemarker/core/ASTExpArithmetic.java       |   4 +-
 .../freemarker/core/ASTExpBooleanLiteral.java   |   9 +-
 .../apache/freemarker/core/ASTExpBuiltIn.java   |   4 +-
 .../freemarker/core/ASTExpBuiltInVariable.java  |   9 +-
 .../freemarker/core/ASTExpComparison.java       |   4 +-
 .../apache/freemarker/core/ASTExpDefault.java   |   4 +-
 .../org/apache/freemarker/core/ASTExpDot.java   |   6 +-
 .../freemarker/core/ASTExpDynamicKeyName.java   |   4 +-
 .../apache/freemarker/core/ASTExpExists.java    |   6 +-
 .../freemarker/core/ASTExpFunctionCall.java     |   6 +-
 .../freemarker/core/ASTExpHashLiteral.java      |   4 +-
 .../freemarker/core/ASTExpListLiteral.java      |   4 +-
 .../freemarker/core/ASTExpNegateOrPlus.java     |   4 +-
 .../org/apache/freemarker/core/ASTExpNot.java   |   4 +-
 .../freemarker/core/ASTExpNumberLiteral.java    |   4 +-
 .../org/apache/freemarker/core/ASTExpOr.java    |   4 +-
 .../freemarker/core/ASTExpParenthesis.java      |   4 +-
 .../org/apache/freemarker/core/ASTExpRange.java |   6 +-
 .../freemarker/core/ASTExpStringLiteral.java    |   4 +-
 .../apache/freemarker/core/ASTExpVariable.java  |   4 +-
 .../apache/freemarker/core/ASTExpression.java   |  15 +-
 .../freemarker/core/ASTImplicitParent.java      |  15 +-
 .../freemarker/core/ASTInterpolation.java       |  45 ++--
 .../org/apache/freemarker/core/ASTNode.java     |  95 ++++-----
 .../apache/freemarker/core/ASTStaticText.java   |  15 +-
 .../core/BuiltInWithParseTimeParameters.java    |   6 +-
 .../org/apache/freemarker/core/CallPlace.java   |   2 +
 .../apache/freemarker/core/Configuration.java   |  53 ++++-
 .../apache/freemarker/core/DefaultDialect.java  |  82 +++++++
 .../core/DefaultTemplateLanguage.java           |  25 ++-
 .../org/apache/freemarker/core/Dialect.java     | 127 +++++++++++
 .../org/apache/freemarker/core/Environment.java | 161 ++++++++++----
 .../apache/freemarker/core/MessageUtils.java    |  19 ++
 .../freemarker/core/ParsingConfiguration.java   |   5 +-
 .../core/StaticLinkingCheckException.java       |  74 +++++++
 .../core/StaticallyLinkedNamespaceEntry.java    | 117 ++++++++++
 .../org/apache/freemarker/core/Template.java    |   2 +-
 .../freemarker/core/TemplateConfiguration.java  |   9 +-
 .../core/TemplateElementArrayBuilder.java       |  13 --
 .../core/TemplateElementsToVisit.java           |   4 +-
 .../freemarker/core/TemplateLanguage.java       |  35 +--
 ...nterruptionSupportTemplatePostProcessor.java |  10 +-
 .../core/UnparsedTemplateLanguage.java          |   3 +-
 .../core/UnsupportedFM2TemplateLanguage.java    |   2 +-
 .../apache/freemarker/core/_CallableUtils.java  | 147 +++++++------
 .../java/org/apache/freemarker/core/_Debug.java |  25 ++-
 .../core/_ErrorDescriptionBuilder.java          |   6 +-
 .../org/apache/freemarker/core/_EvalUtils.java  |  30 +++
 .../freemarker/core/util/_ArrayAdapterList.java |   2 +-
 .../freemarker/core/util/_ArrayEnumeration.java |  51 -----
 .../freemarker/core/util/_ArrayIterator.java    |   8 +-
 .../core/util/_NullArgumentException.java       |   2 +
 freemarker-core/src/main/javacc/FTL.jj          |   8 +-
 103 files changed, 1691 insertions(+), 645 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 15a5f33..7d34e6d 100644
--- a/README.md
+++ b/README.md
@@ -201,6 +201,9 @@ Last tested Eclipse Oxygen (4.7.0)
   - Fro each project: Project -> Properties -> FindBugs -> [x] Run Automatically
   - There should 0 errors. But sometimes the plugin fails to take the
     @SuppressFBWarnings annotations into account; then use Project -> Clean. 
+- Setting type filters (not required, but convenient): "Window" -> "Preferences" ->
+  "Java" -> "Appearance" -> "Type filter", "Add..." these:
+  `javax.swing.*`, `freemarker.*`, `com.sun.*`
 
 ### IntelliJ IDEA
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTPrinter.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTPrinter.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTPrinter.java
index 8157c19..297e975 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTPrinter.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTPrinter.java
@@ -35,7 +35,6 @@ import java.nio.charset.CharacterCodingException;
 import java.nio.charset.Charset;
 import java.nio.charset.CodingErrorAction;
 import java.nio.charset.StandardCharsets;
-import java.util.Enumeration;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 
@@ -261,7 +260,7 @@ public class ASTPrinter {
     private static void validateAST(ASTElement te) {
         int childCount = te.getChildCount();
         for (int i = 0; i < childCount; i++) {
-            ASTElement child = te.getChild(i);
+            ASTElement child = te.fastGetChild(i);
             ASTElement parentElement = child.getParent();
             // As ASTImplicitParent.accept does nothing but returns its children, it's optimized out in the final
             // AST tree. While it will be present as a child, the parent element also will have children
@@ -321,7 +320,7 @@ public class ASTPrinter {
             ASTNode tObj = (ASTNode) node;
 
             printNodeLineStart(paramRole, ind, out);
-            out.write(tObj.getASTNodeDescriptor());
+            out.write(tObj.getLabelWithoutParameters());
             printNodeLineEnd(node, out, opts);
             
             if (opts.getShowConstantValue() && node instanceof ASTExpression) {
@@ -345,9 +344,8 @@ public class ASTPrinter {
                 printNode(value, ind + INDENTATION, role, opts, out);
             }
             if (tObj instanceof ASTElement) {
-                Enumeration enu = ((ASTElement) tObj).children();
-                while (enu.hasMoreElements()) {
-                    printNode(enu.nextElement(), INDENTATION + ind, null, opts, out);
+                for (ASTElement child : ((ASTElement) tObj).getChildren()) {
+                    printNode(child, INDENTATION + ind, null, opts, out);
                 }
             }
         } else {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetOptionalTemplateTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetOptionalTemplateTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetOptionalTemplateTest.java
index 22d6aff..f6c973e 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetOptionalTemplateTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetOptionalTemplateTest.java
@@ -107,7 +107,7 @@ public class GetOptionalTemplateTest extends TemplateTest {
     @Test
     public void testWrongArguments() throws Exception {
         assertErrorContains("<#assign t = .getOptionalTemplate()>", "argument");
-        assertErrorContains("<#assign t = .getOptionalTemplate('1', '2', '3')>", "arguments", "3");
+        assertErrorContains("<#assign t = .getOptionalTemplate('1', '2', '3')>", "1", "arguments", "more");
         assertErrorContains("<#assign t = .getOptionalTemplate(1)>", "1st argument", "string", "number");
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java
index 2bf4200..9865c18 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java
@@ -33,11 +33,13 @@ public class InterpolationSyntaxTest extends TemplateTest {
 
     /** Non-standard template language */
     private final TemplateLanguage F3ASU = new DefaultTemplateLanguage("dummy",
+            DefaultDialect.INSTANCE,
             UndefinedOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT,
             TagSyntax.ANGLE_BRACKET, InterpolationSyntax.SQUARE_BRACKET); 
 
     /** Non-standard template language */
     private final TemplateLanguage F3SDU = new DefaultTemplateLanguage("dummy",
+            DefaultDialect.INSTANCE,
             UndefinedOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT,
             TagSyntax.SQUARE_BRACKET, InterpolationSyntax.DOLLAR); 
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core-test/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
index 5fcee97..be85eeb 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
@@ -35,9 +35,12 @@ import org.junit.Test;
 
 /**
  * These are things that users shouldn't do, but we shouldn't break backward compatibility without knowing about it.
+ * 
+ * TODO [FM3] Now we should make this illegal, but I'm not sure how to catch when the user does this.
  */
 public class MistakenlyPublicImportAPIsTest {
 
+    
     @Test
     public void testImportCopying() throws IOException, TemplateException {
         StringTemplateLoader tl = new StringTemplateLoader();
@@ -60,7 +63,7 @@ public class MistakenlyPublicImportAPIsTest {
                 t2.process(null, _NullWriter.INSTANCE);
                 fail();
             } catch (InvalidReferenceException e) {
-                // Apparenly, it has never worked like this...
+                // Apparently, it has never worked like this...
                 assertEquals("i1", e.getBlamedExpressionString());
             }
         }
@@ -72,6 +75,7 @@ public class MistakenlyPublicImportAPIsTest {
         assertThat(i1, instanceOf(Namespace.class));
         TemplateModel i2 = env.getVariable("i2");
         assertThat(i2, instanceOf(Namespace.class));
+        Environment originalEnv = env;
 
         {
             Template t2 = new Template(null, "<@i1.m/>", cfg);
@@ -80,6 +84,7 @@ public class MistakenlyPublicImportAPIsTest {
             env = t2.createProcessingEnvironment(null, sw);
             env.setVariable("i1", i1);
             
+            originalEnv.setOut(sw); // The imported macros are still bound to and will use this.
             env.process();
             assertEquals("1", sw.toString());
         }
@@ -88,10 +93,12 @@ public class MistakenlyPublicImportAPIsTest {
             Template t2 = new Template(null, "<@i2.m/>", cfg);
             
             StringWriter sw = new StringWriter();
+            env.setOut(sw); // In the old Environment instance, to which the imported macros are bound.
             env = t2.createProcessingEnvironment(null, sw);
             env.setVariable("i2", i2);
             
             try {
+                originalEnv.setOut(sw); // The imported macros are still bound to and will use this.
                 env.process();
                 assertEquals("2", sw.toString());
             } catch (NullPointerException e) {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
index a1e684d..8675d13 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
@@ -219,7 +219,7 @@ public class TemplateCallableModelTest extends TemplateTest {
     @Test
     @SuppressWarnings("ThrowableNotThrown")
     public void testRuntimeErrors() throws IOException, TemplateException {
-        assertErrorContains("<@p 9, 9, 9 />", "can only have 2", "3", "by position");
+        assertErrorContains("<@p 9, 9, 9 />", "can only have 2", "more", "by position");
         assertErrorContains("<@n 9 />", "can't have arguments passed by position");
         assertErrorContains("<@n n3=9 />", "has no", "\"n3\"", "supported", "\"n1\", \"n2\"");
         assertErrorContains("<@p n1=9 />", "directive", "can't have arguments that are passed by name", "\"n1\"",

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateLanguageTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateLanguageTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateLanguageTest.java
index 069394b..3bcc382 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateLanguageTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateLanguageTest.java
@@ -67,12 +67,13 @@ public class TemplateLanguageTest {
 
         public DummyTemplateLanguage(String fileExtension, OutputFormat outputFormat,
                 AutoEscapingPolicy autoEscapingPolicy) {
-            super(fileExtension, outputFormat, autoEscapingPolicy);
+            super(fileExtension, DefaultDialect.INSTANCE, outputFormat, autoEscapingPolicy);
         }
 
         DummyTemplateLanguage(String fileExtension, boolean allowExtensionStartingWithF, OutputFormat outputFormat,
                 AutoEscapingPolicy autoEscapingPolicy) {
-            super(fileExtension, allowExtensionStartingWithF, outputFormat, autoEscapingPolicy);
+            super(fileExtension, allowExtensionStartingWithF, DefaultDialect.INSTANCE,
+                    outputFormat, autoEscapingPolicy);
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTComment.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTComment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTComment.java
index 8a16965..2224fa1 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTComment.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTComment.java
@@ -24,6 +24,7 @@ import org.apache.freemarker.core.util._StringUtils;
 /**
  * AST comment node
  */
+//TODO [FM3] will be public
 final class ASTComment extends ASTElement {
 
     private final String text;
@@ -33,13 +34,13 @@ final class ASTComment extends ASTElement {
     }
 
     @Override
-    ASTElement[] accept(Environment env) {
+    ASTElement[] execute(Environment env) {
         // do nothing, skip the body
         return null;
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
             return "<#--" + text + "-->";
         } else {
@@ -48,10 +49,9 @@ final class ASTComment extends ASTElement {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#--...--";
     }
-    
 
     @Override
     int getParameterCount() {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
index 562a2cb..0634ffc 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
@@ -27,23 +27,24 @@ import org.apache.freemarker.core.debug._DebuggerService;
  * AST node: A debug breakpoint
  */
 class ASTDebugBreak extends ASTElement {
-    public ASTDebugBreak(ASTElement nestedBlock) {
+    
+    ASTDebugBreak(ASTElement nestedBlock) {
         addChild(nestedBlock);
         copyLocationFrom(nestedBlock);
     }
     
     @Override
-    protected ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    protected ASTElement[] execute(Environment env) throws TemplateException, IOException {
         if (!_DebuggerService.suspendEnvironment(
-                env, getTemplate().getSourceName(), getChild(0).getBeginLine())) {
-            return getChild(0).accept(env);
+                env, getTemplate().getSourceName(), fastGetChild(0).getBeginLine())) {
+            return fastGetChild(0).execute(env);
         } else {
             throw new StopException(env, "Stopped by debugger");
         }
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
             StringBuilder sb = new StringBuilder();
             sb.append("<#-- ");
@@ -52,7 +53,7 @@ class ASTDebugBreak extends ASTElement {
                 sb.append(" /-->");
             } else {
                 sb.append(" -->");
-                sb.append(getChild(0).getCanonicalForm());                
+                sb.append(fastGetChild(0).getCanonicalForm());                
                 sb.append("<#--/ debug break -->");
             }
             return sb.toString();
@@ -62,7 +63,7 @@ class ASTDebugBreak extends ASTElement {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#debugBreak";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAdhocTest1.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAdhocTest1.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAdhocTest1.java
new file mode 100644
index 0000000..69d40ef
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAdhocTest1.java
@@ -0,0 +1,211 @@
+package org.apache.freemarker.core;
+
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
+import org.apache.freemarker.core.util.CallableUtils;
+import org.apache.freemarker.core.util.CommonSupplier;
+import org.apache.freemarker.core.util.StringToIndexMap;
+
+/**
+ * Example of an AST directive that's bound to some Configuration setting (the htmlOutputFormat in this case).
+ */
+//TODO [FM3][FREEMARKER-99] Delete this class when we are finished.
+class ASTDirAdhocTest1 extends ASTDirective {
+    private final MarkupOutputFormat<?> htmlOutputFormat;
+    private ASTExpression p1;
+    private ASTExpression p2;
+    private ASTExpression n1;
+    private ASTExpression n2;
+    private StringToIndexMap nestedContentParamNames;
+
+    private ASTDirAdhocTest1(MarkupOutputFormat<?> htmlOutputFormat) {
+        this.htmlOutputFormat = htmlOutputFormat;
+    }
+
+    static class Factory implements CommonSupplier<ASTDirective> {
+        private final MarkupOutputFormat<?> htmlOutputFormat;
+
+        Factory(MarkupOutputFormat<?> htmlOutputFormat) {
+            this.htmlOutputFormat = htmlOutputFormat;
+        }
+
+        @Override
+        public ASTDirective get() {
+            return new ASTDirAdhocTest1(htmlOutputFormat);
+        }
+    }
+    
+    static TemplateDirectiveModel VALUE = new TemplateDirectiveModel() {
+
+        @Override
+        public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
+                throws TemplateException, IOException {
+            throw new UnsupportedOperationException("execute not implemented");
+        }
+
+        @Override
+        public boolean isNestedContentSupported() {
+            return true;
+        }
+
+        @Override
+        public ArgumentArrayLayout getDirectiveArgumentArrayLayout() {
+            return ARGS_LAYOUT;
+        }
+
+    };
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(2, false,
+            StringToIndexMap.of("n1", 2, "n2", 3), false);
+    
+    @Override
+    public void setPositionalArgument(int position, ASTExpression valueExp) throws StaticLinkingCheckException {
+        if (position == 0) {
+            p1 = valueExp;
+        } else if (position == 1) {
+            p2 = valueExp;
+        } else {
+            // TODO: Standardize error message; see CallableUtils 
+            throw new StaticLinkingCheckException(new _ErrorDescriptionBuilder(
+                  "Too many positional arguments; can only have ",
+                  ARGS_LAYOUT.getPredefinedPositionalArgumentCount(), "."));
+        }
+    }
+
+    @Override
+    public void setNamedArgument(String name, ASTExpression valueExp) throws StaticLinkingCheckException {
+        if (name.equals("n1")) {
+            n1 = valueExp;
+        } else if (name.equals("n2")) {
+            n2 = valueExp;
+        } else {
+            // TODO: Standardize error message; see CallableUtils 
+            throw new StaticLinkingCheckException(new _ErrorDescriptionBuilder(
+                    "Unsupported parameter name ", new _DelayedJQuote(name), ". The supported parameters are: ",
+                    new _DelayedJQuotedListing(ARGS_LAYOUT.getPredefinedNamedArgumentsMap().getKeys())));
+        }
+    }
+
+    @Override
+    public void checkArgumentsAndSetNestedContentParameters(StringToIndexMap nestedContentParamNames)
+            throws StaticLinkingCheckException {
+        // TODO: Standardize error message; see CallableUtils 
+        if (p1 == null) {
+            throw new StaticLinkingCheckException(new _ErrorDescriptionBuilder(
+                    "Missing mandatory ", new _DelayedOrdinal(1), " positional argument"));
+        }
+        // TODO: Standardize error message; see CallableUtils 
+        if (n1 == null) {
+            throw new StaticLinkingCheckException(new _ErrorDescriptionBuilder(
+                    "Missing mandatory named argument, ", new _DelayedJQuote("n1")));
+        }
+        
+        // TODO: Make this check easier? 
+        if (nestedContentParamNames == null || nestedContentParamNames.size() != 1) {
+            throw new StaticLinkingCheckException(
+                    MessageUtils.newBadNumberOfNestedContentParameterPassedMessage(nestedContentParamNames, 1));
+        }
+        this.nestedContentParamNames = nestedContentParamNames;
+    }
+
+    @Override
+    public StringToIndexMap getNestedContentParamNames() {
+        return nestedContentParamNames;
+    }
+    
+    @Override
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
+        env.pushElement(this);
+        try {
+            Writer out = env.getOut();
+            out.write("#foo(");
+            // TODO Bind to cfg.getOutputFormat("HTML")
+            
+            int p1Int = CallableUtils.castArgumentValueToInt(p1.eval(env), 0, false, 0, VALUE, false);
+            
+            env.interpolate(p1, htmlOutputFormat, AutoEscapingPolicy.ENABLE_IF_DEFAULT, env);
+            if (p2 != null) {
+                out.write(", ");
+                env.interpolate(p2, htmlOutputFormat, AutoEscapingPolicy.ENABLE_IF_DEFAULT, env);
+            }
+            out.write(", n1=");
+            env.interpolate(n1, htmlOutputFormat, AutoEscapingPolicy.ENABLE_IF_DEFAULT, env);
+            if (n2 != null) {
+                out.write(", n2=");
+                env.interpolate(n2, htmlOutputFormat, AutoEscapingPolicy.ENABLE_IF_DEFAULT, env);
+            }
+            out.write(") {\n");
+            
+            for (int i = 0; i < p1Int; i++) {
+                env.executeNestedContent(this, nestedContentParamNames, new TemplateModel[] { new SimpleNumber(i) });
+            }
+            
+            out.write("}-foo\n");
+            return null;
+        } finally {
+            env.popElement();
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    String dump(boolean canonical) {
+        return "<#adhocTest1 ...>";
+    }
+
+    @Override
+    public String getLabelWithoutParameters() {
+        return "#adhocTest1";
+    }
+
+    @Override
+    int getParameterCount() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+    
+    
+
+}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAdhocTest2.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAdhocTest2.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAdhocTest2.java
new file mode 100644
index 0000000..1b03af8
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAdhocTest2.java
@@ -0,0 +1,165 @@
+package org.apache.freemarker.core;
+
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.util.CallableUtils;
+import org.apache.freemarker.core.util.CommonSupplier;
+import org.apache.freemarker.core.util.StringToIndexMap;
+
+/**
+ * Example of an AST directive that's not bound to some Configuration setting.
+ */
+//TODO [FM3][FREEMARKER-99] Delete this class when we are finished.
+class ASTDirAdhocTest2 extends ASTDirective {
+    private ASTExpression countExp;
+
+    private ASTDirAdhocTest2() {
+        //
+    }
+
+    final static CommonSupplier<ASTDirective> FACTORY = new CommonSupplier<ASTDirective>() {
+        @Override
+        public ASTDirective get() throws Exception {
+            return new ASTDirAdhocTest2();
+        }
+    };
+    
+    static TemplateDirectiveModel VALUE = new TemplateDirectiveModel() {
+
+        @Override
+        public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
+                throws TemplateException, IOException {
+            throw new UnsupportedOperationException("execute not implemented");
+        }
+
+        @Override
+        public boolean isNestedContentSupported() {
+            return true;
+        }
+
+        @Override
+        public ArgumentArrayLayout getDirectiveArgumentArrayLayout() {
+            return ARGS_LAYOUT;
+        }
+
+    };
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.SINGLE_POSITIONAL_PARAMETER;
+    
+    @Override
+    public void setPositionalArgument(int position, ASTExpression valueExp) throws StaticLinkingCheckException {
+        if (position == 0) {
+            countExp = valueExp;
+        } else {
+            // TODO: Standardize error message; see CallableUtils 
+            throw new StaticLinkingCheckException(new _ErrorDescriptionBuilder(
+                  "Too many positional arguments; can only have ",
+                  ARGS_LAYOUT.getPredefinedPositionalArgumentCount(), "."));
+        }
+    }
+
+    @Override
+    public void setNamedArgument(String name, ASTExpression valueExp) throws StaticLinkingCheckException {
+        // TODO: Standardize error message; see CallableUtils 
+        throw new StaticLinkingCheckException(new _ErrorDescriptionBuilder(
+                "Unsupported parameter name ", new _DelayedJQuote(name), ". This directive has no named parameters."));
+    }
+
+    @Override
+    public void checkArgumentsAndSetNestedContentParameters(StringToIndexMap nestedContentParamNames)
+            throws StaticLinkingCheckException {
+        // TODO: Standardize error message; see CallableUtils 
+        if (countExp == null) {
+            throw new StaticLinkingCheckException(new _ErrorDescriptionBuilder(
+                    "Missing mandatory ", new _DelayedOrdinal(1), " positional argument"));
+        }
+
+        // TODO: Standardize error message; see CallableUtils
+        if (nestedContentParamNames != null) {
+            throw new StaticLinkingCheckException("This directive doesn't support nested content parameters.");
+        }
+    }
+
+    @Override
+    public StringToIndexMap getNestedContentParamNames() {
+        return null;
+    }
+    
+    @Override
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
+        env.pushElement(this);
+        try {
+            Writer out = env.getOut();
+            int count = CallableUtils.castArgumentValueToInt(countExp.eval(env), 0, false, 0, VALUE, false);
+            for (int i = 0; i < count; i++) {
+                out.write(".");
+            }
+            return null;
+        } finally {
+            env.popElement();
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    String dump(boolean canonical) {
+        return "<#adhocTest2 ...>";
+    }
+
+    @Override
+    public String getLabelWithoutParameters() {
+        return "#adhocTest2";
+    }
+
+    @Override
+    int getParameterCount() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignment.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignment.java
index 26a6d69..aba716d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignment.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignment.java
@@ -102,7 +102,7 @@ final class ASTDirAssignment extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException {
+    ASTElement[] execute(Environment env) throws TemplateException {
         final Environment.Namespace namespace;
         if (namespaceExp == null) {
             switch (scope) {
@@ -187,9 +187,9 @@ final class ASTDirAssignment extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder buf = new StringBuilder();
-        String dn = getParent() instanceof ASTDirAssignmentsContainer ? null : getASTNodeDescriptor();
+        String dn = getParent() instanceof ASTDirAssignmentsContainer ? null : getLabelWithoutParameters();
         if (dn != null) {
             if (canonical) buf.append("<");
             buf.append(dn);
@@ -217,7 +217,7 @@ final class ASTDirAssignment extends ASTDirective {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return getDirectiveName(scope);
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignmentsContainer.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignmentsContainer.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignmentsContainer.java
index 3cd1e91..b572795 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignmentsContainer.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignmentsContainer.java
@@ -44,17 +44,17 @@ final class ASTDirAssignmentsContainer extends ASTDirective {
         this.namespaceExp = namespaceExp;
         int ln = getChildCount();
         for (int i = 0; i < ln; i++) {
-            ((ASTDirAssignment) getChild(i)).setNamespaceExp(namespaceExp);
+            ((ASTDirAssignment) fastGetChild(i)).setNamespaceExp(namespaceExp);
         }
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         return getChildBuffer();
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder buf = new StringBuilder();
         if (canonical) buf.append('<');
         buf.append(ASTDirAssignment.getDirectiveName(scope));
@@ -65,7 +65,7 @@ final class ASTDirAssignmentsContainer extends ASTDirective {
                 if (i != 0) {
                     buf.append(", ");
                 }
-                ASTDirAssignment assignment = (ASTDirAssignment) getChild(i);
+                ASTDirAssignment assignment = (ASTDirAssignment) fastGetChild(i);
                 buf.append(assignment.getCanonicalForm());
             }
         } else {
@@ -103,7 +103,7 @@ final class ASTDirAssignmentsContainer extends ASTDirective {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return ASTDirAssignment.getDirectiveName(scope);
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAttemptRecoverContainer.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAttemptRecoverContainer.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAttemptRecoverContainer.java
index 19aff47..d996420 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAttemptRecoverContainer.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAttemptRecoverContainer.java
@@ -40,20 +40,20 @@ final class ASTDirAttemptRecoverContainer extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         env.visitAttemptRecover(this, attemptedSection, recoverySection);
         return null;
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (!canonical) {
-            return getASTNodeDescriptor();
+            return getLabelWithoutParameters();
         } else {
             StringBuilder buf = new StringBuilder();
-            buf.append("<").append(getASTNodeDescriptor()).append(">");
+            buf.append("<").append(getLabelWithoutParameters()).append(">");
             buf.append(getChildrenCanonicalForm());            
-            buf.append("</").append(getASTNodeDescriptor()).append(">");
+            buf.append("</").append(getLabelWithoutParameters()).append(">");
             return buf.toString();
         }
     }
@@ -76,7 +76,7 @@ final class ASTDirAttemptRecoverContainer extends ASTDirective {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#attempt";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAutoEsc.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAutoEsc.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAutoEsc.java
index b982e56..9e52f30 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAutoEsc.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAutoEsc.java
@@ -31,21 +31,21 @@ final class ASTDirAutoEsc extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         return getChildBuffer();
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
-            return "<" + getASTNodeDescriptor() + "\">" + getChildrenCanonicalForm() + "</" + getASTNodeDescriptor() + ">";
+            return "<" + getLabelWithoutParameters() + "\">" + getChildrenCanonicalForm() + "</" + getLabelWithoutParameters() + ">";
         } else {
-            return getASTNodeDescriptor();
+            return getLabelWithoutParameters();
         }
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#autoEsc";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirBreak.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirBreak.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirBreak.java
index 128e684..428f4be 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirBreak.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirBreak.java
@@ -25,17 +25,17 @@ package org.apache.freemarker.core;
 final class ASTDirBreak extends ASTDirective {
 
     @Override
-    ASTElement[] accept(Environment env) {
+    ASTElement[] execute(Environment env) {
         throw BreakOrContinueException.BREAK_INSTANCE;
     }
 
     @Override
-    protected String dump(boolean canonical) {
-        return canonical ? "<" + getASTNodeDescriptor() + "/>" : getASTNodeDescriptor();
+    String dump(boolean canonical) {
+        return canonical ? "<" + getLabelWithoutParameters() + "/>" : getLabelWithoutParameters();
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#break";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
index 6fc4c5f..f151ba5 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
@@ -21,6 +21,7 @@ package org.apache.freemarker.core;
 
 import java.io.IOException;
 import java.io.StringWriter;
+import java.io.Writer;
 
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.impl.SimpleString;
@@ -46,13 +47,19 @@ final class ASTDirCapturingAssignment extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         ASTElement[] children = getChildBuffer();
 
         TemplateModel capturedValue;
         if (children != null) {
             StringWriter out = new StringWriter();
-            env.visit(children, out);
+            Writer prevOut = env.getOut();
+            try {
+                env.setOut(out);
+                env.executeElements(children);
+            } finally {
+                env.setOut(prevOut);
+            }
             capturedValue = capturedStringToModel(out.toString());
         } else {
             capturedValue = capturedStringToModel("");
@@ -79,10 +86,10 @@ final class ASTDirCapturingAssignment extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append("<");
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         sb.append(' ');
         sb.append(varName);
         if (namespaceExp != null) {
@@ -93,7 +100,7 @@ final class ASTDirCapturingAssignment extends ASTDirective {
             sb.append('>');
             sb.append(getChildrenCanonicalForm());
             sb.append("</");
-            sb.append(getASTNodeDescriptor());
+            sb.append(getLabelWithoutParameters());
             sb.append('>');
         } else {
             sb.append(" = .nestedOutput");
@@ -102,7 +109,7 @@ final class ASTDirCapturingAssignment extends ASTDirective {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return ASTDirAssignment.getDirectiveName(scope);
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCase.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCase.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCase.java
index eca7ae4..6a4cfab 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCase.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCase.java
@@ -35,15 +35,15 @@ final class ASTDirCase extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) {
+    ASTElement[] execute(Environment env) {
         return getChildBuffer();
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         if (condition != null) {
             sb.append(' ');
             sb.append(condition.getCanonicalForm());
@@ -56,7 +56,7 @@ final class ASTDirCase extends ASTDirective {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return condition != null ? "#case" : "#default";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCompress.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCompress.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCompress.java
index 6bdda37..b03c16d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCompress.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCompress.java
@@ -34,30 +34,33 @@ final class ASTDirCompress extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         ASTElement[] childBuffer = getChildBuffer();
         if (childBuffer != null) {
             CompressWriter out = new CompressWriter(env.getOut(), 2048, false);
+            Writer prevOut = env.getOut();
             try {
-                env.visit(childBuffer, out);
+                env.setOut(out);
+                env.executeElements(childBuffer);
             } finally {
                 out.close();
+                env.setOut(prevOut);
             }
         }
         return null;
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
-            return "<" + getASTNodeDescriptor() + ">" + getChildrenCanonicalForm() + "</" + getASTNodeDescriptor() + ">";
+            return "<" + getLabelWithoutParameters() + ">" + getChildrenCanonicalForm() + "</" + getLabelWithoutParameters() + ">";
         } else {
-            return getASTNodeDescriptor();
+            return getLabelWithoutParameters();
         }
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#compress";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirContinue.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirContinue.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirContinue.java
index 4d18785..8ca7afa 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirContinue.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirContinue.java
@@ -25,17 +25,17 @@ package org.apache.freemarker.core;
 final class ASTDirContinue extends ASTDirective {
 
     @Override
-    ASTElement[] accept(Environment env) {
+    ASTElement[] execute(Environment env) {
         throw BreakOrContinueException.CONTINUE_INSTANCE;
     }
 
     @Override
-    protected String dump(boolean canonical) {
-        return canonical ? "<" + getASTNodeDescriptor() + "/>" : getASTNodeDescriptor();
+    String dump(boolean canonical) {
+        return canonical ? "<" + getLabelWithoutParameters() + "/>" : getLabelWithoutParameters();
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#continue";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
index 31ebea7..6126904 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
@@ -31,24 +31,24 @@ final class ASTDirElseOfList extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         return getChildBuffer();
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
             StringBuilder buf = new StringBuilder();
-            buf.append('<').append(getASTNodeDescriptor()).append('>');
+            buf.append('<').append(getLabelWithoutParameters()).append('>');
             buf.append(getChildrenCanonicalForm());            
             return buf.toString();
         } else {
-            return getASTNodeDescriptor();
+            return getLabelWithoutParameters();
         }
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#else";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirEscape.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirEscape.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirEscape.java
index 7a324cd..57678d3 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirEscape.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirEscape.java
@@ -47,7 +47,7 @@ class ASTDirEscape extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         return getChildBuffer();
     }
 
@@ -56,22 +56,22 @@ class ASTDirEscape extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor())
+        sb.append(getLabelWithoutParameters())
                 .append(' ').append(_StringUtils.toFTLTopLevelIdentifierReference(variable))
                 .append(" as ").append(expr.getCanonicalForm());
         if (canonical) {
             sb.append('>');
             sb.append(getChildrenCanonicalForm());
-            sb.append("</").append(getASTNodeDescriptor()).append('>');
+            sb.append("</").append(getLabelWithoutParameters()).append('>');
         }
         return sb.toString();
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#escape";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirFallback.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirFallback.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirFallback.java
index 255de11..0ddb5d6 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirFallback.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirFallback.java
@@ -27,18 +27,18 @@ import java.io.IOException;
 final class ASTDirFallback extends ASTDirective {
 
     @Override
-    ASTElement[] accept(Environment env) throws IOException, TemplateException {
+    ASTElement[] execute(Environment env) throws IOException, TemplateException {
         env.fallback();
         return null;
     }
 
     @Override
-    protected String dump(boolean canonical) {
-        return canonical ? "<" + getASTNodeDescriptor() + "/>" : getASTNodeDescriptor();
+    String dump(boolean canonical) {
+        return canonical ? "<" + getLabelWithoutParameters() + "/>" : getLabelWithoutParameters();
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#fallback";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirFlush.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirFlush.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirFlush.java
index 469eff8..0b2faed 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirFlush.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirFlush.java
@@ -27,18 +27,18 @@ import java.io.IOException;
 final class ASTDirFlush extends ASTDirective {
 
     @Override
-    ASTElement[] accept(Environment env) throws IOException {
+    ASTElement[] execute(Environment env) throws IOException {
         env.getOut().flush();
         return null;
     }
 
     @Override
-    protected String dump(boolean canonical) {
-        return canonical ? "<" + getASTNodeDescriptor() + "/>" : getASTNodeDescriptor();
+    String dump(boolean canonical) {
+        return canonical ? "<" + getLabelWithoutParameters() + "/>" : getLabelWithoutParameters();
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#flush";
     }
  

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirIfElseIfElseContainer.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirIfElseIfElseContainer.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirIfElseIfElseContainer.java
index 8a250bf..1c8fad7 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirIfElseIfElseContainer.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirIfElseIfElseContainer.java
@@ -38,10 +38,10 @@ final class ASTDirIfElseIfElseContainer extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         int ln  = getChildCount();
         for (int i = 0; i < ln; i++) {
-            ASTDirIfOrElseOrElseIf cblock = (ASTDirIfOrElseOrElseIf) getChild(i);
+            ASTDirIfOrElseOrElseIf cblock = (ASTDirIfOrElseOrElseIf) fastGetChild(i);
             ASTExpression condition = cblock.condition;
             env.replaceElementStackTop(cblock);
             if (condition == null || condition.evalToBoolean(env)) {
@@ -55,7 +55,7 @@ final class ASTDirIfElseIfElseContainer extends ASTDirective {
     ASTElement postParseCleanup(boolean stripWhitespace)
         throws ParseException {
         if (getChildCount() == 1) {
-            ASTDirIfOrElseOrElseIf cblock = (ASTDirIfOrElseOrElseIf) getChild(0);
+            ASTDirIfOrElseOrElseIf cblock = (ASTDirIfOrElseOrElseIf) fastGetChild(0);
             cblock.setLocation(getTemplate(), cblock, this);
             return cblock.postParseCleanup(stripWhitespace);
         } else {
@@ -64,23 +64,23 @@ final class ASTDirIfElseIfElseContainer extends ASTDirective {
     }
     
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
             StringBuilder buf = new StringBuilder();
             int ln = getChildCount();
             for (int i = 0; i < ln; i++) {
-                ASTDirIfOrElseOrElseIf cblock = (ASTDirIfOrElseOrElseIf) getChild(i);
+                ASTDirIfOrElseOrElseIf cblock = (ASTDirIfOrElseOrElseIf) fastGetChild(i);
                 buf.append(cblock.dump(canonical));
             }
             buf.append("</#if>");
             return buf.toString();
         } else {
-            return getASTNodeDescriptor();
+            return getLabelWithoutParameters();
         }
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#if-#elseIf-#else-container";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirIfOrElseOrElseIf.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirIfOrElseOrElseIf.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirIfOrElseOrElseIf.java
index 28178d9..cb6a8cf 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirIfOrElseOrElseIf.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirIfOrElseOrElseIf.java
@@ -44,7 +44,7 @@ final class ASTDirIfOrElseOrElseIf extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         if (condition == null || condition.evalToBoolean(env)) {
             return getChildBuffer();
         }
@@ -52,10 +52,10 @@ final class ASTDirIfOrElseOrElseIf extends ASTDirective {
     }
     
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder buf = new StringBuilder();
         if (canonical) buf.append('<');
-        buf.append(getASTNodeDescriptor());
+        buf.append(getLabelWithoutParameters());
         if (condition != null) {
             buf.append(' ');
             buf.append(condition.getCanonicalForm());
@@ -71,7 +71,7 @@ final class ASTDirIfOrElseOrElseIf extends ASTDirective {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         if (type == TYPE_ELSE) {
             return "#else";
         } else if (type == TYPE_IF) {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirImport.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirImport.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirImport.java
index 88cfeb5..9d7b522 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirImport.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirImport.java
@@ -45,7 +45,7 @@ final class ASTDirImport extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         final String importedTemplateName = importedTemplateNameExp.evalAndCoerceToPlainText(env);
         final String fullImportedTemplateName;
         try {
@@ -68,10 +68,10 @@ final class ASTDirImport extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder buf = new StringBuilder();
         if (canonical) buf.append('<');
-        buf.append(getASTNodeDescriptor());
+        buf.append(getLabelWithoutParameters());
         buf.append(' ');
         buf.append(importedTemplateNameExp.getCanonicalForm());
         buf.append(" as ");
@@ -81,7 +81,7 @@ final class ASTDirImport extends ASTDirective {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#import";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirInclude.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirInclude.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirInclude.java
index 76ce1f6..5afc30a 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirInclude.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirInclude.java
@@ -56,7 +56,7 @@ final class ASTDirInclude extends ASTDirective {
     }
     
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         final String includedTemplateName = includedTemplateNameExp.evalAndCoerceToPlainText(env);
         final String fullIncludedTemplateName;
         try {
@@ -93,10 +93,10 @@ final class ASTDirInclude extends ASTDirective {
     }
     
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder buf = new StringBuilder();
         if (canonical) buf.append('<');
-        buf.append(getASTNodeDescriptor());
+        buf.append(getLabelWithoutParameters());
         buf.append(' ');
         buf.append(includedTemplateNameExp.getCanonicalForm());
         if (ignoreMissingExp != null) {
@@ -107,7 +107,7 @@ final class ASTDirInclude extends ASTDirective {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#include";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirItems.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirItems.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirItems.java
index cf85b77..04aaf94 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirItems.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirItems.java
@@ -43,12 +43,12 @@ class ASTDirItems extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         final IterationContext iterCtx = ASTDirList.findEnclosingIterationContext(env, null);
         if (iterCtx == null) {
             // The parser should prevent this situation
             throw new TemplateException(env,
-                    getASTNodeDescriptor(), " without iteration in context");
+                    getLabelWithoutParameters(), " without iteration in context");
         }
         
         iterCtx.loopForItemsElement(env, getChildBuffer(), nestedContentParamName, nestedContentParam2Name);
@@ -61,10 +61,10 @@ class ASTDirItems extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         sb.append(" as ");
         sb.append(_StringUtils.toFTLTopLevelIdentifierReference(nestedContentParamName));
         if (nestedContentParam2Name != null) {
@@ -75,14 +75,14 @@ class ASTDirItems extends ASTDirective {
             sb.append('>');
             sb.append(getChildrenCanonicalForm());
             sb.append("</");
-            sb.append(getASTNodeDescriptor());
+            sb.append(getLabelWithoutParameters());
             sb.append('>');
         }
         return sb.toString();
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#items";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
index 8034018..201c96c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
@@ -78,7 +78,7 @@ final class ASTDirList extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         acceptWithResult(env);
         return null;
     }
@@ -117,10 +117,10 @@ final class ASTDirList extends ASTDirective {
     }
     
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder buf = new StringBuilder();
         if (canonical) buf.append('<');
-        buf.append(getASTNodeDescriptor());
+        buf.append(getLabelWithoutParameters());
         buf.append(' ');
         buf.append(listedExp.getCanonicalForm());
         if (nestedContentParamName != null) {
@@ -136,7 +136,7 @@ final class ASTDirList extends ASTDirective {
             buf.append(getChildrenCanonicalForm());
             if (!(getParent() instanceof ASTDirListElseContainer)) {
                 buf.append("</");
-                buf.append(getASTNodeDescriptor());
+                buf.append(getLabelWithoutParameters());
                 buf.append('>');
             }
         }
@@ -179,7 +179,7 @@ final class ASTDirList extends ASTDirective {
     }    
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#list";
     }
 
@@ -266,7 +266,7 @@ final class ASTDirList extends ASTDirective {
                                 nestedContentParam = iterModel.next();
                                 hasNext = iterModel.hasNext();
                                 try {
-                                    env.visit(childBuffer);
+                                    env.executeElements(childBuffer);
                                 } catch (BreakOrContinueException br) {
                                     if (br == BreakOrContinueException.BREAK_INSTANCE) {
                                         break listLoop;
@@ -279,7 +279,7 @@ final class ASTDirList extends ASTDirective {
                         // We must reuse this later, because TemplateIterableModel-s that wrap an Iterator only
                         // allow one iterator() call.
                         openedIterator = iterModel;
-                        env.visit(childBuffer);
+                        env.executeElements(childBuffer);
                     }
                 }
             } else if (listedValue instanceof TemplateHashModelEx) {
@@ -316,7 +316,7 @@ final class ASTDirList extends ASTDirective {
                                 nestedContentParam2 = kvp.getValue();
                                 hasNext = kvpIter.hasNext();
                                 try {
-                                    env.visit(childBuffer);
+                                    env.executeElements(childBuffer);
                                 } catch (BreakOrContinueException br) {
                                     if (br == BreakOrContinueException.BREAK_INSTANCE) {
                                         break listLoop;
@@ -328,7 +328,7 @@ final class ASTDirList extends ASTDirective {
                     } else {
                         // We will reuse this at the #iterms
                         openedIterator = kvpIter;
-                        env.visit(childBuffer);
+                        env.executeElements(childBuffer);
                     }
                 }
             } else if (listedValue instanceof TemplateIterableModel) {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirListElseContainer.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirListElseContainer.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirListElseContainer.java
index 686b03c..47cc9f7 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirListElseContainer.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirListElseContainer.java
@@ -37,9 +37,9 @@ class ASTDirListElseContainer extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         if (!listPart.acceptWithResult(env)) {
-            return elsePart.accept(env);
+            return elsePart.execute(env);
         }
         return null;
     }
@@ -50,23 +50,23 @@ class ASTDirListElseContainer extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
             StringBuilder buf = new StringBuilder();
             int ln = getChildCount();
             for (int i = 0; i < ln; i++) {
-                ASTElement element = getChild(i);
+                ASTElement element = fastGetChild(i);
                 buf.append(element.dump(canonical));
             }
             buf.append("</#list>");
             return buf.toString();
         } else {
-            return getASTNodeDescriptor();
+            return getLabelWithoutParameters();
         }
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#list-#else-container";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java
index 786bfcf..3c64eaa 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java
@@ -112,13 +112,13 @@ final class ASTDirMacroOrFunction extends ASTDirective implements TemplateModel
     }
 
     @Override
-    ASTElement[] accept(Environment env) {
+    ASTElement[] execute(Environment env) {
         env.visitMacroOrFunctionDefinition(this);
         return null;
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return function ? "#function" : "#macro";
     }
 
@@ -138,10 +138,10 @@ final class ASTDirMacroOrFunction extends ASTDirective implements TemplateModel
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         sb.append(' ');
         sb.append(_StringUtils.toFTLTopLevelTragetIdentifier(name));
 
@@ -236,7 +236,7 @@ final class ASTDirMacroOrFunction extends ASTDirective implements TemplateModel
         if (canonical) {
             sb.append('>');
             sb.append(getChildrenCanonicalForm());
-            sb.append("</").append(getASTNodeDescriptor()).append('>');
+            sb.append("</").append(getLabelWithoutParameters()).append('>');
         }
         return sb.toString();
     }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java
index 25344e6..df0074e 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java
@@ -42,7 +42,7 @@ final class ASTDirNested extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws IOException, TemplateException {
+    ASTElement[] execute(Environment env) throws IOException, TemplateException {
         CallPlace macroCallPlace = env.getCurrentMacroContext().callPlace;
 
         // When nestedContParamCnt < nestedContentParameters.getCollectionSize(), then we just skip calculating the
@@ -68,10 +68,10 @@ final class ASTDirNested extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         if (nestedContentParameters != null) {
             for (int i = 0; i < nestedContentParameters.size(); i++) {
                 sb.append(' ');
@@ -83,7 +83,7 @@ final class ASTDirNested extends ASTDirective {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#nested";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNoAutoEsc.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNoAutoEsc.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNoAutoEsc.java
index a3ef03d..5bbc6c7 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNoAutoEsc.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNoAutoEsc.java
@@ -31,21 +31,21 @@ final class ASTDirNoAutoEsc extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         return getChildBuffer();
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
-            return "<" + getASTNodeDescriptor() + "\">" + getChildrenCanonicalForm() + "</" + getASTNodeDescriptor() + ">";
+            return "<" + getLabelWithoutParameters() + "\">" + getChildrenCanonicalForm() + "</" + getLabelWithoutParameters() + ">";
         } else {
-            return getASTNodeDescriptor();
+            return getLabelWithoutParameters();
         }
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#noAutoEsc";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNoEscape.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNoEscape.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNoEscape.java
index 61a32b8..b3af156 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNoEscape.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNoEscape.java
@@ -31,17 +31,17 @@ class ASTDirNoEscape extends ASTDirective {
     }
     
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         return getChildBuffer();
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
-            return "<" + getASTNodeDescriptor() + '>' + getChildrenCanonicalForm()
-                    + "</" + getASTNodeDescriptor() + '>';
+            return "<" + getLabelWithoutParameters() + '>' + getChildrenCanonicalForm()
+                    + "</" + getLabelWithoutParameters() + '>';
         } else {
-            return getASTNodeDescriptor();
+            return getLabelWithoutParameters();
         }
     }
 
@@ -61,7 +61,7 @@ class ASTDirNoEscape extends ASTDirective {
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#noEscape";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirOutputFormat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirOutputFormat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirOutputFormat.java
index c2aa7b8..33912ab 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirOutputFormat.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirOutputFormat.java
@@ -34,22 +34,22 @@ final class ASTDirOutputFormat extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         return getChildBuffer();
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
-            return "<" + getASTNodeDescriptor() + " \"" + paramExp.getCanonicalForm() + "\">"
-                    + getChildrenCanonicalForm() + "</" + getASTNodeDescriptor() + ">";
+            return "<" + getLabelWithoutParameters() + " \"" + paramExp.getCanonicalForm() + "\">"
+                    + getChildrenCanonicalForm() + "</" + getLabelWithoutParameters() + ">";
         } else {
-            return getASTNodeDescriptor();
+            return getLabelWithoutParameters();
         }
     }
     
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#outputFormat";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecover.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecover.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecover.java
index 7879f18..b441eeb 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecover.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecover.java
@@ -31,24 +31,24 @@ final class ASTDirRecover extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+    ASTElement[] execute(Environment env) throws TemplateException, IOException {
         return getChildBuffer();
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         if (canonical) {
             StringBuilder buf = new StringBuilder();
-            buf.append('<').append(getASTNodeDescriptor()).append('>');
+            buf.append('<').append(getLabelWithoutParameters()).append('>');
             buf.append(getChildrenCanonicalForm());            
             return buf.toString();
         } else {
-            return getASTNodeDescriptor();
+            return getLabelWithoutParameters();
         }
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#recover";
     }
     

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java
index 1904edc..1a491f5 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java
@@ -41,7 +41,7 @@ final class ASTDirRecurse extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws IOException, TemplateException {
+    ASTElement[] execute(Environment env) throws IOException, TemplateException {
         TemplateModel node = targetNode == null ? null : targetNode.eval(env);
         if (node != null && !(node instanceof TemplateNodeModel)) {
             throw MessageUtils.newUnexpectedOperandTypeException(
@@ -75,10 +75,10 @@ final class ASTDirRecurse extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         if (targetNode != null) {
             sb.append(' ');
             sb.append(targetNode.getCanonicalForm());
@@ -92,7 +92,7 @@ final class ASTDirRecurse extends ASTDirective {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#recurse";
     }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java
index c5d23b3..5a24eef 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java
@@ -31,7 +31,7 @@ final class ASTDirReturn extends ASTDirective {
     }
 
     @Override
-    ASTElement[] accept(Environment env) throws TemplateException {
+    ASTElement[] execute(Environment env) throws TemplateException {
         if (exp != null) {
             env.setLastReturnValue(exp.eval(env));
         }
@@ -43,10 +43,10 @@ final class ASTDirReturn extends ASTDirective {
     }
 
     @Override
-    protected String dump(boolean canonical) {
+    String dump(boolean canonical) {
         StringBuilder sb = new StringBuilder();
         if (canonical) sb.append('<');
-        sb.append(getASTNodeDescriptor());
+        sb.append(getLabelWithoutParameters());
         if (exp != null) {
             sb.append(' ');
             sb.append(exp.getCanonicalForm());
@@ -56,7 +56,7 @@ final class ASTDirReturn extends ASTDirective {
     }
 
     @Override
-    String getASTNodeDescriptor() {
+    public String getLabelWithoutParameters() {
         return "#return";
     }