Merge remote-tracking branch 'origin/2.3-gae'

Conflicts:
	src/main/java/freemarker/core/DebugBreak.java
	src/main/java/freemarker/core/Environment.java
	src/main/java/freemarker/core/Expression.java
	src/main/java/freemarker/core/IfBlock.java
	src/main/java/freemarker/core/Macro.java
	src/main/java/freemarker/core/TemplateObject.java
	src/main/java/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
	src/main/java/freemarker/core/UnifiedCall.java
	src/main/javacc/FTL.jj
diff --git a/src/main/java/freemarker/cache/TemplateLookupStrategy.java b/src/main/java/freemarker/cache/TemplateLookupStrategy.java
index 0ce7c67..6b50aa9 100644
--- a/src/main/java/freemarker/cache/TemplateLookupStrategy.java
+++ b/src/main/java/freemarker/cache/TemplateLookupStrategy.java
@@ -26,7 +26,7 @@
 import freemarker.template.Template;
 
 /**
- * Finds the {@link TemplateLoader}-level (storage-level) template source for the template name witch which the template
+ * Finds the {@link TemplateLoader}-level (storage-level) template source for the template name with which the template
  * was requested (as in {@link Configuration#getTemplate(String)}). This usually means trying various
  * {@link TemplateLoader}-level template names (so called source names; see also {@link Template#getSourceName()}) that
  * were deduced from the requested name. Trying a name usually means calling
diff --git a/src/main/java/freemarker/core/Assignment.java b/src/main/java/freemarker/core/Assignment.java
index 0521250..bdce203 100644
--- a/src/main/java/freemarker/core/Assignment.java
+++ b/src/main/java/freemarker/core/Assignment.java
@@ -101,7 +101,7 @@
     }
 
     @Override
-    void accept(Environment env) throws TemplateException {
+    TemplateElement[] accept(Environment env) throws TemplateException {
         final Environment.Namespace namespace;
         if (namespaceExp == null) {
             switch (scope) {
@@ -193,6 +193,7 @@
         } else {
             namespace.put(variableName, value);
         }
+        return null;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/AssignmentInstruction.java b/src/main/java/freemarker/core/AssignmentInstruction.java
index d575ca2..87a7d56 100644
--- a/src/main/java/freemarker/core/AssignmentInstruction.java
+++ b/src/main/java/freemarker/core/AssignmentInstruction.java
@@ -35,28 +35,24 @@
 
     AssignmentInstruction(int scope) {
         this.scope = scope;
-        setRegulatedChildBufferCapacity(1);
+        setChildBufferCapacity(1);
     }
 
     void addAssignment(Assignment assignment) {
-        addRegulatedChild(assignment);
+        addChild(assignment);
     }
     
     void setNamespaceExp(Expression namespaceExp) {
         this.namespaceExp = namespaceExp;
-        int ln = getRegulatedChildCount();
+        int ln = getChildCount();
         for (int i = 0; i < ln; i++) {
-            ((Assignment) getRegulatedChild(i)).setNamespaceExp(namespaceExp);
+            ((Assignment) getChild(i)).setNamespaceExp(namespaceExp);
         }
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        int ln = getRegulatedChildCount();
-        for (int i = 0; i < ln; i++) {
-            Assignment assignment = (Assignment) getRegulatedChild(i);
-            env.visit(assignment);
-        }
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
     }
 
     @Override
@@ -66,12 +62,12 @@
         buf.append(Assignment.getDirectiveName(scope));
         if (canonical) {
             buf.append(' ');
-            int ln = getRegulatedChildCount();
+            int ln = getChildCount();
             for (int i = 0; i < ln; i++) {
                 if (i != 0) {
                     buf.append(", ");
                 }
-                Assignment assignment = (Assignment) getRegulatedChild(i);
+                Assignment assignment = (Assignment) getChild(i);
                 buf.append(assignment.getCanonicalForm());
             }
         } else {
@@ -114,12 +110,6 @@
     }
 
     @Override
-    public TemplateElement postParseCleanup(boolean stripWhitespace) throws ParseException {
-        super.postParseCleanup(stripWhitespace);
-        return this;
-    }
-
-    @Override
     boolean isNestedBlockRepeater() {
         return false;
     }
diff --git a/src/main/java/freemarker/core/AttemptBlock.java b/src/main/java/freemarker/core/AttemptBlock.java
index be82cf2..5222500 100644
--- a/src/main/java/freemarker/core/AttemptBlock.java
+++ b/src/main/java/freemarker/core/AttemptBlock.java
@@ -24,24 +24,26 @@
 import freemarker.template.TemplateException;
 
 /**
- * #attempt element; might has a nested {@link RecoveryBlock}.
+ * Holder for the attempted section of the #attempt element and of the nested #recover element ({@link RecoveryBlock}).
  */
 final class AttemptBlock extends TemplateElement {
     
-    private TemplateElement attemptBlock;
-    private RecoveryBlock recoveryBlock;
+    private TemplateElement attemptedSection;
+    private RecoveryBlock recoverySection;
     
-    AttemptBlock(TemplateElement attemptBlock, RecoveryBlock recoveryBlock) {
-        this.attemptBlock = attemptBlock;
-        this.recoveryBlock = recoveryBlock;
-        setRegulatedChildBufferCapacity(2);
-        addRegulatedChild(attemptBlock);
-        addRegulatedChild(recoveryBlock);
+    AttemptBlock(TemplateElements attemptedSectionChildren, RecoveryBlock recoverySection) {
+        TemplateElement attemptedSection = attemptedSectionChildren.asSingleElement();
+        this.attemptedSection = attemptedSection;
+        this.recoverySection = recoverySection;
+        setChildBufferCapacity(2);
+        addChild(attemptedSection); // for backward compatibility
+        addChild(recoverySection);
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        env.visitAttemptRecover(attemptBlock, recoveryBlock);
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        env.visitAttemptRecover(this, attemptedSection, recoverySection);
+        return null;
     }
 
     @Override
@@ -51,12 +53,7 @@
         } else {
             StringBuilder buf = new StringBuilder();
             buf.append("<").append(getNodeTypeSymbol()).append(">");
-            if (attemptBlock != null) {
-                buf.append(attemptBlock.getCanonicalForm());            
-            }
-            if (recoveryBlock != null) {
-                buf.append(recoveryBlock.getCanonicalForm());
-            }
+            buf.append(getChildrenCanonicalForm());            
             buf.append("</").append(getNodeTypeSymbol()).append(">");
             return buf.toString();
         }
@@ -70,7 +67,7 @@
     @Override
     Object getParameterValue(int idx) {
         if (idx != 0) throw new IndexOutOfBoundsException();
-        return recoveryBlock;
+        return recoverySection;
     }
 
     @Override
@@ -85,11 +82,6 @@
     }
     
     @Override
-    boolean isShownInStackTrace() {
-        return false;
-    }
-
-    @Override
     boolean isNestedBlockRepeater() {
         return false;
     }
