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

[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

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);
+        }
+        
+    }
+
+}