diff --git a/src/main/java/freemarker/core/AutoEscBlock.java b/src/main/java/freemarker/core/AutoEscBlock.java
index ce23189..b8a75e2 100644
--- a/src/main/java/freemarker/core/AutoEscBlock.java
+++ b/src/main/java/freemarker/core/AutoEscBlock.java
@@ -28,22 +28,19 @@
  */
 final class AutoEscBlock extends TemplateElement {
     
-    AutoEscBlock(TemplateElement nestedBlock) { 
-        setNestedBlock(nestedBlock);
+    AutoEscBlock(TemplateElements children) { 
+        setChildren(children);
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        if (getNestedBlock() != null) {
-            env.visitByHiddingParent(getNestedBlock());
-        }
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
     }
 
     @Override
     protected String dump(boolean canonical) {
         if (canonical) {
-            String nested = getNestedBlock() != null ? getNestedBlock().getCanonicalForm() : "";
-            return "<" + getNodeTypeSymbol() + "\">" + nested + "</" + getNodeTypeSymbol() + ">";
+            return "<" + getNodeTypeSymbol() + "\">" + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
         } else {
             return getNodeTypeSymbol();
         }
@@ -70,8 +67,8 @@
     }
 
     @Override
-    boolean isIgnorable() {
-        return getNestedBlock() == null || getNestedBlock().isIgnorable();
+    boolean isIgnorable(boolean stripWhitespace) {
+        return getChildCount() == 0;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/BlockAssignment.java b/src/main/java/freemarker/core/BlockAssignment.java
index d86c724..6de0c8f 100644
--- a/src/main/java/freemarker/core/BlockAssignment.java
+++ b/src/main/java/freemarker/core/BlockAssignment.java
@@ -40,8 +40,8 @@
     private final int scope;
     private final MarkupOutputFormat<?> markupOutputFormat;
 
-    BlockAssignment(TemplateElement nestedBlock, String varName, int scope, Expression namespaceExp, MarkupOutputFormat<?> markupOutputFormat) {
-        setNestedBlock(nestedBlock);
+    BlockAssignment(TemplateElements children, String varName, int scope, Expression namespaceExp, MarkupOutputFormat<?> markupOutputFormat) {
+        setChildren(children);
         this.varName = varName;
         this.namespaceExp = namespaceExp;
         this.scope = scope;
@@ -49,9 +49,10 @@
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        if (getNestedBlock() != null) {
-            env.visitAndTransform(getNestedBlock(), new CaptureOutput(env), null);
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        TemplateElement[] children = getChildBuffer();
+        if (children != null) {
+            env.visitAndTransform(children, new CaptureOutput(env), null);
         } else {
             TemplateModel value = capturedStringToModel("");
             if (namespaceExp != null) {
@@ -65,6 +66,7 @@
                 env.setLocalVariable(varName, value);
             }
         }
+        return null;
     }
 
     private TemplateModel capturedStringToModel(String s) throws TemplateModelException {
@@ -134,7 +136,7 @@
         }
         if (canonical) {
             sb.append('>');
-            sb.append(getNestedBlock() == null ? "" : getNestedBlock().getCanonicalForm());
+            sb.append(getChildrenCanonicalForm());
             sb.append("</");
             sb.append(getNodeTypeSymbol());
             sb.append('>');
diff --git a/src/main/java/freemarker/core/BodyInstruction.java b/src/main/java/freemarker/core/BodyInstruction.java
index a0795c1..db7527b 100644
--- a/src/main/java/freemarker/core/BodyInstruction.java
+++ b/src/main/java/freemarker/core/BodyInstruction.java
@@ -55,9 +55,10 @@
      * I (JR) realized this thanks to some incisive comments from Daniel Dekany.
      */
     @Override
-    void accept(Environment env) throws IOException, TemplateException {
+    TemplateElement[] accept(Environment env) throws IOException, TemplateException {
         Context bodyContext = new Context(env);
         env.invokeNestedContent(bodyContext);
+        return null;
     }
 
     @Override
@@ -113,6 +114,11 @@
     }
     */
     
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
+
     class Context implements LocalContext {
         CallableInvocationContext invokingMacroContext;
         Environment.Namespace bodyVars;
diff --git a/src/main/java/freemarker/core/BoundCallable.java b/src/main/java/freemarker/core/BoundCallable.java
index 65105c5..1855c08 100644
--- a/src/main/java/freemarker/core/BoundCallable.java
+++ b/src/main/java/freemarker/core/BoundCallable.java
@@ -113,8 +113,8 @@
 
     /** For backward compatibility only; delegates to the {@link UnboundCallable}'s identical method. */
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        unboundCallable.accept(env);
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return unboundCallable.accept(env);
     }
 
     /** For backward compatibility only; delegates to the {@link UnboundCallable}'s identical method. */
diff --git a/src/main/java/freemarker/core/BreakInstruction.java b/src/main/java/freemarker/core/BreakInstruction.java
index dc76f68..475ed3d 100644
--- a/src/main/java/freemarker/core/BreakInstruction.java
+++ b/src/main/java/freemarker/core/BreakInstruction.java
@@ -25,7 +25,7 @@
 final class BreakInstruction extends TemplateElement {
 
     @Override
-    void accept(Environment env) {
+    TemplateElement[] accept(Environment env) {
         throw Break.INSTANCE;
     }
 
diff --git a/src/main/java/freemarker/core/CallableInvocationContext.java b/src/main/java/freemarker/core/CallableInvocationContext.java
index 1d0f731..d2e204b 100644
--- a/src/main/java/freemarker/core/CallableInvocationContext.java
+++ b/src/main/java/freemarker/core/CallableInvocationContext.java
@@ -19,8 +19,6 @@
 
 package freemarker.core;
 
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
@@ -37,20 +35,20 @@
 class CallableInvocationContext implements LocalContext {
     final UnboundCallable callableDefinition;
     final Environment.Namespace localVars; 
-    final TemplateElement nestedContent;
+    final TemplateElement[] nestedContentBuffer;
     final Environment.Namespace nestedContentNamespace;
     final Template nestedContentTemplate;
     final List nestedContentParameterNames;
-    final ArrayList prevLocalContextStack;
+    final LocalContextStack prevLocalContextStack;
     final CallableInvocationContext prevMacroContext;
     
     CallableInvocationContext(UnboundCallable callableDefinition,
             Environment env, 
-            TemplateElement nestedContent,
+            TemplateElement[] nestedContentBuffer,
             List nestedContentParameterNames) {
         this.callableDefinition = callableDefinition;
         this.localVars = env.new Namespace();
-        this.nestedContent = nestedContent;
+        this.nestedContentBuffer = nestedContentBuffer;
         this.nestedContentNamespace = env.getCurrentNamespace();
         this.nestedContentTemplate = env.getCurrentTemplate();
         this.nestedContentParameterNames = nestedContentParameterNames;
@@ -62,14 +60,6 @@
         return callableDefinition;
     }
 
-    void invoce(Environment env) throws TemplateException, IOException {
-        sanityCheck(env);
-        // Set default values for unspecified parameters
-        if (callableDefinition.getNestedBlock() != null) {
-            env.visit(callableDefinition.getNestedBlock());
-        }
-    }
-
     // Set default parameters, check if all the required parameters are defined.
     void sanityCheck(Environment env) throws TemplateException {
         boolean resolvedAnArg, hasUnresolvedArg;
diff --git a/src/main/java/freemarker/core/Case.java b/src/main/java/freemarker/core/Case.java
index 0897b2d..4f9c5c8 100644
--- a/src/main/java/freemarker/core/Case.java
+++ b/src/main/java/freemarker/core/Case.java
@@ -19,10 +19,6 @@
 
 package freemarker.core;
 
-import java.io.IOException;
-
-import freemarker.template.TemplateException;
-
 /**
  * Represents a case in a switch statement.
  */
@@ -33,17 +29,14 @@
     
     Expression condition;
 
-    Case(Expression matchingValue, TemplateElement nestedBlock) {
+    Case(Expression matchingValue, TemplateElements children) {
         this.condition = matchingValue;
-        setNestedBlock(nestedBlock);
+        setChildren(children);
     }
 
     @Override
-    void accept(Environment env) 
-        throws TemplateException, IOException {
-        if (getNestedBlock() != null) {
-            env.visitByHiddingParent(getNestedBlock());
-        }
+    TemplateElement[] accept(Environment env) {
+        return getChildBuffer();
     }
 
     @Override
@@ -57,7 +50,7 @@
         }
         if (canonical) {
             sb.append('>');
-            if (getNestedBlock() != null) sb.append(getNestedBlock().getCanonicalForm());
+            sb.append(getChildrenCanonicalForm());
         }
         return sb.toString();
     }
diff --git a/src/main/java/freemarker/core/Comment.java b/src/main/java/freemarker/core/Comment.java
index 94e8a92..89ac087 100644
--- a/src/main/java/freemarker/core/Comment.java
+++ b/src/main/java/freemarker/core/Comment.java
@@ -37,8 +37,9 @@
     }
 
     @Override
-    void accept(Environment env) {
+    TemplateElement[] accept(Environment env) {
         // do nothing, skip the body
+        return null;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/CompressedBlock.java b/src/main/java/freemarker/core/CompressedBlock.java
index 702b3c8..9a497e3 100644
--- a/src/main/java/freemarker/core/CompressedBlock.java
+++ b/src/main/java/freemarker/core/CompressedBlock.java
@@ -31,22 +31,23 @@
  */
 final class CompressedBlock extends TemplateElement {
 
-    CompressedBlock(TemplateElement nestedBlock) { 
-        setNestedBlock(nestedBlock);
+    CompressedBlock(TemplateElements children) { 
+        setChildren(children);
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        if (getNestedBlock() != null) {
-            env.visitAndTransform(getNestedBlock(), StandardCompress.INSTANCE, null);
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        TemplateElement[] childBuffer = getChildBuffer();
+        if (childBuffer != null) {
+            env.visitAndTransform(childBuffer, StandardCompress.INSTANCE, null);
         }
+        return null;
     }
 
     @Override
     protected String dump(boolean canonical) {
         if (canonical) {
-            String nested = getNestedBlock() != null ? getNestedBlock().getCanonicalForm() : "";
-            return "<" + getNodeTypeSymbol() + ">" + nested + "</" + getNodeTypeSymbol() + ">";
+            return "<" + getNodeTypeSymbol() + ">" + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
         } else {
             return getNodeTypeSymbol();
         }
@@ -73,8 +74,8 @@
     }
 
     @Override
-    boolean isIgnorable() {
-        return getNestedBlock() == null || getNestedBlock().isIgnorable();
+    boolean isIgnorable(boolean stripWhitespace) {
+        return getChildCount() == 0 && getParameterCount() == 0;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/ConditionalBlock.java b/src/main/java/freemarker/core/ConditionalBlock.java
index cbc1b40..3b94001 100644
--- a/src/main/java/freemarker/core/ConditionalBlock.java
+++ b/src/main/java/freemarker/core/ConditionalBlock.java
@@ -36,21 +36,19 @@
     
     final Expression condition;
     private final int type;
-    boolean isLonelyIf;
 
-    ConditionalBlock(Expression condition, TemplateElement nestedBlock, int type) {
+    ConditionalBlock(Expression condition, TemplateElements children, int type) {
         this.condition = condition;
-        setNestedBlock(nestedBlock);
+        setChildren(children);
         this.type = type;
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         if (condition == null || condition.evalToBoolean(env)) {
-            if (getNestedBlock() != null) {
-                env.visitByHiddingParent(getNestedBlock());
-            }
+            return getChildBuffer();
         }
+        return null;
     }
     
     @Override
@@ -64,10 +62,8 @@
         }
         if (canonical) {
             buf.append(">");
-            if (getNestedBlock() != null) {
-                buf.append(getNestedBlock().getCanonicalForm());
-            }
-            if (isLonelyIf) {
+            buf.append(getChildrenCanonicalForm());
+            if (!(getParentElement() instanceof IfBlock)) {
                 buf.append("</#if>");
             }
         }
diff --git a/src/main/java/freemarker/core/DebugBreak.java b/src/main/java/freemarker/core/DebugBreak.java
index 53ad26c..32efcac 100644
--- a/src/main/java/freemarker/core/DebugBreak.java
+++ b/src/main/java/freemarker/core/DebugBreak.java
@@ -33,17 +33,17 @@
 @Deprecated
 public class DebugBreak extends TemplateElement {
     public DebugBreak(TemplateElement nestedBlock) {
-        setNestedBlock(nestedBlock);
+        addChild(nestedBlock);
         copyLocationFrom(nestedBlock);
     }
     
     @Override
-    protected void accept(Environment env) throws TemplateException, IOException {
+    protected TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         if (!DebuggerService.suspendEnvironment(
-                env, this.getUnboundTemplate().getSourceName(), getNestedBlock().getBeginLine())) {
-            getNestedBlock().accept(env);
+                env, this.getUnboundTemplate().getSourceName(), getChild(0).getBeginLine())) {
+            return getChild(0).accept(env);
         } else {
-            throw new StopException(env, "Stopped by debugger");        
+            throw new StopException(env, "Stopped by debugger");
         }
     }
 
@@ -53,11 +53,11 @@
             StringBuilder sb = new StringBuilder();
             sb.append("<#-- ");
             sb.append("debug break");
-            if (getNestedBlock() == null) {
+            if (getChildCount() == 0) {
                 sb.append(" /-->");
             } else {
                 sb.append(" -->");
-                sb.append(getNestedBlock().getCanonicalForm());                
+                sb.append(getChild(0).getCanonicalForm());                
                 sb.append("<#--/ debug break -->");
             }
             return sb.toString();
diff --git a/src/main/java/freemarker/core/DollarVariable.java b/src/main/java/freemarker/core/DollarVariable.java
index 2ecab8c..1f7c933 100644
--- a/src/main/java/freemarker/core/DollarVariable.java
+++ b/src/main/java/freemarker/core/DollarVariable.java
@@ -55,7 +55,7 @@
      * Outputs the string value of the enclosed expression.
      */
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         final Object moOrStr = calculateInterpolatedStringOrMarkup(env);
         final Writer out = env.getOut();
         if (moOrStr instanceof String) {
@@ -88,6 +88,7 @@
                 moOF.output(mo, out);
             }
         }
+        return null;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/ElseOfList.java b/src/main/java/freemarker/core/ElseOfList.java
index 9741d15..3557e38 100644
--- a/src/main/java/freemarker/core/ElseOfList.java
+++ b/src/main/java/freemarker/core/ElseOfList.java
@@ -28,15 +28,13 @@
  */
 final class ElseOfList extends TemplateElement {
     
-    ElseOfList(TemplateElement block) {
-        setNestedBlock(block);
+    ElseOfList(TemplateElements children) {
+        setChildren(children);
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        if (getNestedBlock() != null) {
-            env.visitByHiddingParent(getNestedBlock());
-        }
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
     }
 
     @Override
@@ -44,9 +42,7 @@
         if (canonical) {
             StringBuilder buf = new StringBuilder();
             buf.append('<').append(getNodeTypeSymbol()).append('>');
-            if (getNestedBlock() != null) {
-                buf.append(getNestedBlock().getCanonicalForm());            
-            }
+            buf.append(getChildrenCanonicalForm());            
             return buf.toString();
         } else {
             return getNodeTypeSymbol();
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 6242cea..55a0ad9 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -41,6 +41,7 @@
 import java.util.Set;
 import java.util.TimeZone;
 
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import freemarker.cache.TemplateNameFormat;
 import freemarker.cache._CacheAPI;
 import freemarker.ext.beans.BeansWrapper;
@@ -110,7 +111,8 @@
 
     private final Configuration configuration;
     private final TemplateHashModel rootDataModel;
-    private final ArrayList/* <TemplateElement> */ instructionStack = new ArrayList();
+    private TemplateElement[] instructionStack = new TemplateElement[16];
+    private int instructionStackSize = 0;
     private final ArrayList recoveredErrorStack = new ArrayList();
 
     private TemplateNumberFormat cachedTemplateNumberFormat;
@@ -160,7 +162,7 @@
     private CallableInvocationContext currentMacroContext;
     
     private Writer out;
-    private ArrayList localContextStack;
+    private LocalContextStack localContextStack;
     private final Namespace mainNamespace;
     private Namespace globalNamespace;
     private HashMap loadedLibs;
@@ -251,6 +253,7 @@
      * 
      * @since 2.3.23
      */
+    @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "False alarm")
     public Template getCurrentTemplate() {
         return currentTemplate;
     }
@@ -263,13 +266,14 @@
      * 
      * @since 2.3.22
      */
+    @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "False alarm")
     public DirectiveCallPlace getCurrentDirectiveCallPlace() {
-        int ln = instructionStack.size();
+        int ln = instructionStackSize;
         if (ln == 0) return null;
-        TemplateElement te = (TemplateElement) instructionStack.get(ln - 1);
+        TemplateElement te = instructionStack[ln - 1];
         if (te instanceof UnifiedCall) return (UnifiedCall) te;
-        if (te instanceof Macro && ln > 1 && instructionStack.get(ln - 2) instanceof UnifiedCall) {
-            return (UnifiedCall) instructionStack.get(ln - 2);
+        if (te instanceof Macro && ln > 1 && instructionStack[ln - 2] instanceof UnifiedCall) {
+            return (UnifiedCall) instructionStack[ln - 2];
         }
         return null;
     }
@@ -317,51 +321,89 @@
     /**
      * "Visit" the template element.
      */
-    void visit(TemplateElement element)
-            throws TemplateException, IOException {
+    void visit(TemplateElement element) throws IOException, TemplateException {
+        // ATTENTION: This method body is manually "inlined" into visit(TemplateElement[]); keep them in sync!
         pushElement(element);
         try {
-            element.accept(this);
+            TemplateElement[] templateElementsToVisit = element.accept(this);
+            if (templateElementsToVisit != null) {
+                for (TemplateElement el : templateElementsToVisit) {
+                    if (el == null) {
+                        break;  // Skip unused trailing buffer capacity 
+                    }
+                    visit(el);
+                }
+            }
         } catch (TemplateException te) {
             handleTemplateException(te);
         } finally {
             popElement();
         }
+        // ATTENTION: This method body above is manually "inlined" into visit(TemplateElement[]); keep them in sync!
     }
-
+    
     /**
-     * Instead of pushing into the element stack, we replace the top element for the time the parameter element is
-     * visited, and then we restore the top element. The main purpose of this is to get rid of elements in the error
-     * stack trace that from user perspective shouldn't have a stack frame. The typical example is
-     * {@code [#if foo]...[@failsHere/]...[/#if]}, where the #if call shouldn't be in the stack trace. (Simply marking
-     * #if as hidden in stack traces would be wrong, because we still want to show #if when its test expression fails.)
+     * @param elementBuffer
+     *            The elements to visit; might contains trailing {@code null}-s. Can be {@code null}.
+     * 
+     * @since 2.3.24
      */
-    void visitByHiddingParent(TemplateElement element)
-            throws TemplateException, IOException {
-        TemplateElement parent = replaceTopElement(element);
-        try {
-            element.accept(this);
-        } catch (TemplateException te) {
-            handleTemplateException(te);
-        } finally {
-            replaceTopElement(parent);
+    final void visit(TemplateElement[] elementBuffer) throws IOException, TemplateException {
+        if (elementBuffer == null) {
+            return;
+        }
+        for (TemplateElement element : elementBuffer) {
+            if (element == null) {
+                break;  // Skip unused trailing buffer capacity 
+            }
+            
+            // ATTENTION: This part is the manually "inlining" of visit(TemplateElement[]); keep them in sync!
+            // We don't just let Hotspot to do it, as we want a hard guarantee regarding maximum stack usage. 
+            pushElement(element);
+            try {
+                TemplateElement[] templateElementsToVisit = element.accept(this);
+                if (templateElementsToVisit != null) {
+                    for (TemplateElement el : templateElementsToVisit) {
+                        if (el == null) {
+                            break;  // Skip unused trailing buffer capacity 
+                        }
+                        visit(el);
+                    }
+                }
+            } catch (TemplateException te) {
+                handleTemplateException(te);
+            } finally {
+                popElement();
+            }
+            // ATTENTION: This part above is the manually "inlining" of visit(TemplateElement[]); keep them in sync!
         }
     }
 
+    @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "Not called when stack is empty")
     private TemplateElement replaceTopElement(TemplateElement element) {
-        return (TemplateElement) instructionStack.set(instructionStack.size() - 1, element);
+        return instructionStack[instructionStackSize - 1] = element;
     }
 
     private static final TemplateModel[] NO_OUT_ARGS = new TemplateModel[0];
 
+    /**
+     * @deprecated Should be internal API
+     */
+    @Deprecated
     public void visit(final TemplateElement element,
             TemplateDirectiveModel directiveModel, Map args,
             final List bodyParameterNames) throws TemplateException, IOException {
+        visit(new TemplateElement[] { element }, directiveModel, args, bodyParameterNames);
+    }
+    
+    void visit(final TemplateElement[] childBuffer,
+            TemplateDirectiveModel directiveModel, Map args,
+            final List bodyParameterNames) throws TemplateException, IOException {
         TemplateDirectiveBody nested;
-        if (element == null) {
+        if (childBuffer == null) {
             nested = null;
         } else {
-            nested = new NestedElementTemplateDirectiveBody(element);
+            nested = new NestedElementTemplateDirectiveBody(childBuffer);
         }
         final TemplateModel[] outArgs;
         if (bodyParameterNames == null || bodyParameterNames.isEmpty()) {
@@ -386,7 +428,7 @@
             directiveModel.execute(this, args, outArgs, nested);
         } finally {
             if (outArgs.length > 0) {
-                popLocalContext();
+                localContextStack.pop();
             }
         }
     }
@@ -394,14 +436,14 @@
     /**
      * "Visit" the template element, passing the output through a TemplateTransformModel
      * 
-     * @param element
-     *            the element to visit through a transform
+     * @param elementBuffer
+     *            the element to visit through a transform; might contains trailing {@code null}-s
      * @param transform
      *            the transform to pass the element output through
      * @param args
      *            optional arguments fed to the transform
      */
-    void visitAndTransform(TemplateElement element,
+    void visitAndTransform(TemplateElement[] elementBuffer,
             TemplateTransformModel transform,
             Map args)
                     throws TemplateException, IOException {
@@ -417,9 +459,7 @@
             try {
                 if (tc == null || tc.onStart() != TransformControl.SKIP_BODY) {
                     do {
-                        if (element != null) {
-                            visitByHiddingParent(element);
-                        }
+                        visit(elementBuffer);
                     } while (tc != null && tc.afterBody() == TransformControl.REPEAT_EVALUATION);
                 }
             } catch (Throwable t) {
@@ -452,8 +492,9 @@
     /**
      * Visit a block using buffering/recovery
      */
-    void visitAttemptRecover(TemplateElement attemptBlock, RecoveryBlock recoveryBlock)
-            throws TemplateException, IOException {
+     void visitAttemptRecover(
+             AttemptBlock attemptBlock, TemplateElement attemptedSection, RecoveryBlock recoverySection)
+             throws TemplateException, IOException {
         Writer prevOut = this.out;
         StringWriter sw = new StringWriter();
         this.out = sw;
@@ -462,7 +503,7 @@
         boolean lastInAttemptBlock = inAttemptBlock;
         try {
             inAttemptBlock = true;
-            visitByHiddingParent(attemptBlock);
+            visit(attemptedSection);
         } catch (TemplateException te) {
             thrownException = te;
         } finally {
@@ -477,7 +518,7 @@
             }
             try {
                 recoveredErrorStack.add(thrownException);
-                visit(recoveryBlock);
+                visit(recoverySection);
             } finally {
                 recoveredErrorStack.remove(recoveredErrorStack.size() - 1);
             }
@@ -509,9 +550,9 @@
      */
     void invokeNestedContent(BodyInstruction.Context bodyCtx) throws TemplateException, IOException {
         CallableInvocationContext invokingMacroContext = getCurrentMacroContext();
-        ArrayList prevLocalContextStack = localContextStack;
-        TemplateElement nestedContent = invokingMacroContext.nestedContent;
-        if (nestedContent != null) {
+        LocalContextStack prevLocalContextStack = localContextStack;
+        TemplateElement[] nestedContentBuffer = invokingMacroContext.nestedContentBuffer;
+        if (nestedContentBuffer != null) {
             this.currentMacroContext = invokingMacroContext.prevMacroContext;
             
             final Namespace prevCurrentNamespace = currentNamespace;  
@@ -534,10 +575,10 @@
                 pushLocalContext(bodyCtx);
             }
             try {
-                visit(nestedContent);
+                visit(nestedContentBuffer);
             } finally {
                 if (invokingMacroContext.nestedContentParameterNames != null) {
-                    popLocalContext();
+                    localContextStack.pop();
                 }
                 this.currentMacroContext = invokingMacroContext;
                 currentNamespace = prevCurrentNamespace;
@@ -564,7 +605,7 @@
             handleTemplateException(te);
             return true;
         } finally {
-            popLocalContext();
+            localContextStack.pop();
         }
     }
 
@@ -657,7 +698,7 @@
      */
     void invoke(BoundCallable boundCallable, 
             Map namedArgs, List positionalArgs,
-            List bodyParameterNames, TemplateElement nestedBlock) throws TemplateException, IOException {
+            List bodyParameterNames, TemplateElement[] childBuffer) throws TemplateException, IOException {
         UnboundCallable unboundCallable = boundCallable.getUnboundCallable();
         if (unboundCallable == UnboundCallable.NO_OP_MACRO) {
             return;
@@ -665,13 +706,13 @@
 
         pushElement(unboundCallable);
         try {
-            final CallableInvocationContext macroCtx = new CallableInvocationContext(unboundCallable, this, nestedBlock, bodyParameterNames);
+            final CallableInvocationContext macroCtx = new CallableInvocationContext(unboundCallable, this, childBuffer, bodyParameterNames);
             setMacroContextLocalsFromArguments(macroCtx, unboundCallable, namedArgs, positionalArgs);
 
             final CallableInvocationContext prevMacroCtx = currentMacroContext;
             currentMacroContext = macroCtx;
 
-            final ArrayList prevLocalContextStack = localContextStack;
+            final LocalContextStack prevLocalContextStack = localContextStack;
             localContextStack = null;
 
             final Namespace prevCurrentNamespace = currentNamespace;
@@ -681,7 +722,8 @@
             currentTemplate = boundCallable.getTemplate();
 
             try {
-                macroCtx.invoce(this);
+                macroCtx.sanityCheck(this);
+                visit(unboundCallable.getChildBuffer());
             } catch (ReturnInstruction.Return re) {
                 // Not an error, just a <#return>
             } catch (TemplateException te) {
@@ -1830,7 +1872,7 @@
     public TemplateModel getLocalVariable(String name) throws TemplateModelException {
         if (localContextStack != null) {
             for (int i = localContextStack.size() - 1; i >= 0; i--) {
-                LocalContext lc = (LocalContext) localContextStack.get(i);
+                LocalContext lc = localContextStack.get(i);
                 TemplateModel tm = lc.getLocalVariable(name);
                 if (tm != null) {
                     return tm;
@@ -1957,7 +1999,7 @@
         }
         if (localContextStack != null) {
             for (int i = localContextStack.size() - 1; i >= 0; i--) {
-                LocalContext lc = (LocalContext) localContextStack.get(i);
+                LocalContext lc = localContextStack.get(i);
                 set.addAll(lc.getLocalVariableNames());
             }
         }
@@ -2064,11 +2106,11 @@
      */
     TemplateElement[] getInstructionStackSnapshot() {
         int requiredLength = 0;
-        int ln = instructionStack.size();
+        int ln = instructionStackSize;
 
         for (int i = 0; i < ln; i++) {
-            TemplateElement stackEl = (TemplateElement) instructionStack.get(i);
-            if (i == ln || stackEl.isShownInStackTrace()) {
+            TemplateElement stackEl = instructionStack[i];
+            if (i == ln - 1 || stackEl.isShownInStackTrace()) {
                 requiredLength++;
             }
         }
@@ -2078,8 +2120,8 @@
         TemplateElement[] result = new TemplateElement[requiredLength];
         int dstIdx = requiredLength - 1;
         for (int i = 0; i < ln; i++) {
-            TemplateElement stackEl = (TemplateElement) instructionStack.get(i);
-            if (i == ln || stackEl.isShownInStackTrace()) {
+            TemplateElement stackEl = instructionStack[i];
+            if (i == ln - 1 || stackEl.isShownInStackTrace()) {
                 result[dstIdx--] = stackEl;
             }
         }
@@ -2118,16 +2160,12 @@
 
     private void pushLocalContext(LocalContext localContext) {
         if (localContextStack == null) {
-            localContextStack = new ArrayList();
+            localContextStack = new LocalContextStack();
         }
-        localContextStack.add(localContext);
+        localContextStack.push(localContext);
     }
 
-    private void popLocalContext() {
-        localContextStack.remove(localContextStack.size() - 1);
-    }
-
-    ArrayList getLocalContextStack() {
+    LocalContextStack getLocalContextStack() {
         return localContextStack;
     }
 
@@ -2249,15 +2287,25 @@
     }
 
     private void pushElement(TemplateElement element) {
-        instructionStack.add(element);
+        final int newSize = ++instructionStackSize;
+        TemplateElement[] instructionStack = this.instructionStack;
+        if (newSize > instructionStack.length) {
+            final TemplateElement[] newInstructionStack = new TemplateElement[newSize * 2];
+            for (int i = 0; i < instructionStack.length; i++) {
+                newInstructionStack[i] = instructionStack[i]; 
+            }
+            instructionStack = newInstructionStack;
+            this.instructionStack = instructionStack;
+        }
+        instructionStack[newSize - 1] = element;
     }
 
     private void popElement() {
-        instructionStack.remove(instructionStack.size() - 1);
+        instructionStackSize--;
     }
 
     void replaceElementStackTop(TemplateElement instr) {
-        instructionStack.set(instructionStack.size() - 1, instr);
+        instructionStack[instructionStackSize - 1] = instr;
     }
 
     public TemplateNodeModel getCurrentVisitorNode() {
@@ -2686,24 +2734,24 @@
 
     final class NestedElementTemplateDirectiveBody implements TemplateDirectiveBody {
 
-        private final TemplateElement element;
+        private final TemplateElement[] childBuffer;
 
-        private NestedElementTemplateDirectiveBody(TemplateElement element) {
-            this.element = element;
+        private NestedElementTemplateDirectiveBody(TemplateElement[] childBuffer) {
+            this.childBuffer = childBuffer;
         }
 
         public void render(Writer newOut) throws TemplateException, IOException {
             Writer prevOut = out;
             out = newOut;
             try {
-                Environment.this.visit(element);
+                visit(childBuffer);
             } finally {
                 out = prevOut;
             }
         }
-
-        public TemplateElement getElement() {
-            return element;
+        
+        TemplateElement[] getChildrenBuffer() {
+            return childBuffer;
         }
 
     }
diff --git a/src/main/java/freemarker/core/EscapeBlock.java b/src/main/java/freemarker/core/EscapeBlock.java
index da33316..9f17626 100644
--- a/src/main/java/freemarker/core/EscapeBlock.java
+++ b/src/main/java/freemarker/core/EscapeBlock.java
@@ -40,17 +40,15 @@
         this.escapedExpr = escapedExpr;
     }
 
-    void setContent(TemplateElement nestedBlock) {
-        setNestedBlock(nestedBlock);
+    void setContent(TemplateElements children) {
+        setChildren(children);
         // We don't need it anymore at this point
         this.escapedExpr = null;
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        if (getNestedBlock() != null) {
-            env.visit(getNestedBlock());
-        }
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
     }
 
     Expression doEscape(Expression expression) {
@@ -66,9 +64,7 @@
                 .append(" as ").append(expr.getCanonicalForm());
         if (canonical) {
             sb.append('>');
-            if (getNestedBlock() != null) {
-                sb.append(getNestedBlock().getCanonicalForm());
-            }
+            sb.append(getChildrenCanonicalForm());
             sb.append("</").append(getNodeTypeSymbol()).append('>');
         }
         return sb.toString();
@@ -80,11 +76,6 @@
     }
     
     @Override
-    boolean isShownInStackTrace() {
-        return false;
-    }
-    
-    @Override
     int getParameterCount() {
         return 2;
     }
diff --git a/src/main/java/freemarker/core/Expression.java b/src/main/java/freemarker/core/Expression.java
index 10ff7df..fec7090 100644
--- a/src/main/java/freemarker/core/Expression.java
+++ b/src/main/java/freemarker/core/Expression.java
@@ -57,8 +57,7 @@
     // Hook in here to set the constant value if possible.
     
     @Override
-    void setLocation(UnboundTemplate unboundTemplate, int beginColumn, int beginLine, int endColumn, int endLine)
-    throws ParseException {
+    void setLocation(UnboundTemplate unboundTemplate, int beginColumn, int beginLine, int endColumn, int endLine) {
         super.setLocation(unboundTemplate, beginColumn, beginLine, endColumn, endLine);
         if (isLiteral()) {
             try {
diff --git a/src/main/java/freemarker/core/FallbackInstruction.java b/src/main/java/freemarker/core/FallbackInstruction.java
index a86f302..58d6845 100644
--- a/src/main/java/freemarker/core/FallbackInstruction.java
+++ b/src/main/java/freemarker/core/FallbackInstruction.java
@@ -26,8 +26,9 @@
 final class FallbackInstruction extends TemplateElement {
 
     @Override
-    void accept(Environment env) throws IOException, TemplateException {
+    TemplateElement[] accept(Environment env) throws IOException, TemplateException {
         env.fallback();
+        return null;
     }
 
     @Override
@@ -60,4 +61,9 @@
         return false;
     }
     
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
+
 }
diff --git a/src/main/java/freemarker/core/FlushInstruction.java b/src/main/java/freemarker/core/FlushInstruction.java
index 1657395..ff1338a 100644
--- a/src/main/java/freemarker/core/FlushInstruction.java
+++ b/src/main/java/freemarker/core/FlushInstruction.java
@@ -27,8 +27,9 @@
 final class FlushInstruction extends TemplateElement {
 
     @Override
-    void accept(Environment env) throws IOException {
+    TemplateElement[] accept(Environment env) throws IOException {
         env.getOut().flush();
+        return null;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/IfBlock.java b/src/main/java/freemarker/core/IfBlock.java
index 0bb476f..69b3484 100644
--- a/src/main/java/freemarker/core/IfBlock.java
+++ b/src/main/java/freemarker/core/IfBlock.java
@@ -31,36 +31,33 @@
 final class IfBlock extends TemplateElement {
 
     IfBlock(ConditionalBlock block) {
-        setRegulatedChildBufferCapacity(1);
+        setChildBufferCapacity(1);
         addBlock(block);
     }
 
     void addBlock(ConditionalBlock block) {
-        addRegulatedChild(block);
+        addChild(block);
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        int ln  = getRegulatedChildCount();
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        int ln  = getChildCount();
         for (int i = 0; i < ln; i++) {
-            ConditionalBlock cblock = (ConditionalBlock) getRegulatedChild(i);
+            ConditionalBlock cblock = (ConditionalBlock) getChild(i);
             Expression condition = cblock.condition;
             env.replaceElementStackTop(cblock);
             if (condition == null || condition.evalToBoolean(env)) {
-                if (cblock.getNestedBlock() != null) {
-                    env.visitByHiddingParent(cblock.getNestedBlock());
-                }
-                return;
+                return cblock.getChildBuffer();
             }
         }
+        return null;
     }
 
     @Override
     TemplateElement postParseCleanup(boolean stripWhitespace)
         throws ParseException {
-        if (getRegulatedChildCount() == 1) {
-            ConditionalBlock cblock = (ConditionalBlock) getRegulatedChild(0);
-            cblock.isLonelyIf = true;
+        if (getChildCount() == 1) {
+            ConditionalBlock cblock = (ConditionalBlock) getChild(0);
             cblock.setLocation(getUnboundTemplate(), cblock, this);
             return cblock.postParseCleanup(stripWhitespace);
         } else {
@@ -72,9 +69,9 @@
     protected String dump(boolean canonical) {
         if (canonical) {
             StringBuilder buf = new StringBuilder();
-            int ln = getRegulatedChildCount();
+            int ln = getChildCount();
             for (int i = 0; i < ln; i++) {
-                ConditionalBlock cblock = (ConditionalBlock) getRegulatedChild(i);
+                ConditionalBlock cblock = (ConditionalBlock) getChild(i);
                 buf.append(cblock.dump(canonical));
             }
             buf.append("</#if>");
@@ -103,11 +100,6 @@
     ParameterRole getParameterRole(int idx) {
         throw new IndexOutOfBoundsException();
     }
-    
-    @Override
-    boolean isShownInStackTrace() {
-        return false;
-    }
 
     @Override
     boolean isNestedBlockRepeater() {
diff --git a/src/main/java/freemarker/core/Include.java b/src/main/java/freemarker/core/Include.java
index fe36924..755a32f 100644
--- a/src/main/java/freemarker/core/Include.java
+++ b/src/main/java/freemarker/core/Include.java
@@ -118,7 +118,7 @@
     }
     
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         final String includedTemplateName = includedTemplateNameExp.evalAndCoerceToPlainText(env);
         final String fullIncludedTemplateName;
         try {
@@ -170,6 +170,7 @@
         if (includedTemplate != null) {
             env.include(includedTemplate);
         }
+        return null;
     }
     
     @Override
@@ -249,4 +250,10 @@
         return true;
     }
 */
+    
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
+    
 }
diff --git a/src/main/java/freemarker/core/Items.java b/src/main/java/freemarker/core/Items.java
index eecc15f..38bc5a6 100644
--- a/src/main/java/freemarker/core/Items.java
+++ b/src/main/java/freemarker/core/Items.java
@@ -30,13 +30,13 @@
 
     private final String loopVarName;
 
-    public Items(String loopVariableName, TemplateElement nestedBlock) {
+    Items(String loopVariableName, TemplateElements children) {
         this.loopVarName = loopVariableName;
-        setNestedBlock(nestedBlock);
+        setChildren(children);
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         final IterationContext iterCtx = IteratorBlock.findEnclosingIterationContext(env, null);
         if (iterCtx == null) {
             // The parser should prevent this situation
@@ -44,7 +44,8 @@
                     getNodeTypeSymbol(), " without iteraton in context");
         }
         
-        iterCtx.loopForItemsElement(env, getNestedBlock(), loopVarName);
+        iterCtx.loopForItemsElement(env, getChildBuffer(), loopVarName);
+        return null;
     }
 
     @Override
@@ -61,7 +62,7 @@
         sb.append(loopVarName);
         if (canonical) {
             sb.append('>');
-            if (getNestedBlock() != null) sb.append(getNestedBlock().getCanonicalForm());
+            sb.append(getChildrenCanonicalForm());
             sb.append("</");
             sb.append(getNodeTypeSymbol());
             sb.append('>');
diff --git a/src/main/java/freemarker/core/IteratorBlock.java b/src/main/java/freemarker/core/IteratorBlock.java
index 22b2164..2a4df44 100644
--- a/src/main/java/freemarker/core/IteratorBlock.java
+++ b/src/main/java/freemarker/core/IteratorBlock.java
@@ -35,7 +35,7 @@
 import freemarker.template.utility.Constants;
 
 /**
- * A #list or #foreach element.
+ * A #list (or #foreach) element, or pre-#else section of it inside a {@link ListElseContainer}.
  */
 final class IteratorBlock extends TemplateElement {
 
@@ -48,23 +48,24 @@
      *            a variable referring to a sequence or collection ("the list" from now on)
      * @param loopVarName
      *            The name of the variable that will hold the value of the current item when looping through the list.
-     * @param nestedBlock
+     * @param childrenBeforeElse
      *            The nested content to execute if the list wasn't empty; can't be {@code null}. If the loop variable
      *            was specified in the start tag, this is also what we will iterator over.
      */
     IteratorBlock(Expression listExp,
                   String loopVarName,
-                  TemplateElement nestedBlock,
+                  TemplateElements childrenBeforeElse,
                   boolean isForEach) {
         this.listExp = listExp;
         this.loopVarName = loopVarName;
-        setNestedBlock(nestedBlock);
+        setChildren(childrenBeforeElse);
         this.isForEach = isForEach;
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         acceptWithResult(env);
+        return null;
     }
     
     boolean acceptWithResult(Environment env) throws TemplateException, IOException {
@@ -88,7 +89,7 @@
      */
     static IterationContext findEnclosingIterationContext(Environment env, String loopVariableName)
             throws _MiscTemplateException {
-        ArrayList ctxStack = env.getLocalContextStack();
+        LocalContextStack ctxStack = env.getLocalContextStack();
         if (ctxStack != null) {
             for (int i = ctxStack.size() - 1; i >= 0; i--) {
                 Object ctx = ctxStack.get(i);
@@ -121,9 +122,7 @@
         }
         if (canonical) {
             buf.append(">");
-            if (getNestedBlock() != null) {
-                buf.append(getNestedBlock().getCanonicalForm());
-            }
+            buf.append(getChildrenCanonicalForm());
             if (!(getParentElement() instanceof ListElseContainer)) {
                 buf.append("</");
                 buf.append(getNodeTypeSymbol());
@@ -198,10 +197,10 @@
         }
         
         boolean accept(Environment env) throws TemplateException, IOException {
-            return executeNestedBlock(env, getNestedBlock());
+            return executeNestedContent(env, getChildBuffer());
         }
 
-        void loopForItemsElement(Environment env, TemplateElement nestedBlock, String loopVarName)
+        void loopForItemsElement(Environment env, TemplateElement[] childBuffer, String loopVarName)
                     throws NonSequenceOrCollectionException, TemplateModelException, InvalidReferenceException,
                     TemplateException, IOException {
             try {
@@ -211,7 +210,7 @@
                 }
                 alreadyEntered = true;
                 this.loopVarName = loopVarName;
-                executeNestedBlock(env, nestedBlock);
+                executeNestedContent(env, childBuffer);
             } finally {
                 this.loopVarName = null;
             }
@@ -221,13 +220,7 @@
          * Executes the given block for the {@link #listValue}: if {@link #loopVarName} is non-{@code null}, then for
          * each list item once, otherwise once if {@link #listValue} isn't empty.
          */
-        private boolean executeNestedBlock(Environment env, TemplateElement nestedBlock)
-                throws TemplateModelException, TemplateException, IOException,
-                NonSequenceOrCollectionException, InvalidReferenceException {
-            return executeNestedBlockInner(env, nestedBlock);
-        }
-
-        private boolean executeNestedBlockInner(Environment env, TemplateElement nestedBlock)
+        private boolean executeNestedContent(Environment env, TemplateElement[] childBuffer)
                 throws TemplateModelException, TemplateException, IOException, NonSequenceOrCollectionException,
                 InvalidReferenceException {
             final boolean listNotEmpty;
@@ -243,9 +236,7 @@
                             while (hasNext) {
                                 loopVar = iterModel.next();
                                 hasNext = iterModel.hasNext();
-                                if (nestedBlock != null) {
-                                    env.visitByHiddingParent(nestedBlock);
-                                }
+                                env.visit(childBuffer);
                                 index++;
                             }
                         } catch (BreakInstruction.Break br) {
@@ -256,9 +247,7 @@
                         // We must reuse this later, because TemplateCollectionModel-s that wrap an Iterator only
                         // allow one iterator() call.
                         openedIteratorModel = iterModel;
-                        if (nestedBlock != null) {
-                            env.visitByHiddingParent(nestedBlock);
-                        }
+                        env.visit(childBuffer);
                     }
                 }
             } else if (listValue instanceof TemplateSequenceModel) {
@@ -271,17 +260,13 @@
                             for (index = 0; index < size; index++) {
                                 loopVar = seqModel.get(index);
                                 hasNext = (size > index + 1);
-                                if (nestedBlock != null) {
-                                    env.visitByHiddingParent(nestedBlock);
-                                }
+                                env.visit(childBuffer);
                             }
                         } catch (BreakInstruction.Break br) {
                             // Silently exit loop
                         }
                     } else {
-                        if (nestedBlock != null) {
-                            env.visitByHiddingParent(nestedBlock);
-                        }
+                        env.visit(childBuffer);
                     }
                 }
             } else if (env.isClassicCompatible()) {
@@ -291,9 +276,7 @@
                     hasNext = false;
                 }
                 try {
-                    if (nestedBlock != null) {
-                        env.visitByHiddingParent(nestedBlock);
-                    }
+                    env.visit(childBuffer);
                 } catch (BreakInstruction.Break br) {
                     // Silently exit "loop"
                 }
diff --git a/src/main/java/freemarker/core/LibraryLoad.java b/src/main/java/freemarker/core/LibraryLoad.java
index 98a7c38..4597539 100644
--- a/src/main/java/freemarker/core/LibraryLoad.java
+++ b/src/main/java/freemarker/core/LibraryLoad.java
@@ -50,7 +50,7 @@
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         final String importedTemplateName = importedTemplateNameExp.evalAndCoerceToPlainText(env);
         final String fullImportedTemplateName;
         try {
@@ -71,6 +71,7 @@
                     "):\n", new _DelayedGetMessage(e));
         }
         env.importLib(importedTemplate, namespace);
+        return null;
     }
 
     @Override
@@ -122,4 +123,10 @@
     boolean isNestedBlockRepeater() {
         return false;
     }
+    
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
+    
 }
diff --git a/src/main/java/freemarker/core/ListElseContainer.java b/src/main/java/freemarker/core/ListElseContainer.java
index 34a67a9..1065e9e 100644
--- a/src/main/java/freemarker/core/ListElseContainer.java
+++ b/src/main/java/freemarker/core/ListElseContainer.java
@@ -28,18 +28,19 @@
     private final ElseOfList elsePart;
 
     public ListElseContainer(IteratorBlock listPart, ElseOfList elsePart) {
-        setRegulatedChildBufferCapacity(2);
-        addRegulatedChild(listPart);
-        addRegulatedChild(elsePart);
+        setChildBufferCapacity(2);
+        addChild(listPart);
+        addChild(elsePart);
         this.listPart = listPart;
         this.elsePart = elsePart;
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         if (!listPart.acceptWithResult(env)) {
-            elsePart.accept(env);
+            return elsePart.accept(env);
         }
+        return null;
     }
 
     @Override
@@ -51,9 +52,9 @@
     protected String dump(boolean canonical) {
         if (canonical) {
             StringBuilder buf = new StringBuilder();
-            int ln = getRegulatedChildCount();
+            int ln = getChildCount();
             for (int i = 0; i < ln; i++) {
-                TemplateElement element = getRegulatedChild(i);
+                TemplateElement element = getChild(i);
                 buf.append(element.dump(canonical));
             }
             buf.append("</#list>");
diff --git a/src/main/java/freemarker/core/LocalContextStack.java b/src/main/java/freemarker/core/LocalContextStack.java
new file mode 100644
index 0000000..5f556bf
--- /dev/null
+++ b/src/main/java/freemarker/core/LocalContextStack.java
@@ -0,0 +1,39 @@
+package freemarker.core;
+
+/**
+ * Class that's a little bit more efficient than using an {@code ArrayList<LocalContext>}. 
+ * 
+ * @since 2.3.24
+ */
+final class LocalContextStack {
+
+    private LocalContext[] buffer = new LocalContext[8];
+    private int size;
+
+    void push(LocalContext localContext) {
+        final int newSize = ++size;
+        LocalContext[] buffer = this.buffer;
+        if (buffer.length < newSize) {
+            final LocalContext[] newBuffer = new LocalContext[newSize * 2];
+            for (int i = 0; i < buffer.length; i++) {
+                newBuffer[i] = buffer[i];
+            }
+            buffer = newBuffer;
+            this.buffer = newBuffer;
+        }
+        buffer[newSize - 1] = localContext;
+    }
+
+    void pop() {
+        buffer[--size] = null;
+    }
+
+    public LocalContext get(int index) {
+        return buffer[index];
+    }
+    
+    public int size() {
+        return size;
+    }
+
+}
diff --git a/src/main/java/freemarker/core/MixedContent.java b/src/main/java/freemarker/core/MixedContent.java
index c7a4c6e..3306988 100644
--- a/src/main/java/freemarker/core/MixedContent.java
+++ b/src/main/java/freemarker/core/MixedContent.java
@@ -29,20 +29,28 @@
 final class MixedContent extends TemplateElement {
 
     MixedContent() { }
-
+    
+    /**
+     * @deprecated Use {@link #addChild(TemplateElement)} instead.
+     */
+    @Deprecated
     void addElement(TemplateElement element) {
-        addRegulatedChild(element);
+        addChild(element);
     }
 
+    /**
+     * @deprecated Use {@link #addChild(int, TemplateElement)} instead.
+     */
+    @Deprecated
     void addElement(int index, TemplateElement element) {
-        addRegulatedChild(index, element);
+        addChild(index, element);
     }
     
     @Override
     TemplateElement postParseCleanup(boolean stripWhitespace)
         throws ParseException {
         super.postParseCleanup(stripWhitespace);
-        return getRegulatedChildCount() == 1 ? getRegulatedChild(0) : this;
+        return getChildCount() == 1 ? getChild(0) : this;
     }
 
     /**
@@ -50,23 +58,15 @@
      * and outputs the resulting text.
      */
     @Override
-    void accept(Environment env) 
+    TemplateElement[] accept(Environment env)
         throws TemplateException, IOException {
-        int ln = getRegulatedChildCount();
-        for (int i = 0; i < ln; i++) {
-            env.visit(getRegulatedChild(i));
-        }
+        return getChildBuffer();
     }
 
     @Override
     protected String dump(boolean canonical) {
         if (canonical) {
-            StringBuilder buf = new StringBuilder();
-            int ln = getRegulatedChildCount();
-            for (int i = 0; i < ln; i++) {
-                buf.append(getRegulatedChild(i).getCanonicalForm());
-            }
-            return buf.toString();
+            return getChildrenCanonicalForm();
         } else {
             if (getParentElement() == null) {
                 return "root";
@@ -77,9 +77,9 @@
 
     @Override
     protected boolean isOutputCacheable() {
-        int ln = getRegulatedChildCount();
+        int ln = getChildCount();
         for (int i = 0; i < ln; i++) {
-            if (!getRegulatedChild(i).isOutputCacheable()) {
+            if (!getChild(i).isOutputCacheable()) {
                 return false;
             }
         }
@@ -107,13 +107,8 @@
     }
     
     @Override
-    boolean isShownInStackTrace() {
-        return false;
-    }
-    
-    @Override
-    boolean isIgnorable() {
-        return getRegulatedChildCount() == 0;
+    boolean isIgnorable(boolean stripWhitespace) {
+        return getChildCount() == 0;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/NestedContentNotSupportedException.java b/src/main/java/freemarker/core/NestedContentNotSupportedException.java
index 926fab1..a667af1 100644
--- a/src/main/java/freemarker/core/NestedContentNotSupportedException.java
+++ b/src/main/java/freemarker/core/NestedContentNotSupportedException.java
@@ -37,8 +37,9 @@
             return;
         }
         if (body instanceof NestedElementTemplateDirectiveBody) {
-            TemplateElement te = ((NestedElementTemplateDirectiveBody) body).getElement();
-            if (te == null || te instanceof ThreadInterruptionCheck) {
+            TemplateElement[] tes = ((NestedElementTemplateDirectiveBody) body).getChildrenBuffer();
+            if (tes == null || tes.length == 0
+                    || tes[0] instanceof ThreadInterruptionCheck && (tes.length == 1 || tes[1] == null)) {
                 return;
             }
         }
diff --git a/src/main/java/freemarker/core/NoAutoEscBlock.java b/src/main/java/freemarker/core/NoAutoEscBlock.java
index d59c368..7e07bca 100644
--- a/src/main/java/freemarker/core/NoAutoEscBlock.java
+++ b/src/main/java/freemarker/core/NoAutoEscBlock.java
@@ -28,22 +28,19 @@
  */
 final class NoAutoEscBlock extends TemplateElement {
     
-    NoAutoEscBlock(TemplateElement nestedBlock) { 
-        setNestedBlock(nestedBlock);
+    NoAutoEscBlock(TemplateElements children) { 
+        setChildren(children);
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        if (getNestedBlock() != null) {
-            env.visitByHiddingParent(getNestedBlock());
-        }
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
     }
 
     @Override
     protected String dump(boolean canonical) {
         if (canonical) {
-            String nested = getNestedBlock() != null ? getNestedBlock().getCanonicalForm() : "";
-            return "<" + getNodeTypeSymbol() + "\">" + nested + "</" + getNodeTypeSymbol() + ">";
+            return "<" + getNodeTypeSymbol() + "\">" + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
         } else {
             return getNodeTypeSymbol();
         }
@@ -70,8 +67,8 @@
     }
 
     @Override
-    boolean isIgnorable() {
-        return getNestedBlock() == null || getNestedBlock().isIgnorable();
+    boolean isIgnorable(boolean stripWhitespace) {
+        return getChildCount() == 0;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/NoEscapeBlock.java b/src/main/java/freemarker/core/NoEscapeBlock.java
index f9d94bd..6ac3960 100644
--- a/src/main/java/freemarker/core/NoEscapeBlock.java
+++ b/src/main/java/freemarker/core/NoEscapeBlock.java
@@ -27,21 +27,19 @@
  */
 class NoEscapeBlock extends TemplateElement {
 
-    NoEscapeBlock(TemplateElement nestedBlock) {
-        setNestedBlock(nestedBlock);
+    NoEscapeBlock(TemplateElements children) {
+        setChildren(children);
     }
     
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        if (getNestedBlock() != null) {
-            env.visit(getNestedBlock());
-        }
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
     }
 
     @Override
     protected String dump(boolean canonical) {
         if (canonical) {
-            return "<" + getNodeTypeSymbol() + '>' + getNestedBlock().getCanonicalForm()
+            return "<" + getNodeTypeSymbol() + '>' + getChildrenCanonicalForm()
                     + "</" + getNodeTypeSymbol() + '>';
         } else {
             return getNodeTypeSymbol();
diff --git a/src/main/java/freemarker/core/NumericalOutput.java b/src/main/java/freemarker/core/NumericalOutput.java
index c9028c6..3e6e53a 100644
--- a/src/main/java/freemarker/core/NumericalOutput.java
+++ b/src/main/java/freemarker/core/NumericalOutput.java
@@ -59,7 +59,7 @@
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         String s = calculateInterpolatedStringOrMarkup(env);
         Writer out = env.getOut();
         if (autoEscapeOutputFormat != null) {
@@ -67,6 +67,7 @@
         } else {
             out.write(s);
         }
+        return null;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/OutputFormatBlock.java b/src/main/java/freemarker/core/OutputFormatBlock.java
index 12afb93..1b5a746 100644
--- a/src/main/java/freemarker/core/OutputFormatBlock.java
+++ b/src/main/java/freemarker/core/OutputFormatBlock.java
@@ -30,24 +30,21 @@
     
     private final Expression paramExp;
 
-    OutputFormatBlock(TemplateElement nestedBlock, Expression paramExp) { 
+    OutputFormatBlock(TemplateElements children, Expression paramExp) { 
         this.paramExp = paramExp; 
-        setNestedBlock(nestedBlock);
+        setChildren(children);
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        if (getNestedBlock() != null) {
-            env.visitByHiddingParent(getNestedBlock());
-        }
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
     }
 
     @Override
     protected String dump(boolean canonical) {
         if (canonical) {
-            String nested = getNestedBlock() != null ? getNestedBlock().getCanonicalForm() : "";
             return "<" + getNodeTypeSymbol() + " \"" + paramExp.getCanonicalForm() + "\">"
-                    + nested + "</" + getNodeTypeSymbol() + ">";
+                    + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
         } else {
             return getNodeTypeSymbol();
         }
@@ -78,8 +75,8 @@
     }
 
     @Override
-    boolean isIgnorable() {
-        return getNestedBlock() == null || getNestedBlock().isIgnorable();
+    boolean isIgnorable(boolean stripWhitespace) {
+        return getChildCount() == 0;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/PropertySetting.java b/src/main/java/freemarker/core/PropertySetting.java
index 86309da..e975f84 100644
--- a/src/main/java/freemarker/core/PropertySetting.java
+++ b/src/main/java/freemarker/core/PropertySetting.java
@@ -110,7 +110,7 @@
     }
 
     @Override
-    void accept(Environment env) throws TemplateException {
+    TemplateElement[] accept(Environment env) throws TemplateException {
         TemplateModel mval = value.eval(env);
         String strval;
         if (mval instanceof TemplateScalarModel) {
@@ -123,6 +123,7 @@
             strval = value.evalAndCoerceToStringOrUnsupportedMarkup(env);
         }
         env.setSetting(key, strval);
+        return null;
     }
     
     @Override
diff --git a/src/main/java/freemarker/core/RecoveryBlock.java b/src/main/java/freemarker/core/RecoveryBlock.java
index 3e44468..67ff1fb 100644
--- a/src/main/java/freemarker/core/RecoveryBlock.java
+++ b/src/main/java/freemarker/core/RecoveryBlock.java
@@ -25,15 +25,13 @@
 
 final class RecoveryBlock extends TemplateElement {
     
-    RecoveryBlock(TemplateElement block) {
-        setNestedBlock(block);
+    RecoveryBlock(TemplateElements children) {
+        setChildren(children);
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
-        if (getNestedBlock() != null) {
-            env.visitByHiddingParent(getNestedBlock());
-        }
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
     }
 
     @Override
@@ -41,9 +39,7 @@
         if (canonical) {
             StringBuilder buf = new StringBuilder();
             buf.append('<').append(getNodeTypeSymbol()).append('>');
-            if (getNestedBlock() != null) {
-                buf.append(getNestedBlock().getCanonicalForm());            
-            }
+            buf.append(getChildrenCanonicalForm());            
             return buf.toString();
         } else {
             return getNodeTypeSymbol();
diff --git a/src/main/java/freemarker/core/RecurseNode.java b/src/main/java/freemarker/core/RecurseNode.java
index 456727b..1c15da8 100644
--- a/src/main/java/freemarker/core/RecurseNode.java
+++ b/src/main/java/freemarker/core/RecurseNode.java
@@ -43,7 +43,7 @@
     }
 
     @Override
-    void accept(Environment env) throws IOException, TemplateException {
+    TemplateElement[] accept(Environment env) throws IOException, TemplateException {
         TemplateModel node = targetNode == null ? null : targetNode.eval(env);
         if (node != null && !(node instanceof TemplateNodeModel)) {
             throw new NonNodeException(targetNode, node, "node", env);
@@ -71,6 +71,7 @@
         }
         
         env.recurse((TemplateNodeModel) node, (TemplateSequenceModel) nss);
+        return null;
     }
 
     @Override
@@ -123,4 +124,9 @@
         return false;
     }
     
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
+    
 }
diff --git a/src/main/java/freemarker/core/ReturnInstruction.java b/src/main/java/freemarker/core/ReturnInstruction.java
index b3d0574..51feab7 100644
--- a/src/main/java/freemarker/core/ReturnInstruction.java
+++ b/src/main/java/freemarker/core/ReturnInstruction.java
@@ -33,18 +33,15 @@
     }
 
     @Override
-    void accept(Environment env) throws TemplateException {
+    TemplateElement[] accept(Environment env) throws TemplateException {
         if (exp != null) {
             env.setLastReturnValue(exp.eval(env));
         }
-        if (nextSibling() != null) {
-            // We need to jump out using an exception.
-            throw Return.INSTANCE;
+        if (nextSibling() == null && getParentElement() instanceof Macro) {
+            // Avoid unnecessary exception throwing 
+            return null;
         }
-        if (!(getParentElement() instanceof Macro || getParentElement().getParentElement() instanceof Macro)) {
-            // Here also, we need to jump out using an exception.
-            throw Return.INSTANCE;
-        }
+        throw Return.INSTANCE;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/Sep.java b/src/main/java/freemarker/core/Sep.java
index 7550656..0ae5de2 100644
--- a/src/main/java/freemarker/core/Sep.java
+++ b/src/main/java/freemarker/core/Sep.java
@@ -28,12 +28,12 @@
  */
 class Sep extends TemplateElement {
 
-    public Sep(TemplateElement nestedBlock) {
-        setNestedBlock(nestedBlock);
+    public Sep(TemplateElements children) {
+        setChildren(children);
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         final IterationContext iterCtx = IteratorBlock.findEnclosingIterationContext(env, null);
         if (iterCtx == null) {
             // The parser should prevent this situation
@@ -42,8 +42,9 @@
         }
         
         if (iterCtx.hasNext()) {
-            env.visitByHiddingParent(getNestedBlock());
+            return getChildBuffer();
         }
+        return null;
     }
 
     @Override
@@ -58,7 +59,7 @@
         sb.append(getNodeTypeSymbol());
         if (canonical) {
             sb.append('>');
-            if (getNestedBlock() != null) sb.append(getNestedBlock().getCanonicalForm());
+            sb.append(getChildrenCanonicalForm());
             sb.append("</");
             sb.append(getNodeTypeSymbol());
             sb.append('>');
diff --git a/src/main/java/freemarker/core/StackTraceVisibility.java b/src/main/java/freemarker/core/StackTraceVisibility.java
new file mode 100644
index 0000000..6fcd877
--- /dev/null
+++ b/src/main/java/freemarker/core/StackTraceVisibility.java
@@ -0,0 +1,8 @@
+package freemarker.core;
+
+
+public enum StackTraceVisibility {
+    
+    ALWAYS, WHEN_ON_TOP, NEVER
+
+}
diff --git a/src/main/java/freemarker/core/StopInstruction.java b/src/main/java/freemarker/core/StopInstruction.java
index c6a7526..590d9f1 100644
--- a/src/main/java/freemarker/core/StopInstruction.java
+++ b/src/main/java/freemarker/core/StopInstruction.java
@@ -33,7 +33,7 @@
     }
 
     @Override
-    void accept(Environment env) throws TemplateException {
+    TemplateElement[] accept(Environment env) throws TemplateException {
         if (exp == null) {
             throw new StopException(env);
         }
diff --git a/src/main/java/freemarker/core/SwitchBlock.java b/src/main/java/freemarker/core/SwitchBlock.java
index 284aa02..2cef5ce 100644
--- a/src/main/java/freemarker/core/SwitchBlock.java
+++ b/src/main/java/freemarker/core/SwitchBlock.java
@@ -36,7 +36,7 @@
      */
     SwitchBlock(Expression searched) {
         this.searched = searched;
-        setRegulatedChildBufferCapacity(4);
+        setChildBufferCapacity(4);
     }
 
     /**
@@ -46,17 +46,17 @@
         if (cas.condition == null) {
             defaultCase = cas;
         }
-        addRegulatedChild(cas);
+        addChild(cas);
     }
 
     @Override
-    void accept(Environment env) 
+    TemplateElement[] accept(Environment env)
         throws TemplateException, IOException {
         boolean processedCase = false;
-        int ln = getRegulatedChildCount();
+        int ln = getChildCount();
         try {
             for (int i = 0; i < ln; i++) {
-                Case cas = (Case) getRegulatedChild(i);
+                Case cas = (Case) getChild(i);
                 boolean processCase = false;
 
                 // Fall through if a previous case tested true.
@@ -69,7 +69,7 @@
                             EvalUtil.CMP_OP_EQUALS, "case==", cas.condition, cas.condition, env);
                 }
                 if (processCase) {
-                    env.visitByHiddingParent(cas);
+                    env.visit(cas);
                     processedCase = true;
                 }
             }
@@ -77,9 +77,10 @@
             // If we didn't process any nestedElements, and we have a default,
             // process it.
             if (!processedCase && defaultCase != null) {
-                env.visitByHiddingParent(defaultCase);
+                env.visit(defaultCase);
             }
         } catch (BreakInstruction.Break br) {}
+        return null;
     }
 
     @Override
@@ -91,9 +92,9 @@
         buf.append(searched.getCanonicalForm());
         if (canonical) {
             buf.append('>');
-            int ln = getRegulatedChildCount();
+            int ln = getChildCount();
             for (int i = 0; i < ln; i++) {
-                Case cas = (Case) getRegulatedChild(i);
+                Case cas = (Case) getChild(i);
                 buf.append(cas.getCanonicalForm());
             }
             buf.append("</").append(getNodeTypeSymbol()).append('>');
diff --git a/src/main/java/freemarker/core/TemplateElement.java b/src/main/java/freemarker/core/TemplateElement.java
index 07407f3..916769e 100644
--- a/src/main/java/freemarker/core/TemplateElement.java
+++ b/src/main/java/freemarker/core/TemplateElement.java
@@ -31,8 +31,7 @@
 /**
  * <b>Internal API - subject to change:</b> Represent directive call, interpolation, text block, or other such
  * non-expression node in the parsed template. Some information that can be found here can be accessed through the
- * {@link Environment#getCurrentDirectiveCallPlace()}, which a published API, and thus promises backward
- * compatibility.
+ * {@link Environment#getCurrentDirectiveCallPlace()}, which a published API, and thus promises backward compatibility.
  * 
  * @deprecated This is an internal FreeMarker API with no backward compatibility guarantees, so you shouldn't depend on
  *             it.
@@ -45,44 +44,44 @@
     private TemplateElement parent;
 
     /**
-     * Used by elements that has no fixed schema for its child elements. For example, a {@code #case} can enclose any
-     * kind of elements. Only one of {@link #nestedBlock} and {@link #regulatedChildBuffer} can be non-{@code null}.
-     * This element is typically a {@link MixedContent}, at least before {@link #postParseCleanup(boolean)} (which
-     * optimizes out {@link MixedContent} with child count less than 2).
+     * Contains 1 or more nested elements with optional trailing {@code null}-s, or is {@code null} exactly if there are
+     * no nested elements.
      */
-    private TemplateElement nestedBlock;
-    
-    /**
-     * Used by elements that has a fixed schema for its child elements. For example, {@code #switch} can only have
-     * {@code #case} and {@code #default} child elements. Only one of {@link #nestedBlock} and
-     * {@link #regulatedChildBuffer} can be non-{@code null}.
-     */
-    private TemplateElement[] regulatedChildBuffer;
-    private int regulatedChildCount;
+    private TemplateElement[] childBuffer;
 
     /**
-     * The index of the element in the parent's {@link #regulatedChildBuffer} array, or 0 if this is the
-     * {@link #nestedBlock} of the parent.
+     * Contains the number of elements in the {@link #childBuffer}, not counting the trailing {@code null}-s. If this is
+     * 0, then and only then {@link #childBuffer} must be {@code null}.
+     */
+    private int childCount;
+
+    /**
+     * The index of the element in the parent's {@link #childBuffer} array.
      * 
      * @since 2.3.23
      */
     private int index;
 
     /**
-     * Processes the contents of this <tt>TemplateElement</tt> and
-     * outputs the resulting text
+     * Executes this {@link TemplateElement}. Usually should not be called directly, but through
+     * {@link Environment#visit(TemplateElement)} or a similar {@link Environment} method.
      *
-     * @param env The runtime environment
+     * @param env
+     *            The runtime environment
+     * 
+     * @return The template elements to execute (meant to be used for nested elements), or {@code null}. Can have
+     *         <em>trailing</em> {@code null}-s (unused buffer capacity). Returning the nested elements instead of
+     *         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 void accept(Environment env) throws TemplateException, IOException;
+    abstract TemplateElement[] accept(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.
+     * 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)}.
      * 
@@ -92,7 +91,7 @@
     public final String getDescription() {
         return dump(false);
     }
-    
+
     /**
      * This final implementation calls {@link #dump(boolean) dump(false)}.
      */
@@ -100,74 +99,88 @@
     public final String getCanonicalForm() {
         return dump(true);
     }
-    
-    /**
-     * Tells if the element should show up in error stack traces. If you think you need to set this to {@code false} for
-     * an element, always consider if you should use {@link Environment#visitByHiddingParent(TemplateElement)} instead.
-     * 
-     * Note that this will be ignored for the top (current) element of a stack trace, as that's always shown.
-     */
-    boolean isShownInStackTrace() {
-        return true;
+
+    final String getChildrenCanonicalForm() {
+        return getChildrenCanonicalForm(childBuffer);
     }
     
+    static String getChildrenCanonicalForm(TemplateElement[] children) {
+        if (children == null) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (TemplateElement child : children) {
+            if (child == null) {
+                break;
+            }
+            sb.append(child.getCanonicalForm());
+        }
+        return sb.toString();
+    }
+
     /**
-     * Tells if this element possibly executes its {@link #nestedBlock} for many times. This flag is useful when
-     * a template AST is modified for running time limiting (see {@link ThreadInterruptionSupportTemplatePostProcessor}).
-     * Elements that use {@link #regulatedChildBuffer} should not need this, as the insertion of the timeout checks is
-     * impossible there, given their rigid nested element schema.
+     * 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.
+     */
+    boolean isShownInStackTrace() {
+        return false;
+    }
+
+    /**
+     * Tells if this element possibly executes its nested content for many times. This flag is useful when a template
+     * AST is modified for running time limiting (see {@link ThreadInterruptionSupportTemplatePostProcessor}). Elements
+     * that use {@link #childBuffer} should not need this, as the insertion of the timeout checks is impossible there,
+     * given their rigid nested element schema.
      */
     abstract boolean isNestedBlockRepeater();
 
     /**
-     * Brings the implementation of {@link #getCanonicalForm()} and {@link #getDescription()} to a single place.
-     * Don't call those methods in method on {@code this}, because that will result in infinite recursion! 
+     * Brings the implementation of {@link #getCanonicalForm()} and {@link #getDescription()} 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()}.
+     * @param canonical
+     *            if {@code true}, it calculates the return value of {@link #getCanonicalForm()}, otherwise of
+     *            {@link #getDescription()}.
      */
     abstract protected String dump(boolean canonical);
-    
-// Methods to implement TemplateNodeModel 
+
+    // Methods to implement TemplateNodeModel
 
     public TemplateNodeModel getParentNode() {
-//        return parent;
-         return null;
+        // return parent;
+        return null;
     }
-    
+
     public String getNodeNamespace() {
         return null;
     }
-    
+
     public String getNodeType() {
         return "element";
     }
-    
+
     public TemplateSequenceModel getChildNodes() {
-        if (regulatedChildBuffer != null) {
-            final SimpleSequence seq = new SimpleSequence(regulatedChildCount);
-            for (int i = 0; i < regulatedChildCount; i++) {
-                seq.add(regulatedChildBuffer[i]);
+        if (childBuffer != null) {
+            final SimpleSequence seq = new SimpleSequence(childCount);
+            for (int i = 0; i < childCount; i++) {
+                seq.add(childBuffer[i]);
             }
             return seq;
+        } else {
+            return new SimpleSequence(0);
         }
-        SimpleSequence result = new SimpleSequence(1);
-        if (nestedBlock != null) {
-            result.add(nestedBlock);
-        } 
-        return result;
     }
-    
+
     public String getNodeName() {
         String classname = this.getClass().getName();
         int shortNameOffset = classname.lastIndexOf('.') + 1;
         return classname.substring(shortNameOffset);
     }
-    
-    // Methods so that we can implement the Swing TreeNode API.    
+
+    // Methods so that we can implement the Swing TreeNode API.
 
     public boolean isLeaf() {
-        return nestedBlock == null && regulatedChildCount == 0;
+        return childCount == 0;
     }
 
     /**
@@ -179,31 +192,16 @@
     }
 
     public int getIndex(TemplateElement node) {
-        if (nestedBlock instanceof MixedContent) {
-            return nestedBlock.getIndex(node);
-        }
-        if (nestedBlock != null) {
-            if (node == nestedBlock) {
-                return 0;
-            }
-        } else {
-            for (int i = 0; i < regulatedChildCount; i++) {
-                if (regulatedChildBuffer[i].equals(node)) { 
-                    return i;
-                }
+        for (int i = 0; i < childCount; i++) {
+            if (childBuffer[i].equals(node)) {
+                return i;
             }
         }
         return -1;
     }
 
     public int getChildCount() {
-        if (nestedBlock instanceof MixedContent) {
-            return nestedBlock.getChildCount();
-        }
-        if (nestedBlock != null) {
-            return 1;
-        }
-        return regulatedChildCount;
+        return childCount;
     }
 
     /**
@@ -211,57 +209,37 @@
      * {@link MixedContent}.
      */
     public Enumeration children() {
-        if (nestedBlock instanceof MixedContent) {
-            return nestedBlock.children();
-        }
-        if (nestedBlock != null) {
-            return Collections.enumeration(Collections.singletonList(nestedBlock));
-        } else if (regulatedChildBuffer != null) {
-            return new _ArrayEnumeration(regulatedChildBuffer, regulatedChildCount);
-        }
-        return Collections.enumeration(Collections.EMPTY_LIST);
+        return childBuffer != null
+                ? new _ArrayEnumeration(childBuffer, childCount)
+                : Collections.enumeration(Collections.EMPTY_LIST);
     }
 
+    /**
+     * @deprecated Internal API - even internally, use {@link #getChild(int)} instead.
+     */
+    @Deprecated
     public TemplateElement getChildAt(int index) {
-        if (nestedBlock instanceof MixedContent) {
-            return nestedBlock.getChildAt(index);
+        if (childCount == 0) {
+            throw new IndexOutOfBoundsException("Template element has no children");
         }
-        if (nestedBlock != null) {
-            if (index == 0) {
-                return nestedBlock;
-            }
-            throw new ArrayIndexOutOfBoundsException("invalid index");
-        } else if (regulatedChildCount != 0) {
-            try {
-                return regulatedChildBuffer[index];
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // nestedElements was a List earlier, so we emulate the same kind of exception
-                throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + regulatedChildCount);
-            }
+        try {
+            return childBuffer[index];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // nestedElements was a List earlier, so we emulate the same kind of exception
+            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + childCount);
         }
-        throw new ArrayIndexOutOfBoundsException("Template element has no children");
     }
 
     public void setChildAt(int index, TemplateElement element) {
-        if (nestedBlock instanceof MixedContent) {
-            nestedBlock.setChildAt(index, element);
-        } else if (nestedBlock != null) {
-            if (index == 0) {
-                nestedBlock = element;
-                element.index = 0;
-                element.parent = this;
-            } else {
-                throw new IndexOutOfBoundsException("invalid index");
-            }
-        } else if (regulatedChildBuffer != null) {
-            regulatedChildBuffer[index] = element;
+        if (index < childCount && index >= 0) {
+            childBuffer[index] = element;
             element.index = index;
             element.parent = this;
         } else {
-            throw new IndexOutOfBoundsException("element has no children");
+            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + childCount);
         }
     }
-    
+
     /**
      * The element whose child this element is, or {@code null} if this is the root node.
      * 
@@ -272,29 +250,42 @@
         return parent;
     }
     
-    final void setRegulatedChildBufferCapacity(int capacity) {
-        int ln = regulatedChildCount;
-        TemplateElement[] newRegulatedChildBuffer = new TemplateElement[capacity];
-        for (int i = 0; i < ln; i++) {
-            newRegulatedChildBuffer[i] = regulatedChildBuffer[i];
-        }
-        regulatedChildBuffer = newRegulatedChildBuffer;
-    }
-    
-    final void addRegulatedChild(TemplateElement nestedElement) {
-        addRegulatedChild(regulatedChildCount, nestedElement);
+    /**
+     * The element whose child this element is, or {@code null} if this is the root node.
+     */
+    final TemplateElement getParentElement() {
+        return parent;
     }
 
-    final void addRegulatedChild(int index, TemplateElement nestedElement) {
-        final int lRegulatedChildCount = regulatedChildCount;
-        
-        TemplateElement[] lRegulatedChildBuffer = regulatedChildBuffer;
+    final void setChildBufferCapacity(int capacity) {
+        int ln = childCount;
+        TemplateElement[] newRegulatedChildBuffer = new TemplateElement[capacity];
+        for (int i = 0; i < ln; i++) {
+            newRegulatedChildBuffer[i] = childBuffer[i];
+        }
+        childBuffer = newRegulatedChildBuffer;
+    }
+
+    /**
+     * Inserts a new nested element after the last nested element.
+     */
+    final void addChild(TemplateElement nestedElement) {
+        addChild(childCount, nestedElement);
+    }
+
+    /**
+     * Inserts a new nested element at the given index, which can also be one higher than the current highest index.
+     */
+    final void addChild(int index, TemplateElement nestedElement) {
+        final int lRegulatedChildCount = childCount;
+
+        TemplateElement[] lRegulatedChildBuffer = childBuffer;
         if (lRegulatedChildBuffer == null) {
             lRegulatedChildBuffer = new TemplateElement[INITIAL_REGULATED_CHILD_BUFFER_CAPACITY];
-            regulatedChildBuffer = lRegulatedChildBuffer;
+            childBuffer = lRegulatedChildBuffer;
         } else if (lRegulatedChildCount == lRegulatedChildBuffer.length) {
-            setRegulatedChildBufferCapacity(lRegulatedChildCount != 0 ? lRegulatedChildCount * 2 : 1);
-            lRegulatedChildBuffer = regulatedChildBuffer; 
+            setChildBufferCapacity(lRegulatedChildCount != 0 ? lRegulatedChildCount * 2 : 1);
+            lRegulatedChildBuffer = childBuffer;
         }
         // At this point: nestedElements == this.nestedElements, and has sufficient capacity.
 
@@ -306,40 +297,42 @@
         nestedElement.index = index;
         nestedElement.parent = this;
         lRegulatedChildBuffer[index] = nestedElement;
-        regulatedChildCount = lRegulatedChildCount + 1;
+        childCount = lRegulatedChildCount + 1;
     }
-    
-    final int getRegulatedChildCount() {
-       return regulatedChildCount; 
+
+    final TemplateElement getChild(int index) {
+        return childBuffer[index];
     }
-    
-    final TemplateElement getRegulatedChild(int index) {
-        return regulatedChildBuffer[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.
+     */
+    final TemplateElement[] getChildBuffer() {
+        return childBuffer;
     }
-    
+
+    /**
+     * @param buffWithCnt Maybe {@code null}
+     * 
+     * @since 2.3.24
+     */
+    final void setChildren(TemplateElements buffWithCnt) {
+        TemplateElement[] childBuffer = buffWithCnt.getBuffer();
+        int childCount = buffWithCnt.getCount();
+        for (int i = 0; i < childCount; i++) {
+            TemplateElement child = childBuffer[i];
+            child.index = i;
+            child.parent = this;
+        }
+        this.childBuffer = childBuffer;
+        this.childCount = childCount;
+    }
+
     final int getIndex() {
         return index;
     }
-    
-    /**
-     * The element whose child this element is, or {@code null} if this is the root node.
-     */
-    final TemplateElement getParentElement() {
-        return parent;
-    }
-    
-    final TemplateElement getNestedBlock() {
-        return nestedBlock;
-    }
 
-    final void setNestedBlock(TemplateElement nestedBlock) {
-        if (nestedBlock != null) {
-            nestedBlock.parent = this;
-            nestedBlock.index = 0;
-        }
-        this.nestedBlock = nestedBlock;
-    }
-    
     /**
      * This is a special case, because a root element is not contained in another element, so we couldn't set the
      * private fields.
@@ -350,7 +343,7 @@
     }
 
     /**
-     * Walk the AST subtree rooted by this element, and do simplifications where possible, also remove superfluous
+     * Walk the AST subtree rooted by this element, and do simplifications where possible, also removes superfluous
      * whitespace.
      * 
      * @param stripWhitespace
@@ -361,56 +354,63 @@
      *         is the duty of the caller, not of this method.
      */
     TemplateElement postParseCleanup(boolean stripWhitespace) throws ParseException {
-        int regulatedChildCount = this.regulatedChildCount;
-        if (regulatedChildCount != 0) {
-            for (int i = 0; i < regulatedChildCount; i++) {
-                TemplateElement te = regulatedChildBuffer[i];
+        int childCount = this.childCount;
+        if (childCount != 0) {
+            for (int i = 0; i < childCount; i++) {
+                TemplateElement te = childBuffer[i];
+                
+                /*
+                // Assertion:
+                if (te.getIndex() != i) {
+                    throw new BugException("Invalid index " + te.getIndex() + " (expected: "
+                            + i + ") for: " + te.dump(false));
+                }
+                if (te.getParent() != this) {
+                    throw new BugException("Invalid parent " + te.getParent() + " (expected: "
+                            + this.dump(false) + ") for: " + te.dump(false));
+                }
+                */
+                
                 te = te.postParseCleanup(stripWhitespace);
-                regulatedChildBuffer[i] = te;
+                childBuffer[i] = te;
                 te.parent = this;
                 te.index = i;
             }
-            if (stripWhitespace) {
-                for (int i = 0; i < regulatedChildCount; i++) {
-                    TemplateElement te = regulatedChildBuffer[i];
-                    if (te.isIgnorable()) {
-                        regulatedChildCount--;
-                        for (int j = i; j < regulatedChildCount; j++) {
-                            final TemplateElement te2 = regulatedChildBuffer[j  + 1];
-                            regulatedChildBuffer[j] = te2;
-                            te2.index = j;
-                        }
-                        regulatedChildBuffer[regulatedChildCount] = null;
-                        this.regulatedChildCount = regulatedChildCount;
-                        i--;
+            for (int i = 0; i < childCount; i++) {
+                TemplateElement te = childBuffer[i];
+                if (te.isIgnorable(stripWhitespace)) {
+                    childCount--;
+                    // As later isIgnorable calls might investigates the siblings, we have to move all the items now. 
+                    for (int j = i; j < childCount; j++) {
+                        final TemplateElement te2 = childBuffer[j + 1];
+                        childBuffer[j] = te2;
+                        te2.index = j;
                     }
+                    childBuffer[childCount] = null;
+                    this.childCount = childCount;
+                    i--;
                 }
             }
-            if (regulatedChildCount < regulatedChildBuffer.length
-                    && regulatedChildCount <= regulatedChildBuffer.length * 3 / 4) {
-                TemplateElement[] trimmedregulatedChildBuffer = new TemplateElement[regulatedChildCount];
-                for (int i = 0; i < regulatedChildCount; i++) {
-                    trimmedregulatedChildBuffer[i] = regulatedChildBuffer[i];
+            if (childCount == 0) {
+                childBuffer = null;
+            } else if (childCount < childBuffer.length
+                    && childCount <= childBuffer.length * 3 / 4) {
+                TemplateElement[] trimmedChildBuffer = new TemplateElement[childCount];
+                for (int i = 0; i < childCount; i++) {
+                    trimmedChildBuffer[i] = childBuffer[i];
                 }
-                regulatedChildBuffer = trimmedregulatedChildBuffer;
-            }
-        } else if (nestedBlock != null) {
-            nestedBlock = nestedBlock.postParseCleanup(stripWhitespace);
-            if (nestedBlock.isIgnorable()) {
-                nestedBlock = null;
-            } else {
-                nestedBlock.parent = this;
+                childBuffer = trimmedChildBuffer;
             }
         }
         return this;
     }
 
-    boolean isIgnorable() {
+    boolean isIgnorable(boolean stripWhitespace) {
         return false;
     }
 
-// The following methods exist to support some fancier tree-walking 
-// and were introduced to support the whitespace cleanup feature in 2.2
+    // The following methods exist to support some fancier tree-walking
+    // and were introduced to support the whitespace cleanup feature in 2.2
 
     TemplateElement prevTerminalNode() {
         TemplateElement prev = previousSibling();
@@ -436,41 +436,29 @@
         if (parent == null) {
             return null;
         }
-        return index > 0 ? parent.regulatedChildBuffer[index - 1] : null;
+        return index > 0 ? parent.childBuffer[index - 1] : null;
     }
 
     TemplateElement nextSibling() {
         if (parent == null) {
             return null;
         }
-        return index + 1 < parent.regulatedChildCount ? parent.regulatedChildBuffer[index + 1] : null;
+        return index + 1 < parent.childCount ? parent.childBuffer[index + 1] : null;
     }
 
     private TemplateElement getFirstChild() {
-        if (nestedBlock != null) {
-            return nestedBlock;
-        }
-        if (regulatedChildCount == 0) {
-            return null;
-        }
-        return regulatedChildBuffer[0];
+        return childCount == 0 ? null : childBuffer[0];
     }
 
     private TemplateElement getLastChild() {
-        if (nestedBlock != null) {
-            return nestedBlock;
-        }
-        final int regulatedChildCount = this.regulatedChildCount;
-        if (regulatedChildCount == 0) {
-            return null;
-        }
-        return regulatedChildBuffer[regulatedChildCount - 1];
+        final int regulatedChildCount = this.childCount;
+        return regulatedChildCount == 0 ? null : childBuffer[regulatedChildCount - 1];
     }
 
     private TemplateElement getFirstLeaf() {
         TemplateElement te = this;
         while (!te.isLeaf() && !(te instanceof Macro) && !(te instanceof BlockAssignment)) {
-             // A macro or macro invocation is treated as a leaf here for special reasons
+            // A macro or macro invocation is treated as a leaf here for special reasons
             te = te.getFirstChild();
         }
         return te;
@@ -492,20 +480,28 @@
     boolean isOutputCacheable() {
         return false;
     }
-    
+
+    boolean isChildrenOutputCacheable() {
+        int ln = childCount;
+        for (int i = 0; i < ln; i++) {
+            if (!childBuffer[i].isOutputCacheable()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     /**
-     * determines whether this element's presence on a line 
-     * indicates that we should not strip opening whitespace
-     * in the post-parse whitespace gobbling step.
+     * determines whether this element's presence on a line indicates that we should not strip opening whitespace in the
+     * post-parse whitespace gobbling step.
      */
     boolean heedsOpeningWhitespace() {
         return false;
     }
 
     /**
-     * determines whether this element's presence on a line 
-     * indicates that we should not strip trailing whitespace
-     * in the post-parse whitespace gobbling step.
+     * determines whether this element's presence on a line indicates that we should not strip trailing whitespace in
+     * the post-parse whitespace gobbling step.
      */
     boolean heedsTrailingWhitespace() {
         return false;
diff --git a/src/main/java/freemarker/core/TemplateElementArrayBuilder.java b/src/main/java/freemarker/core/TemplateElementArrayBuilder.java
new file mode 100644
index 0000000..70a990b
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateElementArrayBuilder.java
@@ -0,0 +1,80 @@
+package freemarker.core;
+
+import freemarker.template.utility.CollectionUtils;
+
+/**
+ * Holds an buffer (array) of {@link TemplateElement}-s with the count of the utilized items in it. The un-utilized tail
+ * of the array must only contain {@code null}-s.
+ * 
+ * @since 2.3.24
+ */
+class TemplateElements {
+    
+    static final TemplateElements EMPTY = new TemplateElements(null, 0);
+
+    private final TemplateElement[] buffer;
+    private final int count;
+
+    /**
+     * @param buffer
+     *            The buffer; {@code null} exactly if {@code count} is 0.
+     * @param count
+     *            The number of utilized buffer elements; if 0, then {@code null} must be {@code null}.
+     */
+    TemplateElements(TemplateElement[] buffer, int count) {
+        /*
+        // Assertion:
+        if (count == 0 && buffer != null) {
+            throw new IllegalArgumentException(); 
+        }
+        */
+        
+        this.buffer = buffer;
+        this.count = count;
+    }
+
+    TemplateElement[] getBuffer() {
+        return buffer;
+    }
+
+    int getCount() {
+        return count;
+    }
+    
+    TemplateElement getLast() {
+        return buffer != null ? buffer[count - 1] : null;
+    }
+    
+    /**
+     * Used for some backward compatibility hacks.
+     */
+    TemplateElement asSingleElement() {
+        if (count == 0) {
+            return new TextBlock(CollectionUtils.EMPTY_CHAR_ARRAY, false); 
+        } else {
+            TemplateElement first = buffer[0];
+            if (count == 1) {
+                return first;
+            } else {
+                MixedContent mixedContent = new MixedContent();
+                mixedContent.setChildren(this);
+                mixedContent.setLocation(first.getUnboundTemplate(), first, getLast());
+                return mixedContent;
+            }
+        }
+    }
+    
+    /**
+     * Used for some backward compatibility hacks.
+     */
+    MixedContent asMixedContent() {
+        MixedContent mixedContent = new MixedContent();
+        if (count != 0) {
+            TemplateElement first = buffer[0];
+            mixedContent.setChildren(this);
+            mixedContent.setLocation(first.getUnboundTemplate(), first, getLast());
+        }
+        return mixedContent;
+    }
+
+}
diff --git a/src/main/java/freemarker/core/TemplateElementsToVisit.java b/src/main/java/freemarker/core/TemplateElementsToVisit.java
new file mode 100644
index 0000000..6159f13
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateElementsToVisit.java
@@ -0,0 +1,30 @@
+package freemarker.core;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Used as the return value of {@link TemplateElement#accept(Environment)} when the invoked element has nested elements
+ * to invoke. It would be more natural to invoke child elements before returning from
+ * {@link TemplateElement#accept(Environment)}, however, if there's nothing to do after the child elements were invoked,
+ * that would mean wasting stack space.
+ * 
+ * @since 2.3.24
+ */
+class TemplateElementsToVisit {
+
+    private final Collection<TemplateElement> templateElements;
+
+    TemplateElementsToVisit(Collection<TemplateElement> templateElements) {
+        this.templateElements = null != templateElements ? templateElements : Collections.<TemplateElement> emptyList();
+    }
+
+    TemplateElementsToVisit(TemplateElement nestedBlock) {
+        this(Collections.singleton(nestedBlock));
+    }
+
+    Collection<TemplateElement> getTemplateElements() {
+        return templateElements;
+    }
+    
+}
diff --git a/src/main/java/freemarker/core/TemplateObject.java b/src/main/java/freemarker/core/TemplateObject.java
index 2333645..805af1c 100644
--- a/src/main/java/freemarker/core/TemplateObject.java
+++ b/src/main/java/freemarker/core/TemplateObject.java
@@ -19,6 +19,7 @@
 
 package freemarker.core;
 
+import freemarker.template.Template;
 
 /**
  * <b>Internal API - subject to change:</b> Represent a node in the parsed template (either a {@link Expression} or a
@@ -41,28 +42,34 @@
      *  by a negative line numbers, starting from this constant as line 1. */
     static final int RUNTIME_EVAL_LINE_DISPLACEMENT = -1000000000;  
 
-    final void setLocation(UnboundTemplate unboundTemplate, Token begin, Token end)
-    throws ParseException {
+    final void setLocation(UnboundTemplate unboundTemplate, Token begin, Token end) {
         setLocation(unboundTemplate, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
     }
 
-    final void setLocation(UnboundTemplate unboundTemplate, Token begin, TemplateObject end)
-    throws ParseException {
+    final void setLocation(UnboundTemplate unboundTemplate, Token tagBegin, Token tagEnd, TemplateElements children) {
+        TemplateElement lastChild = children.getLast();
+        if (lastChild != null) {
+            // [<#if exp>children]<#else>
+            setLocation(unboundTemplate, tagBegin, lastChild);
+        } else {
+            // [<#if exp>]<#else>
+            setLocation(unboundTemplate, tagBegin, tagEnd);
+        }
+    }
+    
+    final void setLocation(UnboundTemplate unboundTemplate, Token begin, TemplateObject end) {
+        setLocation(unboundTemplate, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
+    }
+    
+    final void setLocation(UnboundTemplate unboundTemplate, TemplateObject begin, Token end) {
         setLocation(unboundTemplate, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
     }
 
-    final void setLocation(UnboundTemplate unboundTemplate, TemplateObject begin, Token end)
-    throws ParseException {
+    final void setLocation(UnboundTemplate unboundTemplate, TemplateObject begin, TemplateObject end) {
         setLocation(unboundTemplate, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
     }
 
-    final void setLocation(UnboundTemplate unboundTemplate, TemplateObject begin, TemplateObject end)
-    throws ParseException {
-        setLocation(unboundTemplate, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
-    }
-
-    void setLocation(UnboundTemplate unboundTemplate, int beginColumn, int beginLine, int endColumn, int endLine)
-    throws ParseException {
+    void setLocation(UnboundTemplate unboundTemplate, int beginColumn, int beginLine, int endColumn, int endLine) {
         this.unboundTemplate = unboundTemplate;
         this.beginColumn = beginColumn;
         this.beginLine = beginLine;
diff --git a/src/main/java/freemarker/core/TextBlock.java b/src/main/java/freemarker/core/TextBlock.java
index 688ddae..3a7c78f 100644
--- a/src/main/java/freemarker/core/TextBlock.java
+++ b/src/main/java/freemarker/core/TextBlock.java
@@ -21,14 +21,14 @@
 
 import java.io.IOException;
 
+import freemarker.template.utility.CollectionUtils;
 import freemarker.template.utility.StringUtil;
 
 /**
  * A TemplateElement representing a block of plain text.
  */
 public final class TextBlock extends TemplateElement {
-    private static final char[] EMPTY_CHAR_ARRAY = new char[0];
-    static final TextBlock EMPTY_BLOCK = new TextBlock(EMPTY_CHAR_ARRAY, false);
+    
     // We're using char[] instead of String for storing the text block because
     // Writer.write(String) involves copying the String contents to a char[] 
     // using String.getChars(), and then calling Writer.write(char[]). By
@@ -45,7 +45,7 @@
         this(text.toCharArray(), unparsed);
     }
 
-    private TextBlock(char[] text, boolean unparsed) {
+    TextBlock(char[] text, boolean unparsed) {
         this.text = text;
         this.unparsed = unparsed;
     }
@@ -58,9 +58,10 @@
      * Simply outputs the text.
      */
     @Override
-    public void accept(Environment env) 
+    public TemplateElement[] accept(Environment env)
     throws IOException {
         env.getOut().write(text);
+        return null;
     }
 
     @Override
@@ -107,7 +108,10 @@
         if (!stripWhitespace || text.length == 0 ) {
             return this;
         }
-        if (getParentElement().getParentElement() == null && previousSibling() == null) return this;
+        TemplateElement parentElement = getParentElement();
+        if (isTopLevelTextIfParentIs(parentElement) && previousSibling() == null) {
+            return this;
+        }
         if (!deliberateLeftTrim) {
             trailingCharsToStrip = trailingCharsToStrip();
         }
@@ -217,7 +221,7 @@
                                     break;
                                 }
                             }
-                            if (trimTrailingPart) trailingPart = EMPTY_CHAR_ARRAY;
+                            if (trimTrailingPart) trailingPart = CollectionUtils.EMPTY_CHAR_ARRAY;
                         }
                         this.text = concat(printablePart, trailingPart);
                     }
@@ -305,7 +309,7 @@
 
     @Override
     boolean heedsTrailingWhitespace() {
-        if (isIgnorable()) {
+        if (isIgnorable(true)) {
             return false;
         }
         for (int i = 0; i < text.length; i++) {
@@ -322,7 +326,7 @@
 
     @Override
     boolean heedsOpeningWhitespace() {
-        if (isIgnorable()) {
+        if (isIgnorable(true)) {
             return false;
         }
         for (int i = text.length - 1; i >= 0; i--) {
@@ -338,18 +342,28 @@
     }
 
     @Override
-    boolean isIgnorable() {
+    boolean isIgnorable(boolean stripWhitespace) {
         if (text == null || text.length == 0) {
             return true;
         }
-        if (!StringUtil.isTrimmableToEmpty(text)) {
+        if (stripWhitespace) {
+            if (!StringUtil.isTrimmableToEmpty(text)) {
+                return false;
+            }
+            TemplateElement parentElement = getParentElement();
+            boolean atTopLevel = isTopLevelTextIfParentIs(parentElement);
+            TemplateElement prevSibling = previousSibling();
+            TemplateElement nextSibling = nextSibling();
+            return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling))
+                    && ((nextSibling == null && atTopLevel) || nonOutputtingType(nextSibling));
+        } else {
             return false;
         }
-        boolean atTopLevel = (getParentElement().getParentElement() == null);
-        TemplateElement prevSibling = previousSibling();
-        TemplateElement nextSibling = nextSibling();
-        return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling))
-                && ((nextSibling == null && atTopLevel) || nonOutputtingType(nextSibling));
+    }
+
+    private boolean isTopLevelTextIfParentIs(TemplateElement parentElement) {
+        return parentElement == null
+                || parentElement.getParentElement() == null && parentElement instanceof MixedContent;
     }
     
 
diff --git a/src/main/java/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java b/src/main/java/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
index b17dc99..51ce3fc 100644
--- a/src/main/java/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
+++ b/src/main/java/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
@@ -58,41 +58,14 @@
             return;
         }
         
-        final TemplateElement nestedBlock = te.getNestedBlock();
-
-        // Deepest-first recursion:
-        if (nestedBlock != null) {
-            addInterruptionChecks(nestedBlock);
-        }
-        final int regulatedChildrenCount = te.getRegulatedChildCount();
+        final int regulatedChildrenCount = te.getChildCount();
         for (int i = 0; i < regulatedChildrenCount; i++) {
-            addInterruptionChecks(te.getRegulatedChild(i));
+            addInterruptionChecks(te.getChild(i));
         }
         
-        // Because nestedElements (means fixed schema for the children) and nestedBlock (means no fixed schema) are
-        // mutually exclusive, and we only care about the last kind:
         if (te.isNestedBlockRepeater()) {
-            if (regulatedChildrenCount != 0) {
-                // Only elements that use nestedBlock instead of regulatedChildren should be block repeaters.
-                // Note that nestedBlock and nestedElements are (should be) mutually exclusive.
-                throw new BugException(); 
-            }
             try {
-                final ThreadInterruptionCheck interruptedChk = new ThreadInterruptionCheck(te);
-                if (nestedBlock == null) {
-                    te.setNestedBlock(interruptedChk);
-                } else {
-                    final MixedContent nestedMixedC;
-                    if (nestedBlock instanceof MixedContent) {
-                        nestedMixedC = (MixedContent) nestedBlock;
-                    } else {
-                        nestedMixedC = new MixedContent();
-                        nestedMixedC.setLocation(te.getUnboundTemplate(), 0, 0, 0, 0);
-                        nestedMixedC.addElement(nestedBlock);
-                        te.setNestedBlock(nestedMixedC);
-                    }
-                    nestedMixedC.addElement(0, interruptedChk);
-                }
+                te.addChild(0, new ThreadInterruptionCheck(te));
             } catch (ParseException e) {
                 throw new TemplatePostProcessorException("Unexpected error; see cause", e);
             }
@@ -110,12 +83,13 @@
         }
 
         @Override
-        void accept(Environment env) throws TemplateException, IOException {
+        TemplateElement[] accept(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()) {
                 throw new TemplateProcessingThreadInterruptedException();
             }
+            return null;
         }
 
         @Override
diff --git a/src/main/java/freemarker/core/TransformBlock.java b/src/main/java/freemarker/core/TransformBlock.java
index f7a1db9..5cfddc4 100644
--- a/src/main/java/freemarker/core/TransformBlock.java
+++ b/src/main/java/freemarker/core/TransformBlock.java
@@ -48,14 +48,14 @@
      */
     TransformBlock(Expression transformExpression, 
                    Map namedArgs,
-                   TemplateElement nestedBlock) {
+                   TemplateElements children) {
         this.transformExpression = transformExpression;
         this.namedArgs = namedArgs;
-        setNestedBlock(nestedBlock);
+        setChildren(children);
     }
 
     @Override
-    void accept(Environment env) 
+    TemplateElement[] accept(Environment env)
     throws TemplateException, IOException {
         TemplateTransformModel ttm = env.getTransform(transformExpression);
         if (ttm != null) {
@@ -72,13 +72,14 @@
             } else {
                 args = EmptyMap.instance;
             }
-            env.visitAndTransform(getNestedBlock(), ttm, args);
+            env.visitAndTransform(getChildBuffer(), ttm, args);
         } else {
             TemplateModel tm = transformExpression.eval(env);
             throw new UnexpectedTypeException(
                     transformExpression, tm,
                     "transform", new Class[] { TemplateTransformModel.class }, env);
         }
+        return null;
     }
 
     @Override
@@ -99,9 +100,7 @@
         }
         if (canonical) {
             sb.append(">");
-            if (getNestedBlock() != null) {
-                sb.append(getNestedBlock().getCanonicalForm());
-            }
+            sb.append(getChildrenCanonicalForm());
             sb.append("</").append(getNodeTypeSymbol()).append('>');
         }
         return sb.toString();
@@ -160,5 +159,10 @@
     boolean isNestedBlockRepeater() {
         return false;
     }
+
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
     
 }
diff --git a/src/main/java/freemarker/core/TrimInstruction.java b/src/main/java/freemarker/core/TrimInstruction.java
index 053c5a0..7e48367 100644
--- a/src/main/java/freemarker/core/TrimInstruction.java
+++ b/src/main/java/freemarker/core/TrimInstruction.java
@@ -38,8 +38,9 @@
     }
 
     @Override
-    void accept(Environment env) {
+    TemplateElement[] accept(Environment env) {
         // This instruction does nothing at render-time, only parse-time.
+        return null;
     }
 
     @Override
@@ -65,7 +66,7 @@
     }
     
     @Override
-    boolean isIgnorable() {
+    boolean isIgnorable(boolean stripWhitespace) {
         return true;
     }
 
diff --git a/src/main/java/freemarker/core/UnboundCallable.java b/src/main/java/freemarker/core/UnboundCallable.java
index 61e3da9..4066818 100644
--- a/src/main/java/freemarker/core/UnboundCallable.java
+++ b/src/main/java/freemarker/core/UnboundCallable.java
@@ -45,7 +45,7 @@
             Collections.EMPTY_LIST, 
             Collections.EMPTY_MAP,
             null, false,
-            TextBlock.EMPTY_BLOCK);
+            TemplateElements.EMPTY);
     
     final static int TYPE_MACRO = 0;
     final static int TYPE_FUNCTION = 1;
@@ -58,7 +58,7 @@
 
     UnboundCallable(String name, List argumentNames, Map args, 
             String catchAllParamName, boolean function,
-            TemplateElement nestedBlock) {
+            TemplateElements children) {
         this.name = name;
         this.paramNames = (String[]) argumentNames.toArray(
                 new String[argumentNames.size()]);
@@ -67,7 +67,7 @@
         this.function = function;
         this.catchAllParamName = catchAllParamName; 
         
-        this.setNestedBlock(nestedBlock);
+        this.setChildren(children);
     }
     
     String[] getParamNames() {
@@ -102,8 +102,9 @@
     }
 
     @Override
-    void accept(Environment env) {
+    TemplateElement[] accept(Environment env) {
         env.visitCallableDefinition(this);
+        return null;
     }
 
     @Override
@@ -149,9 +150,7 @@
         if (function) sb.append(')');
         if (canonical) {
             sb.append('>');
-            if (getNestedBlock() != null) {
-                sb.append(getNestedBlock().getCanonicalForm());
-            }
+            sb.append(getChildrenCanonicalForm());
             sb.append("</").append(getNodeTypeSymbol()).append('>');
         }
         return sb.toString();
diff --git a/src/main/java/freemarker/core/UnifiedCall.java b/src/main/java/freemarker/core/UnifiedCall.java
index 3c0ce94..04a6d3a 100644
--- a/src/main/java/freemarker/core/UnifiedCall.java
+++ b/src/main/java/freemarker/core/UnifiedCall.java
@@ -50,31 +50,28 @@
 
     UnifiedCall(Expression nameExp,
          Map namedArgs,
-         TemplateElement nestedBlock,
+         TemplateElements children,
          List bodyParameterNames) {
         this.nameExp = nameExp;
         this.namedArgs = namedArgs;
-        setNestedBlock(nestedBlock);
+        setChildren(children);
         this.bodyParameterNames = bodyParameterNames;
     }
 
     UnifiedCall(Expression nameExp,
          List positionalArgs,
-         TemplateElement nestedBlock,
+         TemplateElements children,
          List bodyParameterNames) {
         this.nameExp = nameExp;
         this.positionalArgs = positionalArgs;
-        if (nestedBlock == TextBlock.EMPTY_BLOCK) {
-            nestedBlock = null;
-        }
-        setNestedBlock(nestedBlock);
+        setChildren(children);
         this.bodyParameterNames = bodyParameterNames;
     }
 
     @Override
-    void accept(Environment env) throws TemplateException, IOException {
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
         final TemplateModel tm = nameExp.eval(env);
-        if (tm == UnboundCallable.NO_OP_MACRO) return; // shortcut here.
+        if (tm == UnboundCallable.NO_OP_MACRO) return null; // shortcut here.
         if (tm instanceof BoundCallable) {
             final BoundCallable boundMacro = (BoundCallable) tm;
             final Macro unboundMacro = boundMacro.getUnboundCallable();
@@ -84,8 +81,7 @@
                         + "Functions can only be called from expressions, like in ${f()}, ${x + f()} or ",
                         "<@someDirective someParam=f() />", ".");
             }    
-            env.invoke(boundMacro, namedArgs, positionalArgs, bodyParameterNames,
-                    getNestedBlock());
+            env.invoke(boundMacro, namedArgs, positionalArgs, bodyParameterNames, getChildBuffer());
         } else {
             boolean isDirectiveModel = tm instanceof TemplateDirectiveModel; 
             if (isDirectiveModel || tm instanceof TemplateTransformModel) {
@@ -103,9 +99,9 @@
                     args = EmptyMap.instance;
                 }
                 if (isDirectiveModel) {
-                    env.visit(getNestedBlock(), (TemplateDirectiveModel) tm, args, bodyParameterNames);
+                    env.visit(getChildBuffer(), (TemplateDirectiveModel) tm, args, bodyParameterNames);
                 } else { 
-                    env.visitAndTransform(getNestedBlock(), (TemplateTransformModel) tm, args);
+                    env.visitAndTransform(getChildBuffer(), (TemplateTransformModel) tm, args);
                 }
             } else if (tm == null) {
                 throw InvalidReferenceException.getInstance(nameExp, env);
@@ -113,6 +109,7 @@
                 throw new NonUserDefinedDirectiveLikeException(nameExp, tm, env);
             }
         }
+        return null;
     }
 
     @Override
@@ -152,11 +149,11 @@
             }
         }
         if (canonical) {
-            if (getNestedBlock() == null) {
+            if (getChildCount() == 0) {
                 sb.append("/>");
             } else {
                 sb.append('>');
-                sb.append(getNestedBlock().getCanonicalForm());
+                sb.append(getChildrenCanonicalForm());
                 sb.append("</@");
                 if (!nameIsInParen
                         && (nameExp instanceof Identifier
@@ -303,8 +300,7 @@
     }
 
     public boolean isNestedOutputCacheable() {
-        if (getNestedBlock() == null) return true;
-        return getNestedBlock().isOutputCacheable();
+        return isChildrenOutputCacheable();
     }
     
 /*
@@ -341,5 +337,10 @@
     boolean isNestedBlockRepeater() {
         return true;
     }
+
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
     
 }
diff --git a/src/main/java/freemarker/core/VisitNode.java b/src/main/java/freemarker/core/VisitNode.java
index b35e3f8..e116c4a 100644
--- a/src/main/java/freemarker/core/VisitNode.java
+++ b/src/main/java/freemarker/core/VisitNode.java
@@ -42,7 +42,7 @@
     }
 
     @Override
-    void accept(Environment env) throws IOException, TemplateException {
+    TemplateElement[] accept(Environment env) throws IOException, TemplateException {
         TemplateModel node = targetNode.eval(env);
         if (!(node instanceof TemplateNodeModel)) {
             throw new NonNodeException(targetNode, node, env);
@@ -69,6 +69,7 @@
             }
         }
         env.invokeNodeHandlerFor((TemplateNodeModel) node, (TemplateSequenceModel) nss);
+        return null;
     }
 
     @Override
@@ -118,5 +119,10 @@
     boolean isNestedBlockRepeater() {
         return true;
     }
+
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
     
 }
diff --git a/src/main/java/freemarker/core/_CoreAPI.java b/src/main/java/freemarker/core/_CoreAPI.java
index bb8d0f1..1562662 100644
--- a/src/main/java/freemarker/core/_CoreAPI.java
+++ b/src/main/java/freemarker/core/_CoreAPI.java
@@ -466,4 +466,12 @@
         }
     }
     
+    public static TemplateElement getParentElement(TemplateElement te) {
+        return te.getParentElement();
+    }
+
+    public static TemplateElement getChildElement(TemplateElement te, int index) {
+        return te.getChild(index);
+    }
+    
 }
diff --git a/src/main/java/freemarker/debug/impl/RmiDebuggerService.java b/src/main/java/freemarker/debug/impl/RmiDebuggerService.java
index 8065830..25d1b2e 100644
--- a/src/main/java/freemarker/debug/impl/RmiDebuggerService.java
+++ b/src/main/java/freemarker/debug/impl/RmiDebuggerService.java
@@ -39,6 +39,7 @@
 import freemarker.core.DebugBreak;
 import freemarker.core.Environment;
 import freemarker.core.TemplateElement;
+import freemarker.core._CoreAPI;
 import freemarker.debug.Breakpoint;
 import freemarker.debug.DebuggerListener;
 import freemarker.debug.EnvironmentSuspendedEvent;
@@ -188,7 +189,7 @@
         if (te == null) {
             return;
         }
-        TemplateElement parent = te.getParent();
+        TemplateElement parent = _CoreAPI.getParentElement(te);
         DebugBreak db = new DebugBreak(te);
         // TODO: Ensure there always is a parent by making sure
         // that the root element in the template is always a MixedContent
@@ -285,13 +286,13 @@
                 db = (DebugBreak) te;
                 break;
             }
-            te = te.getParent();
+            te = _CoreAPI.getParentElement(te);
         }
         if (db == null) {
             return;
         }
-        TemplateElement parent = db.getParent(); 
-        parent.setChildAt(parent.getIndex(db), db.getChildAt(0));
+        TemplateElement parent = _CoreAPI.getParentElement(db); 
+        parent.setChildAt(parent.getIndex(db), _CoreAPI.getChildElement(db, 0));
     }
     
     void removeBreakpoints(String templateName) {
@@ -334,9 +335,9 @@
     private void removeDebugBreaks(TemplateElement te) {
         int count = te.getChildCount();
         for (int i = 0; i < count; ++i) {
-            TemplateElement child = te.getChildAt(i);
+            TemplateElement child = _CoreAPI.getChildElement(te, i);
             while (child instanceof DebugBreak) {
-                TemplateElement dbchild = child.getChildAt(0); 
+                TemplateElement dbchild = _CoreAPI.getChildElement(child, 0); 
                 te.setChildAt(i, dbchild);
                 child = dbchild;
             }
diff --git a/src/main/java/freemarker/template/SimpleSequence.java b/src/main/java/freemarker/template/SimpleSequence.java
index 2730d58..0cc9411 100644
--- a/src/main/java/freemarker/template/SimpleSequence.java
+++ b/src/main/java/freemarker/template/SimpleSequence.java
@@ -99,7 +99,6 @@
     public SimpleSequence(int capacity) {
         list = new ArrayList(capacity);
     }
-
     
     /**
      * Constructs a simple sequence that will contain the elements
diff --git a/src/main/java/freemarker/template/Template.java b/src/main/java/freemarker/template/Template.java
index 1efab5d..da55d06 100644
--- a/src/main/java/freemarker/template/Template.java
+++ b/src/main/java/freemarker/template/Template.java
@@ -267,23 +267,28 @@
     }
 
     /**
-     * Executes template, using the data-model provided, writing the generated output
-     * to the supplied {@link Writer}.
+     * Executes template, using the data-model provided, writing the generated output to the supplied {@link Writer}.
      * 
-     * <p>For finer control over the runtime environment setup, such as per-HTTP-request configuring of FreeMarker
-     * settings, you may need to use {@link #createProcessingEnvironment(Object, Writer)} instead. 
+     * <p>
+     * For finer control over the runtime environment setup, such as per-HTTP-request configuring of FreeMarker
+     * settings, you may need to use {@link #createProcessingEnvironment(Object, Writer)} instead.
      * 
-     * @param dataModel the holder of the variables visible from the template (name-value pairs); usually a
-     *     {@code Map<String, Object>} or a JavaBean (where the JavaBean properties will be the variables).
-     *     Can be any object that the {@link ObjectWrapper} in use turns into a {@link TemplateHashModel}.
-     *     You can also use an object that already implements {@link TemplateHashModel}; in that case it won't be
-     *     wrapped. If it's {@code null}, an empty data model is used.
-     * @param out The {@link Writer} where the output of the template will go. Note that unless you have used
-     *    {@link Configuration#setAutoFlush(boolean)} to disable this, {@link Writer#flush()} will be called at the
-     *    when the template processing was finished. {@link Writer#close()} is not called.
+     * @param dataModel
+     *            the holder of the variables visible from the template (name-value pairs); usually a
+     *            {@code Map<String, Object>} or a JavaBean (where the JavaBean properties will be the variables). Can
+     *            be any object that the {@link ObjectWrapper} in use turns into a {@link TemplateHashModel}. You can
+     *            also use an object that already implements {@link TemplateHashModel}; in that case it won't be
+     *            wrapped. If it's {@code null}, an empty data model is used.
+     * @param out
+     *            The {@link Writer} where the output of the template will go. Note that unless you have used
+     *            {@link Configuration#setAutoFlush(boolean)} to disable this, {@link Writer#flush()} will be called at
+     *            the when the template processing was finished. {@link Writer#close()} is not called. Can't be
+     *            {@code null}.
      * 
-     * @throws TemplateException if an exception occurs during template processing
-     * @throws IOException if an I/O exception occurs during writing to the writer.
+     * @throws TemplateException
+     *             if an exception occurs during template processing
+     * @throws IOException
+     *             if an I/O exception occurs during writing to the writer.
      */
     public void process(Object dataModel, Writer out)
     throws TemplateException, IOException {
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index 608b43d..2cffd4d 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -2357,37 +2357,37 @@
 {
     Token start, end, t;
     Expression condition;
-    TemplateElement block;
+    TemplateElements children;
     IfBlock ifBlock;
     ConditionalBlock cblock;
 }
 {
     start = <IF>
     condition = Expression()
-    <DIRECTIVE_END>
-    block = OptionalBlock()
+    end = <DIRECTIVE_END>
+    children = MixedContentElements()
     {
-        cblock = new ConditionalBlock(condition, block, ConditionalBlock.TYPE_IF);
-        cblock.setLocation(template, start, block);
+        cblock = new ConditionalBlock(condition, children, ConditionalBlock.TYPE_IF);
+        cblock.setLocation(template, start, end, children);
         ifBlock = new IfBlock(cblock);
     }
     (
         t = <ELSE_IF>
         condition = Expression()
-        LooseDirectiveEnd()
-        block = OptionalBlock()
+        end = LooseDirectiveEnd()
+        children = MixedContentElements()
         {
-            cblock = new ConditionalBlock(condition, block, ConditionalBlock.TYPE_ELSE_IF);
-            cblock.setLocation(template, t, block);
+            cblock = new ConditionalBlock(condition, children, ConditionalBlock.TYPE_ELSE_IF);
+            cblock.setLocation(template, t, end, children);
             ifBlock.addBlock(cblock);
         }
     )*
     [
             t = <ELSE>
-            block = OptionalBlock()
+            children = MixedContentElements()
             {
-                cblock = new ConditionalBlock(null, block, ConditionalBlock.TYPE_ELSE);
-                cblock.setLocation(template, t, block);
+                cblock = new ConditionalBlock(null, children, ConditionalBlock.TYPE_ELSE);
+                cblock.setLocation(template, t, t, children);
                 ifBlock.addBlock(cblock);
             }
     ]
@@ -2401,12 +2401,12 @@
 AttemptBlock Attempt() :
 {
     Token start, end;
-    TemplateElement block;
+    TemplateElements children;
     RecoveryBlock recoveryBlock;
 }
 {
     start = <ATTEMPT>
-    block = OptionalBlock()
+    children = MixedContentElements()
     recoveryBlock = Recover()
     (
         end = <END_RECOVER>
@@ -2414,7 +2414,7 @@
         end = <END_ATTEMPT>
     )
     {
-        AttemptBlock result = new AttemptBlock(block, recoveryBlock);
+        AttemptBlock result = new AttemptBlock(children, recoveryBlock);
         result.setLocation(template, start, end);
         return result;
     }
@@ -2423,14 +2423,14 @@
 RecoveryBlock Recover() : 
 {
     Token start;
-    TemplateElement block;
+    TemplateElements children;
 }
 {
     start = <RECOVER>
-    block = OptionalBlock()
+    children = MixedContentElements()
     {
-        RecoveryBlock result = new RecoveryBlock(block);
-        result.setLocation(template, start, block);
+        RecoveryBlock result = new RecoveryBlock(children);
+        result.setLocation(template, start, start, children);
         return result;
     }
 }
@@ -2439,7 +2439,7 @@
 {
     Expression exp;
     Token loopVar = null, start, end;
-    TemplateElement mainBlock;
+    TemplateElements childrendBeforeElse;
     ElseOfList elseOfList = null;
     ParserIteratorBlockContext iterCtx;
 }
@@ -2459,7 +2459,7 @@
         }
     }
     
-    mainBlock = OptionalBlock()
+    childrendBeforeElse = MixedContentElements()
     {
         if (loopVar != null) {
             breakableDirectiveNesting--;
@@ -2477,7 +2477,7 @@
     
     end = <END_LIST>
     {
-        IteratorBlock list = new IteratorBlock(exp, loopVar != null ? loopVar.image : null, mainBlock, false);
+        IteratorBlock list = new IteratorBlock(exp, loopVar != null ? loopVar.image : null, childrendBeforeElse, false);
         list.setLocation(template, start, end);
 
         TemplateElement result;
@@ -2494,14 +2494,14 @@
 ElseOfList ElseOfList() :
 {
     Token start;
-    TemplateElement block;
+    TemplateElements children;
 }
 {
         start = <ELSE>
-        block = OptionalBlock()
+        children = MixedContentElements()
         {
-            ElseOfList result = new ElseOfList(block);
-	        result.setLocation(template, start, block);
+            ElseOfList result = new ElseOfList(children);
+	        result.setLocation(template, start, start, children);
 	        return result;
         }
 }
@@ -2510,7 +2510,7 @@
 {
     Expression exp;
     Token loopVar, start, end;
-    TemplateElement block;
+    TemplateElements children;
 }
 {
     start = <FOREACH>
@@ -2525,14 +2525,14 @@
         breakableDirectiveNesting++;
     }
     
-    block = OptionalBlock()
+    children = MixedContentElements()
     
     end = <END_FOREACH>
     {
         breakableDirectiveNesting--;
         popIteratorBlockContext();
                 
-        IteratorBlock result = new IteratorBlock(exp, loopVar.image, block, true);
+        IteratorBlock result = new IteratorBlock(exp, loopVar.image, children, true);
         result.setLocation(template, start, end);
         return result;
     }
@@ -2541,7 +2541,7 @@
 Items Items() :
 {
     Token loopVar, start, end;
-    TemplateElement block;
+    TemplateElements children;
     ParserIteratorBlockContext iterCtx;
 }
 {
@@ -2570,14 +2570,14 @@
         breakableDirectiveNesting++;
     }
     
-    block = OptionalBlock()
+    children = MixedContentElements()
     
     end = <END_ITEMS>
     {
         breakableDirectiveNesting--;
         iterCtx.loopVarName = null;
         
-        Items result = new Items(loopVar.image, block);
+        Items result = new Items(loopVar.image, children);
         result.setLocation(template, start, end);
         return result;
     }
@@ -2586,7 +2586,7 @@
 Sep Sep() :
 {
     Token loopVar, start, end = null;
-    TemplateElement block;
+    TemplateElements children;
 }
 {
     start = <SEP>
@@ -2597,17 +2597,17 @@
                     template, start);
         }
     }
-    block = OptionalBlock()
+    children = MixedContentElements()
     [
         LOOKAHEAD(1)
         end = <END_SEP>
     ]
     {
-        Sep result = new Sep(block);
+        Sep result = new Sep(children);
         if (end != null) {
-            result.setLocation(template, start, end); // Template, Token, Token
+            result.setLocation(template, start, end);
         } else {
-            result.setLocation(template, start, block); // Template, Token, TemplateObject
+            result.setLocation(template, start, start, children);
         }
         return result;
     }
@@ -2832,7 +2832,7 @@
     String varName;
     ArrayList assignments = new ArrayList();
     Assignment ass;
-    TemplateElement block;
+    TemplateElements children;
 }
 {
     (
@@ -2962,7 +2962,7 @@
 	            }
 	        ]
 	        <DIRECTIVE_END>
-	        block = OptionalBlock()
+	        children = MixedContentElements()
 	        (
 	            end = <END_LOCAL>
 	            {
@@ -2986,7 +2986,7 @@
 	        )
 	        {
 	            BlockAssignment ba = new BlockAssignment(
-	                   block, varName, scope, nsExp,
+	                   children, varName, scope, nsExp,
 	                   getMarkupOutputFormat());
 	            ba.setLocation(template, start, end);
 	            return ba;
@@ -3070,7 +3070,7 @@
     Expression defValue = null;
     List lastIteratorBlockContexts;
     int lastBreakableDirectiveNesting;
-    TemplateElement block;
+    TemplateElements children;
     boolean isFunction = false, hasDefaults = false;
     boolean isCatchAll = false;
     String catchAll = null;
@@ -3146,16 +3146,16 @@
             lastBreakableDirectiveNesting = 0; // Just to prevent uninitialized local variable error later
         }
     }
-    block = OptionalBlock()
+    children = MixedContentElements()
     (
         end = <END_MACRO>
         {
-        	if(isFunction) throw new ParseException("Expected function end tag here.", template, start);
+        	if (isFunction) throw new ParseException("Expected function end tag here.", template, start);
     	}
         |
         end = <END_FUNCTION>
         {
-    		if(!isFunction) throw new ParseException("Expected macro end tag here.", template, start);
+    		if (!isFunction) throw new ParseException("Expected macro end tag here.", template, start);
     	}
     )
     {
@@ -3165,7 +3165,7 @@
         }
         
         inMacro = inFunction = false;
-        UnboundCallable result = new UnboundCallable(name, argNames, args, catchAll, isFunction, block);
+        UnboundCallable result = new UnboundCallable(name, argNames, args, catchAll, isFunction, children);
         result.setLocation(template, start, end);
         template.addUnboundCallable(result);
         return result;
@@ -3174,15 +3174,15 @@
 
 CompressedBlock Compress() :
 {
-    TemplateElement block;
+    TemplateElements children;
     Token start, end;
 }
 {
     start = <COMPRESS>
-    block = OptionalBlock()
+    children = MixedContentElements()
     end = <END_COMPRESS>
     {
-        CompressedBlock cb = new CompressedBlock(block);
+        CompressedBlock cb = new CompressedBlock(children);
         cb.setLocation(template, start, end);
         return cb;
     }
@@ -3194,7 +3194,7 @@
     HashMap namedArgs = null;
     ArrayList positionalArgs = null, bodyParameters = null;
     Expression startTagNameExp;
-    TemplateElement nestedBlock = null;
+    TemplateElements children;
     Expression exp;
     int pushedCtxCount = 0;
 }
@@ -3226,7 +3226,7 @@
         ]
     ]
     (
-        end = <EMPTY_DIRECTIVE_END>
+        end = <EMPTY_DIRECTIVE_END> { children = TemplateElements.EMPTY; }
         |
         (
             <DIRECTIVE_END> {
@@ -3253,7 +3253,7 @@
                    }
                 }
             }
-            nestedBlock = OptionalBlock()
+            children = MixedContentElements()
             end = <UNIFIED_CALL_END>
             {
                 for (int i = 0; i < pushedCtxCount; i++) {
@@ -3276,8 +3276,8 @@
     )
     {
         TemplateElement result = (positionalArgs != null)
-        		? new UnifiedCall(exp, positionalArgs, nestedBlock, bodyParameters)
-	            : new UnifiedCall(exp, namedArgs, nestedBlock, bodyParameters);
+        		? new UnifiedCall(exp, positionalArgs, children, bodyParameters)
+	            : new UnifiedCall(exp, namedArgs, children, bodyParameters);
         result.setLocation(template, start, end);
         return result;
     }
@@ -3310,9 +3310,9 @@
     {
         UnifiedCall result = null;
         if (positionalArgs != null) {
-            result = new UnifiedCall(new Identifier(macroName), positionalArgs, null, null);
+            result = new UnifiedCall(new Identifier(macroName), positionalArgs, TemplateElements.EMPTY, null);
         } else {
-            result = new UnifiedCall(new Identifier(macroName), namedArgs, null, null);
+            result = new UnifiedCall(new Identifier(macroName), namedArgs, TemplateElements.EMPTY, null);
         }
         result.legacySyntax = true;
         result.setLocation(template, start, end);
@@ -3402,7 +3402,7 @@
 {
     Token start, end, argName;
     Expression exp, argExp;
-    TemplateElement content = null;
+    TemplateElements children = null;
     HashMap args = null;
 }
 {
@@ -3423,12 +3423,12 @@
         |
         (
             <DIRECTIVE_END>
-            content = OptionalBlock()
+            children = MixedContentElements()
             end = <END_TRANSFORM>
         )
     )
     {
-        TransformBlock result = new TransformBlock(exp, args, content);
+        TransformBlock result = new TransformBlock(exp, args, children);
         result.setLocation(template, start, end);
         return result;
     }
@@ -3476,7 +3476,7 @@
 Case Case() :
 {
     Expression exp;
-    TemplateElement block;
+    TemplateElements children;
     Token start;
 }
 {
@@ -3486,10 +3486,10 @@
         |
         start = <DEFAUL> { exp = null; }
     )
-    block = OptionalBlock()
+    children = MixedContentElements()
     {
-        Case result = new Case(exp, block);
-        result.setLocation(template, start, block);
+        Case result = new Case(exp, children);
+        result.setLocation(template, start, start, children);
         return result;
     }
 }
@@ -3498,7 +3498,7 @@
 {
     Token variable, start, end;
     Expression escapeExpr;
-    TemplateElement content;
+    TemplateElements children;
 }
 {
     start = <ESCAPE>
@@ -3519,9 +3519,9 @@
         EscapeBlock result = new EscapeBlock(variable.image, escapeExpr, escapedExpression(escapeExpr));
         escapes.addFirst(result);
     }
-    content = OptionalBlock()
+    children = MixedContentElements()
     {
-        result.setContent(content);
+        result.setContent(children);
         escapes.removeFirst();
     }
     end = <END_ESCAPE>
@@ -3534,7 +3534,7 @@
 NoEscapeBlock NoEscape() :
 {
     Token start, end;
-    TemplateElement content;
+    TemplateElements children;
 }
 {
     start = <NOESCAPE>
@@ -3544,11 +3544,11 @@
         }
         Object escape = escapes.removeFirst();
     }
-    content = OptionalBlock()
+    children = MixedContentElements()
     end = <END_NOESCAPE>
     {
         escapes.addFirst(escape);
-        NoEscapeBlock result = new NoEscapeBlock(content);
+        NoEscapeBlock result = new NoEscapeBlock(children);
         result.setLocation(template, start, end);
         return result;
     }
@@ -3558,7 +3558,7 @@
 {
     Token start, end;
     Expression paramExp;
-    TemplateElement content;
+    TemplateElements children;
     OutputFormat lastOutputFormat;
 }
 {
@@ -3631,10 +3631,10 @@
             throw new ParseException(e.getMessage(), template, start, e.getCause());
         }
     }
-    content = OptionalBlock()
+    children = MixedContentElements()
     end = <END_OUTPUTFORMAT>
     {
-        OutputFormatBlock result = new OutputFormatBlock(content, paramExp);
+        OutputFormatBlock result = new OutputFormatBlock(children, paramExp);
         result.setLocation(template, start, end);
         
         outputFormat = lastOutputFormat;
@@ -3646,7 +3646,7 @@
 AutoEscBlock AutoEsc() :
 {
     Token start, end;
-    TemplateElement content;
+    TemplateElements children;
     int lastAutoEscapingPolicy;
 }
 {
@@ -3657,10 +3657,10 @@
         autoEscapingPolicy = Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY;
         recalculateAutoEscapingField();
     }
-    content = OptionalBlock()
+    children = MixedContentElements()
     end = <END_AUTOESC>
     {
-        AutoEscBlock result = new AutoEscBlock(content);
+        AutoEscBlock result = new AutoEscBlock(children);
         result.setLocation(template, start, end);
         
         autoEscapingPolicy = lastAutoEscapingPolicy; 
@@ -3672,7 +3672,7 @@
 NoAutoEscBlock NoAutoEsc() :
 {
     Token start, end;
-    TemplateElement content;
+    TemplateElements children;
     int lastAutoEscapingPolicy;
 }
 {
@@ -3682,10 +3682,10 @@
         autoEscapingPolicy = Configuration.DISABLE_AUTO_ESCAPING_POLICY;
         recalculateAutoEscapingField();
     }
-    content = OptionalBlock()
+    children = MixedContentElements()
     end = <END_NOAUTOESC>
     {
-        NoAutoEscBlock result = new NoAutoEscBlock(content);
+        NoAutoEscBlock result = new NoAutoEscBlock(children);
         result.setLocation(template, start, end);
         
         autoEscapingPolicy = lastAutoEscapingPolicy;
@@ -3740,7 +3740,7 @@
 }
 {
     // Note that this doesn't include elements like "else", "recover", etc., because those indicate the end
-    // of the OptionalBlock() of "if", "attempt", etc.
+    // of the MixedContentElements of "if", "attempt", etc.
     (
         tp = If()
         |
@@ -3838,7 +3838,7 @@
         }
     )+
     {
-        if (stripText && mixedContentNesting == 1) return TextBlock.EMPTY_BLOCK;
+        if (stripText && mixedContentNesting == 1) return null;
 
         TextBlock result = new TextBlock(buf.toString(), false);
         result.setLocation(template, start, t);
@@ -3873,10 +3873,11 @@
     }
 }
 
-MixedContent MixedContent() :
+TemplateElements MixedContentElements() :
 {
-    MixedContent mixedContent = new MixedContent();
-    TemplateElement elem, begin = null;
+    TemplateElement[] childBuffer = null;
+    int childCount = 0;
+    TemplateElement elem;
     mixedContentNesting++;
 }
 {
@@ -3892,16 +3893,25 @@
             elem = FreemarkerDirective()
         )
         {
-            if (begin == null) {
-                begin = elem;
+            // Note: elem == null when it's was top-level PCData removed by stripText
+            if (elem != null) {
+	            childCount++;
+	            if (childBuffer == null) {
+	                childBuffer = new TemplateElement[16]; 
+	            } else if (childBuffer.length < childCount) {
+	                TemplateElement[] newChildBuffer = new TemplateElement[childCount * 2];
+	                for (int i = 0; i < childBuffer.length; i++) {
+	                    newChildBuffer[i] = childBuffer[i];
+	                }
+	                childBuffer = newChildBuffer;
+	            }
+	            childBuffer[childCount - 1] = elem;
             }
-            mixedContent.addElement(elem);
         }
-    )+
+    )*
     {
         mixedContentNesting--;
-        mixedContent.setLocation(template, begin, elem);
-        return mixedContent;
+        return childBuffer != null ? new TemplateElements(childBuffer, childCount) : TemplateElements.EMPTY;
     }
 }
 
@@ -3927,7 +3937,7 @@
             if (begin == null) {
             	begin = elem;
             }
-            nodes.addElement(elem);
+            nodes.addChild(elem);
         }
     )+
     {
@@ -3936,25 +3946,6 @@
     }
 }
 
-/**
- * A production for a block of optional content.
- * Returns an empty Text block if there is no
- * content.
- */
-TemplateElement OptionalBlock() :
-{
-    TemplateElement tp = TextBlock.EMPTY_BLOCK;
-}
-{
-    [
-        LOOKAHEAD(1) // has no effect but to get rid of a spurious warning.
-        tp = MixedContent()
-    ]
-    {
-        return tp;
-    }
-}
-
 void HeaderElement() :
 {
     Token key;
@@ -4183,20 +4174,21 @@
  */
 TemplateElement Root() :
 {
-    TemplateElement doc;
+    TemplateElements children;
 }
 {
     [
         LOOKAHEAD([<STATIC_TEXT_WS>](<TRIVIAL_FTL_HEADER>|<FTL_HEADER>))
         HeaderElement()
     ]
-    doc = OptionalBlock()
+    children = MixedContentElements()
     <EOF>
     {
-        doc.setFieldsForRootElement();
-        doc = doc.postParseCleanup(stripWhitespace);
+        TemplateElement root = children.asSingleElement(); 
+        root.setFieldsForRootElement();
+        root = root.postParseCleanup(stripWhitespace);
         // The cleanup result is possibly an element from deeper:
-        doc.setFieldsForRootElement();
-        return doc;
+        root.setFieldsForRootElement();
+        return root;
     }
 }
diff --git a/src/manual/book.xml b/src/manual/book.xml
index a35f8d2..0b80c41 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26200,6 +26200,12 @@
             </listitem>
 
             <listitem>
+              <para>Decrease the stack usage of template execution, which can
+              have importance if you have very very deeply nested
+              templates.</para>
+            </listitem>
+
+            <listitem>
               <para>Bug fixed, only with
               <literal>incompatible_improvements</literal> set to 2.3.24
               (<link linkend="topic.defaultObjectWrapperIcI">see how
@@ -26287,6 +26293,24 @@
             </listitem>
 
             <listitem>
+              <para>Internal reworking to simplify the AST (the
+              <literal>TemplateElement</literal> structure). The API related
+              technically public API was marked as internal for a good while.
+              For those who still use that API, the visible change is that
+              <literal>TemplateElement</literal>-s now almost never has a
+              <literal>MixedContent</literal> parent, instead, the parent is
+              directly whatever element the child element indeed belongs under
+              when you look at the source code (like the enclosing
+              <literal>#list</literal> for example, no a
+              <literal>MixedContent</literal> whose parent is the
+              <literal>#list</literal>). Note that when you have moved
+              downwards, towards the child elements, these
+              <literal>MixedContent</literal> parents weren't visible either
+              either, so the tree traversal API was in fact inconsistent. Now
+              it's consistent.</para>
+            </listitem>
+
+            <listitem>
               <para>The non-public AST API of
               <literal>freemarker.core.StringLiteral</literal>-s has been
               changed. In principle it doesn't mater as it isn't a public API,
diff --git a/src/test/java/freemarker/core/ASTPrinter.java b/src/test/java/freemarker/core/ASTPrinter.java
index 9d2c53d..68d6ba0 100644
--- a/src/test/java/freemarker/core/ASTPrinter.java
+++ b/src/test/java/freemarker/core/ASTPrinter.java
@@ -260,14 +260,21 @@
     }
 
     private static void validateAST(TemplateElement te) {
-        int ln = te.getRegulatedChildCount();
-        for (int i = 0; i < ln; i++) {
-            TemplateElement child = te.getRegulatedChild(i);
-            if (child.getParentElement() != te) {
+        int childCount = te.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            TemplateElement child = te.getChild(i);
+            TemplateElement parentElement = child.getParentElement();
+            // As MixedContent.accept does nothing but return its regulatedChildren, it's optimized out in the final
+            // AST tree. While it will be present as a child, the parent element also will have regularedChildren
+            // that contains the children of the MixedContent directly. 
+            if (parentElement instanceof MixedContent && parentElement.getParentElement() != null) {
+                parentElement = parentElement.getParentElement();
+            }
+            if (parentElement != te) {
                 throw new InvalidASTException("Wrong parent node."
                         + "\nNode: " + child.dump(false)
                         + "\nExpected parent: " + te.dump(false)
-                        + "\nActual parent: " + child.getParentElement().dump(false));
+                        + "\nActual parent: " + parentElement.dump(false));
             }
             if (child.getIndex() != i) {
                 throw new InvalidASTException("Wrong node index."
@@ -276,13 +283,37 @@
                         + "\nActual index: " + child.getIndex());
             }
         }
-        if (te instanceof MixedContent && te.getRegulatedChildCount() < 2) {
+        if (te instanceof MixedContent && te.getChildCount() < 2) {
             throw new InvalidASTException("Mixed content with child count less than 2 should removed by optimizatoin, "
-                    + "but found one with " + te.getRegulatedChildCount() + " child(ren).");
+                    + "but found one with " + te.getChildCount() + " child(ren).");
         }
-        if (te.getRegulatedChildCount() != 0 && te.getNestedBlock() != null) {
-            throw new InvalidASTException("Can't have both nestedBlock and regulatedChildren."
-                    + "\nNode: " + te.dump(false));
+        TemplateElement[] regulatedChildren = te.getChildBuffer();
+        if (regulatedChildren != null) {
+            if (childCount == 0) {
+                throw new InvalidASTException(
+                        "regularChildren must be null when regularChild is 0."
+                        + "\nNode: " + te.dump(false));
+            }
+            for (int i = 0; i < te.getChildCount(); i++) {
+                if (regulatedChildren[i] == null) {
+                    throw new InvalidASTException(
+                            "regularChildren can't be null at index " + i
+                            + "\nNode: " + te.dump(false));
+                }
+            }
+            for (int i = te.getChildCount(); i < regulatedChildren.length; i++) {
+                if (regulatedChildren[i] != null) {
+                    throw new InvalidASTException(
+                            "regularChildren can't be non-null at index " + i
+                            + "\nNode: " + te.dump(false));
+                }
+            }
+        } else {
+            if (childCount != 0) {
+                throw new InvalidASTException(
+                        "regularChildren mustn't be null when regularChild isn't 0."
+                        + "\nNode: " + te.dump(false));
+            }
         }
     }
 
diff --git a/src/test/java/freemarker/core/ASTTest.java b/src/test/java/freemarker/core/ASTTest.java
index 6ba4378..a700334 100644
--- a/src/test/java/freemarker/core/ASTTest.java
+++ b/src/test/java/freemarker/core/ASTTest.java
@@ -58,6 +58,10 @@
     public void testMixedContentSimplifications() throws Exception {
         testAST("ast-mixedcontentsimplifications");
     }
+
+    public void testMultipleIgnoredChildren() throws Exception {
+        testAST("ast-multipleignoredchildren");
+    }
     
     private void testAST(String testName) throws FileNotFoundException, IOException {
         final String templateName = testName + ".ftl";
diff --git a/src/test/resources/freemarker/core/ast-multipleignoredchildren.ast b/src/test/resources/freemarker/core/ast-multipleignoredchildren.ast
new file mode 100644
index 0000000..798e81b
--- /dev/null
+++ b/src/test/resources/freemarker/core/ast-multipleignoredchildren.ast
@@ -0,0 +1,12 @@
+#mixed_content  // f.c.MixedContent
+    #text  // f.c.TextBlock
+        - content: "a\n"  // String
+    #text  // f.c.TextBlock
+        - content: "b\n"  // String
+    #text  // f.c.TextBlock
+        - content: "c\n"  // String
+    #text  // f.c.TextBlock
+        - content: "d\n"  // String
+    #if  // f.c.ConditionalBlock
+        - condition: true  // f.c.BooleanLiteral
+        - AST-node subtype: "0"  // Integer
diff --git a/src/test/resources/freemarker/core/ast-multipleignoredchildren.ftl b/src/test/resources/freemarker/core/ast-multipleignoredchildren.ftl
new file mode 100644
index 0000000..10b8d87
--- /dev/null
+++ b/src/test/resources/freemarker/core/ast-multipleignoredchildren.ftl
@@ -0,0 +1,15 @@
+a
+<#compress></#compress>
+b
+<#compress></#compress>
+<#compress></#compress>
+c
+<#compress></#compress>
+<#compress></#compress>
+<#compress></#compress>
+d
+<#if true>
+  <#compress></#compress>
+  <#compress></#compress>
+  <#compress></#compress>
+</#if>
\ No newline at end of file