freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [11/23] incubator-freemarker git commit: Refactoring FMParser to not produce MixedContent, except for the root element.
Date Tue, 15 Dec 2015 23:26:16 GMT
Refactoring FMParser to not produce MixedContent, except for the root element.


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

Branch: refs/heads/master
Commit: 3d7e66273ae393ca9330baf771511766ff411639
Parents: 808ddad
Author: ddekany <ddekany@apache.org>
Authored: Fri Dec 11 23:41:44 2015 +0100
Committer: ddekany <ddekany@apache.org>
Committed: Sun Dec 13 13:09:26 2015 +0100

----------------------------------------------------------------------
 src/main/java/freemarker/core/AttemptBlock.java |  14 +-
 src/main/java/freemarker/core/AutoEscBlock.java |   4 +-
 .../java/freemarker/core/BlockAssignment.java   |   4 +-
 src/main/java/freemarker/core/Case.java         |   4 +-
 .../java/freemarker/core/CompressedBlock.java   |   4 +-
 .../java/freemarker/core/ConditionalBlock.java  |   4 +-
 src/main/java/freemarker/core/ElseOfList.java   |   4 +-
 src/main/java/freemarker/core/Environment.java  |   7 +-
 src/main/java/freemarker/core/EscapeBlock.java  |   4 +-
 src/main/java/freemarker/core/Expression.java   |   3 +-
 src/main/java/freemarker/core/Items.java        |   4 +-
 .../java/freemarker/core/IteratorBlock.java     |   8 +-
 src/main/java/freemarker/core/Macro.java        |   6 +-
 src/main/java/freemarker/core/MixedContent.java |  10 +-
 .../java/freemarker/core/NoAutoEscBlock.java    |   4 +-
 .../java/freemarker/core/NoEscapeBlock.java     |   4 +-
 .../java/freemarker/core/OutputFormatBlock.java |   4 +-
 .../java/freemarker/core/RecoveryBlock.java     |   4 +-
 src/main/java/freemarker/core/Sep.java          |   4 +-
 .../java/freemarker/core/TemplateElement.java   | 177 ++++++++-------
 .../core/TemplateElementArrayBuilder.java       |  76 +++++++
 .../java/freemarker/core/TemplateObject.java    |  28 ++-
 src/main/java/freemarker/core/TextBlock.java    |  21 +-
 .../java/freemarker/core/TransformBlock.java    |   4 +-
 src/main/java/freemarker/core/UnifiedCall.java  |  11 +-
 src/main/javacc/FTL.jj                          | 217 +++++++++----------
 26 files changed, 361 insertions(+), 273 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/AttemptBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/AttemptBlock.java b/src/main/java/freemarker/core/AttemptBlock.java
index 586cf05..64e991f 100644
--- a/src/main/java/freemarker/core/AttemptBlock.java
+++ b/src/main/java/freemarker/core/AttemptBlock.java
@@ -28,20 +28,20 @@ import freemarker.template.TemplateException;
  */
 final class AttemptBlock extends TemplateElement {
     
-    private TemplateElement attemptBlock;
+    private TemplateElement[] attemptBlockChildren;
     private RecoveryBlock recoveryBlock;
     
-    AttemptBlock(TemplateElement attemptBlock, RecoveryBlock recoveryBlock) {
-        this.attemptBlock = attemptBlock;
+    AttemptBlock(TemplateElements attemptBlock, RecoveryBlock recoveryBlock) {
+        this.attemptBlockChildren = attemptBlock.getBuffer();
         this.recoveryBlock = recoveryBlock;
         setChildBufferCapacity(2);
-        addChild(attemptBlock);
+        addChild(attemptBlock.asSingleElement()); // for backward compatibility
         addChild(recoveryBlock);
     }
 
     @Override
     TemplateElement[] accept(Environment env) throws TemplateException, IOException {
-        env.visitAttemptRecover(attemptBlock, recoveryBlock);
+        env.visitAttemptRecover(this, attemptBlockChildren, recoveryBlock);
         return null;
     }
 
@@ -52,9 +52,7 @@ final class AttemptBlock extends TemplateElement {
         } else {
             StringBuilder buf = new StringBuilder();
             buf.append("<").append(getNodeTypeSymbol()).append(">");
-            if (attemptBlock != null) {
-                buf.append(attemptBlock.getCanonicalForm());            
-            }
+            buf.append(getChildrenCanonicalForm(attemptBlockChildren));            
             if (recoveryBlock != null) {
                 buf.append(recoveryBlock.getCanonicalForm());
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/AutoEscBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/AutoEscBlock.java b/src/main/java/freemarker/core/AutoEscBlock.java
index 5bcc555..b8a75e2 100644
--- a/src/main/java/freemarker/core/AutoEscBlock.java
+++ b/src/main/java/freemarker/core/AutoEscBlock.java
@@ -28,8 +28,8 @@ import freemarker.template.TemplateException;
  */
 final class AutoEscBlock extends TemplateElement {
     
-    AutoEscBlock(TemplateElement nestedBlock) { 
-        setChildrenFromElement(nestedBlock);
+    AutoEscBlock(TemplateElements children) { 
+        setChildren(children);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/BlockAssignment.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BlockAssignment.java b/src/main/java/freemarker/core/BlockAssignment.java
index a4f88d1..6de0c8f 100644
--- a/src/main/java/freemarker/core/BlockAssignment.java
+++ b/src/main/java/freemarker/core/BlockAssignment.java
@@ -40,8 +40,8 @@ final class BlockAssignment extends TemplateElement {
     private final int scope;
     private final MarkupOutputFormat<?> markupOutputFormat;
 
-    BlockAssignment(TemplateElement nestedBlock, String varName, int scope, Expression namespaceExp, MarkupOutputFormat<?> markupOutputFormat) {
-        setChildrenFromElement(nestedBlock);
+    BlockAssignment(TemplateElements children, String varName, int scope, Expression namespaceExp, MarkupOutputFormat<?> markupOutputFormat) {
+        setChildren(children);
         this.varName = varName;
         this.namespaceExp = namespaceExp;
         this.scope = scope;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/Case.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Case.java b/src/main/java/freemarker/core/Case.java
index 4b13cfb..4f9c5c8 100644
--- a/src/main/java/freemarker/core/Case.java
+++ b/src/main/java/freemarker/core/Case.java
@@ -29,9 +29,9 @@ final class Case extends TemplateElement {
     
     Expression condition;
 
-    Case(Expression matchingValue, TemplateElement nestedBlock) {
+    Case(Expression matchingValue, TemplateElements children) {
         this.condition = matchingValue;
-        setChildrenFromElement(nestedBlock);
+        setChildren(children);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/CompressedBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/CompressedBlock.java b/src/main/java/freemarker/core/CompressedBlock.java
index 2ecde74..9a497e3 100644
--- a/src/main/java/freemarker/core/CompressedBlock.java
+++ b/src/main/java/freemarker/core/CompressedBlock.java
@@ -31,8 +31,8 @@ import freemarker.template.utility.StandardCompress;
  */
 final class CompressedBlock extends TemplateElement {
 
-    CompressedBlock(TemplateElement nestedBlock) { 
-        setChildrenFromElement(nestedBlock);
+    CompressedBlock(TemplateElements children) { 
+        setChildren(children);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/ConditionalBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ConditionalBlock.java b/src/main/java/freemarker/core/ConditionalBlock.java
index 00dafdc..911affd 100644
--- a/src/main/java/freemarker/core/ConditionalBlock.java
+++ b/src/main/java/freemarker/core/ConditionalBlock.java
@@ -37,9 +37,9 @@ final class ConditionalBlock extends TemplateElement {
     final Expression condition;
     private final int type;
 
-    ConditionalBlock(Expression condition, TemplateElement nestedBlock, int type) {
+    ConditionalBlock(Expression condition, TemplateElements children, int type) {
         this.condition = condition;
-        setChildrenFromElement(nestedBlock);
+        setChildren(children);
         this.type = type;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/ElseOfList.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ElseOfList.java b/src/main/java/freemarker/core/ElseOfList.java
index 8e0bbeb..3557e38 100644
--- a/src/main/java/freemarker/core/ElseOfList.java
+++ b/src/main/java/freemarker/core/ElseOfList.java
@@ -28,8 +28,8 @@ import freemarker.template.TemplateException;
  */
 final class ElseOfList extends TemplateElement {
     
-    ElseOfList(TemplateElement block) {
-        setChildrenFromElement(block);
+    ElseOfList(TemplateElements children) {
+        setChildren(children);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index bf89fbb..3db2f81 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -469,8 +469,9 @@ public final class Environment extends Configurable {
     /**
      * Visit a block using buffering/recovery
      */
-     void visitAttemptRecover(TemplateElement attemptBlock, RecoveryBlock recoveryBlock)
-            throws TemplateException, IOException {
+     void visitAttemptRecover(
+             AttemptBlock attemptBlock, TemplateElement[] attemptBlockChildren, RecoveryBlock recoveryBlock)
+             throws TemplateException, IOException {
         Writer prevOut = this.out;
         StringWriter sw = new StringWriter();
         this.out = sw;
@@ -479,7 +480,7 @@ public final class Environment extends Configurable {
         boolean lastInAttemptBlock = inAttemptBlock;
         try {
             inAttemptBlock = true;
-            visit(attemptBlock);
+            visit(attemptBlockChildren);
         } catch (TemplateException te) {
             thrownException = te;
         } finally {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/EscapeBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/EscapeBlock.java b/src/main/java/freemarker/core/EscapeBlock.java
index b5d40ab..9f17626 100644
--- a/src/main/java/freemarker/core/EscapeBlock.java
+++ b/src/main/java/freemarker/core/EscapeBlock.java
@@ -40,8 +40,8 @@ class EscapeBlock extends TemplateElement {
         this.escapedExpr = escapedExpr;
     }
 
-    void setContent(TemplateElement nestedBlock) {
-        setChildrenFromElement(nestedBlock);
+    void setContent(TemplateElements children) {
+        setChildren(children);
         // We don't need it anymore at this point
         this.escapedExpr = null;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/Expression.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Expression.java b/src/main/java/freemarker/core/Expression.java
index 3129e19..1b07c7d 100644
--- a/src/main/java/freemarker/core/Expression.java
+++ b/src/main/java/freemarker/core/Expression.java
@@ -58,8 +58,7 @@ abstract public class Expression extends TemplateObject {
     // Hook in here to set the constant value if possible.
     
     @Override
-    void setLocation(Template template, int beginColumn, int beginLine, int endColumn, int endLine)
-    throws ParseException {
+    void setLocation(Template template, int beginColumn, int beginLine, int endColumn, int endLine) {
         super.setLocation(template, beginColumn, beginLine, endColumn, endLine);
         if (isLiteral()) {
             try {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/Items.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Items.java b/src/main/java/freemarker/core/Items.java
index ccc0223..38bc5a6 100644
--- a/src/main/java/freemarker/core/Items.java
+++ b/src/main/java/freemarker/core/Items.java
@@ -30,9 +30,9 @@ class Items extends TemplateElement {
 
     private final String loopVarName;
 
-    public Items(String loopVariableName, TemplateElement nestedBlock) {
+    Items(String loopVariableName, TemplateElements children) {
         this.loopVarName = loopVariableName;
-        setChildrenFromElement(nestedBlock);
+        setChildren(children);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/IteratorBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/IteratorBlock.java b/src/main/java/freemarker/core/IteratorBlock.java
index 8f629dd..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.TemplateSequenceModel;
 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,17 +48,17 @@ final class IteratorBlock extends TemplateElement {
      *            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;
-        setChildrenFromElement(nestedBlock);
+        setChildren(childrenBeforeElse);
         this.isForEach = isForEach;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/Macro.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Macro.java b/src/main/java/freemarker/core/Macro.java
index 0cb0159..44b3505 100644
--- a/src/main/java/freemarker/core/Macro.java
+++ b/src/main/java/freemarker/core/Macro.java
@@ -43,7 +43,7 @@ public final class Macro extends TemplateElement implements TemplateModel {
             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;
@@ -56,7 +56,7 @@ public final class Macro extends TemplateElement implements TemplateModel {
 
     Macro(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()]);
@@ -65,7 +65,7 @@ public final class Macro extends TemplateElement implements TemplateModel {
         this.function = function;
         this.catchAllParamName = catchAllParamName; 
         
-        this.setChildrenFromElement(nestedBlock);
+        this.setChildren(children);
     }
 
     public String getCatchAll() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/MixedContent.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/MixedContent.java b/src/main/java/freemarker/core/MixedContent.java
index 5b515d8..3306988 100644
--- a/src/main/java/freemarker/core/MixedContent.java
+++ b/src/main/java/freemarker/core/MixedContent.java
@@ -29,11 +29,19 @@ import freemarker.template.TemplateException;
 final class MixedContent extends TemplateElement {
 
     MixedContent() { }
-
+    
+    /**
+     * @deprecated Use {@link #addChild(TemplateElement)} instead.
+     */
+    @Deprecated
     void addElement(TemplateElement element) {
         addChild(element);
     }
 
+    /**
+     * @deprecated Use {@link #addChild(int, TemplateElement)} instead.
+     */
+    @Deprecated
     void addElement(int index, TemplateElement element) {
         addChild(index, element);
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/NoAutoEscBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/NoAutoEscBlock.java b/src/main/java/freemarker/core/NoAutoEscBlock.java
index 14980b2..7e07bca 100644
--- a/src/main/java/freemarker/core/NoAutoEscBlock.java
+++ b/src/main/java/freemarker/core/NoAutoEscBlock.java
@@ -28,8 +28,8 @@ import freemarker.template.TemplateException;
  */
 final class NoAutoEscBlock extends TemplateElement {
     
-    NoAutoEscBlock(TemplateElement nestedBlock) { 
-        setChildrenFromElement(nestedBlock);
+    NoAutoEscBlock(TemplateElements children) { 
+        setChildren(children);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/NoEscapeBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/NoEscapeBlock.java b/src/main/java/freemarker/core/NoEscapeBlock.java
index 0722b91..6ac3960 100644
--- a/src/main/java/freemarker/core/NoEscapeBlock.java
+++ b/src/main/java/freemarker/core/NoEscapeBlock.java
@@ -27,8 +27,8 @@ import freemarker.template.TemplateException;
  */
 class NoEscapeBlock extends TemplateElement {
 
-    NoEscapeBlock(TemplateElement nestedBlock) {
-        setChildrenFromElement(nestedBlock);
+    NoEscapeBlock(TemplateElements children) {
+        setChildren(children);
     }
     
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/OutputFormatBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/OutputFormatBlock.java b/src/main/java/freemarker/core/OutputFormatBlock.java
index cc3ef4b..1b5a746 100644
--- a/src/main/java/freemarker/core/OutputFormatBlock.java
+++ b/src/main/java/freemarker/core/OutputFormatBlock.java
@@ -30,9 +30,9 @@ final class OutputFormatBlock extends TemplateElement {
     
     private final Expression paramExp;
 
-    OutputFormatBlock(TemplateElement nestedBlock, Expression paramExp) { 
+    OutputFormatBlock(TemplateElements children, Expression paramExp) { 
         this.paramExp = paramExp; 
-        setChildrenFromElement(nestedBlock);
+        setChildren(children);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/RecoveryBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/RecoveryBlock.java b/src/main/java/freemarker/core/RecoveryBlock.java
index 31038e2..67ff1fb 100644
--- a/src/main/java/freemarker/core/RecoveryBlock.java
+++ b/src/main/java/freemarker/core/RecoveryBlock.java
@@ -25,8 +25,8 @@ import freemarker.template.TemplateException;
 
 final class RecoveryBlock extends TemplateElement {
     
-    RecoveryBlock(TemplateElement block) {
-        setChildrenFromElement(block);
+    RecoveryBlock(TemplateElements children) {
+        setChildren(children);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/Sep.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Sep.java b/src/main/java/freemarker/core/Sep.java
index d30b3e9..0ae5de2 100644
--- a/src/main/java/freemarker/core/Sep.java
+++ b/src/main/java/freemarker/core/Sep.java
@@ -28,8 +28,8 @@ import freemarker.template.TemplateException;
  */
 class Sep extends TemplateElement {
 
-    public Sep(TemplateElement nestedBlock) {
-        setChildrenFromElement(nestedBlock);
+    public Sep(TemplateElements children) {
+        setChildren(children);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/TemplateElement.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateElement.java b/src/main/java/freemarker/core/TemplateElement.java
index 61e3150..350bfa1 100644
--- a/src/main/java/freemarker/core/TemplateElement.java
+++ b/src/main/java/freemarker/core/TemplateElement.java
@@ -31,8 +31,7 @@ import freemarker.template.TemplateSequenceModel;
 /**
  * <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,14 +44,14 @@ abstract public class TemplateElement extends TemplateObject {
     private TemplateElement parent;
 
     /**
-     * Contains 1 or more nested elements with optional trailing {@code null}-s, or is {@code null} exactly if there
-     * are no nested elements.
+     * 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[] childBuffer;
-    
+
     /**
-     * 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}.
+     * 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;
 
@@ -72,18 +71,17 @@ abstract public class TemplateElement extends TemplateObject {
      * 
      * @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.
+     *         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 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)}.
      * 
@@ -93,7 +91,7 @@ abstract public class TemplateElement extends TemplateObject {
     public final String getDescription() {
         return dump(false);
     }
-    
+
     /**
      * This final implementation calls {@link #dump(boolean) dump(false)}.
      */
@@ -101,19 +99,25 @@ abstract public class TemplateElement extends TemplateObject {
     public final String getCanonicalForm() {
         return dump(true);
     }
-    
+
     final String getChildrenCanonicalForm() {
-        int ln = childCount;
-        if (ln == 0) {
+        return getChildrenCanonicalForm(childBuffer);
+    }
+    
+    static String getChildrenCanonicalForm(TemplateElement[] children) {
+        if (children == null) {
             return "";
         }
         StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < ln; i++) {
-            sb.append(childBuffer[i].getCanonicalForm());
+        for (TemplateElement child : children) {
+            if (child == null) {
+                break;
+            }
+            sb.append(child.getCanonicalForm());
         }
         return sb.toString();
     }
-    
+
     /**
      * Tells if the element should show up in error stack traces. Note that this will be ignored for the top (current)
      * element of a stack trace, as that's always shown.
@@ -121,39 +125,40 @@ abstract public class TemplateElement extends TemplateObject {
     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.
+     * 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() {
         SimpleSequence result = new SimpleSequence(1);
         if (childBuffer != null) {
@@ -165,14 +170,14 @@ abstract public class TemplateElement extends TemplateObject {
         }
         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 childCount == 0;
@@ -188,7 +193,7 @@ abstract public class TemplateElement extends TemplateObject {
 
     public int getIndex(TemplateElement node) {
         for (int i = 0; i < childCount; i++) {
-            if (childBuffer[i].equals(node)) { 
+            if (childBuffer[i].equals(node)) {
                 return i;
             }
         }
@@ -230,7 +235,7 @@ abstract public class TemplateElement extends TemplateObject {
             throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + childCount);
         }
     }
-    
+
     /**
      * The element whose child this element is, or {@code null} if this is the root node.
      * 
@@ -240,7 +245,7 @@ abstract public class TemplateElement extends TemplateObject {
     public TemplateElement getParent() {
         return parent;
     }
-    
+
     final void setChildBufferCapacity(int capacity) {
         int ln = childCount;
         TemplateElement[] newRegulatedChildBuffer = new TemplateElement[capacity];
@@ -249,9 +254,9 @@ abstract public class TemplateElement extends TemplateObject {
         }
         childBuffer = newRegulatedChildBuffer;
     }
-    
+
     /**
-     * Inserts a new nested element after the last nested element. 
+     * Inserts a new nested element after the last nested element.
      */
     final void addChild(TemplateElement nestedElement) {
         addChild(childCount, nestedElement);
@@ -262,14 +267,14 @@ abstract public class TemplateElement extends TemplateObject {
      */
     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];
             childBuffer = lRegulatedChildBuffer;
         } else if (lRegulatedChildCount == lRegulatedChildBuffer.length) {
             setChildBufferCapacity(lRegulatedChildCount != 0 ? lRegulatedChildCount * 2 : 1);
-            lRegulatedChildBuffer = childBuffer; 
+            lRegulatedChildBuffer = childBuffer;
         }
         // At this point: nestedElements == this.nestedElements, and has sufficient capacity.
 
@@ -283,48 +288,47 @@ abstract public class TemplateElement extends TemplateObject {
         lRegulatedChildBuffer[index] = nestedElement;
         childCount = lRegulatedChildCount + 1;
     }
-    
+
     final TemplateElement getChild(int index) {
         return childBuffer[index];
     }
-    
+
     /**
      * @return Array containing 1 or more nested elements with optional trailing {@code null}-s, or is {@code null}
      *         exactly if there are no nested elements.
      */
-    final TemplateElement[] getChildBuffer(){
+    final TemplateElement[] getChildBuffer() {
         return childBuffer;
     }
-    
-    // TODO Workaround until the parser doesn't emit TemplateElement[]-s for "blocks" 
-    final void setChildrenFromElement(TemplateElement nestedBlock) {
-        if (nestedBlock == null) {
-            childBuffer = null;
-            childCount = 0;
-        } else if (nestedBlock instanceof MixedContent) {
-            MixedContent mixedContent = (MixedContent) nestedBlock;
-            childBuffer = mixedContent.getChildBuffer();
-            childCount = mixedContent.getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                childBuffer[i].parent = this;
-            }
-        } else {
-            childBuffer = new TemplateElement[] { nestedBlock };
-            childCount = 1;
+
+    /**
+     * @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;
     }
-    
+
     /**
      * This is a special case, because a root element is not contained in another element, so we couldn't set the
      * private fields.
@@ -350,6 +354,17 @@ abstract public class TemplateElement extends TemplateObject {
         if (regulatedChildCount != 0) {
             for (int i = 0; i < regulatedChildCount; i++) {
                 TemplateElement te = childBuffer[i];
+                
+                //!!T temporal 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);
                 childBuffer[i] = te;
                 te.parent = this;
@@ -361,7 +376,7 @@ abstract public class TemplateElement extends TemplateObject {
                     // TODO Optimize this...
                     regulatedChildCount--;
                     for (int j = i; j < regulatedChildCount; j++) {
-                        final TemplateElement te2 = childBuffer[j  + 1];
+                        final TemplateElement te2 = childBuffer[j + 1];
                         childBuffer[j] = te2;
                         te2.index = j;
                     }
@@ -373,14 +388,14 @@ abstract public class TemplateElement extends TemplateObject {
             if (regulatedChildCount == 0) {
                 childBuffer = null;
             } else if (regulatedChildCount < childBuffer.length
-                && regulatedChildCount <= childBuffer.length * 3 / 4) {
+                    && regulatedChildCount <= childBuffer.length * 3 / 4) {
                 TemplateElement[] trimmedregulatedChildBuffer = new TemplateElement[regulatedChildCount];
                 for (int i = 0; i < regulatedChildCount; i++) {
                     trimmedregulatedChildBuffer[i] = childBuffer[i];
                 }
                 childBuffer = trimmedregulatedChildBuffer;
             }
-        } 
+        }
         return this;
     }
 
@@ -388,8 +403,8 @@ abstract public class TemplateElement extends TemplateObject {
         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();
@@ -437,7 +452,7 @@ abstract public class TemplateElement extends TemplateObject {
     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;
@@ -459,7 +474,7 @@ abstract public class TemplateElement extends TemplateObject {
     boolean isOutputCacheable() {
         return false;
     }
-    
+
     boolean isChildrenOutputCacheable() {
         int ln = childCount;
         for (int i = 0; i < ln; i++) {
@@ -469,20 +484,18 @@ abstract public class TemplateElement extends TemplateObject {
         }
         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;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/TemplateElementArrayBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateElementArrayBuilder.java b/src/main/java/freemarker/core/TemplateElementArrayBuilder.java
new file mode 100644
index 0000000..623fd17
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateElementArrayBuilder.java
@@ -0,0 +1,76 @@
+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) {
+        if (count == 0 && buffer != null) { // !!T temporal assertion
+            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.getTemplate(), 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.getTemplate(), first, getLast());
+        }
+        return mixedContent;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/TemplateObject.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateObject.java b/src/main/java/freemarker/core/TemplateObject.java
index 5c3eb88..f3f9e05 100644
--- a/src/main/java/freemarker/core/TemplateObject.java
+++ b/src/main/java/freemarker/core/TemplateObject.java
@@ -42,28 +42,34 @@ public abstract class TemplateObject {
      *  by a negative line numbers, starting from this constant as line 1. */
     static final int RUNTIME_EVAL_LINE_DISPLACEMENT = -1000000000;  
 
-    final void setLocation(Template template, Token begin, Token end)
-    throws ParseException {
+    final void setLocation(Template template, Token begin, Token end) {
         setLocation(template, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
     }
 
-    final void setLocation(Template template, Token begin, TemplateObject end)
-    throws ParseException {
+    final void setLocation(Template template, Token tagBegin, Token tagEnd, TemplateElements children) {
+        TemplateElement lastChild = children.getLast();
+        if (lastChild != null) {
+            // [<#if exp>children]<#else>
+            setLocation(template, tagBegin, lastChild);
+        } else {
+            // [<#if exp>]<#else>
+            setLocation(template, tagBegin, tagEnd);
+        }
+    }
+    
+    final void setLocation(Template template, Token begin, TemplateObject end) {
         setLocation(template, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
     }
-
-    final void setLocation(Template template, TemplateObject begin, Token end)
-    throws ParseException {
+    
+    final void setLocation(Template template, TemplateObject begin, Token end) {
         setLocation(template, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
     }
 
-    final void setLocation(Template template, TemplateObject begin, TemplateObject end)
-    throws ParseException {
+    final void setLocation(Template template, TemplateObject begin, TemplateObject end) {
         setLocation(template, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
     }
 
-    void setLocation(Template template, int beginColumn, int beginLine, int endColumn, int endLine)
-    throws ParseException {
+    void setLocation(Template template, int beginColumn, int beginLine, int endColumn, int endLine) {
         this.template = template;
         this.beginColumn = beginColumn;
         this.beginLine = beginLine;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/TextBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TextBlock.java b/src/main/java/freemarker/core/TextBlock.java
index d245d24..3a7c78f 100644
--- a/src/main/java/freemarker/core/TextBlock.java
+++ b/src/main/java/freemarker/core/TextBlock.java
@@ -21,14 +21,14 @@ package freemarker.core;
 
 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 @@ public final class TextBlock extends TemplateElement {
         this(text.toCharArray(), unparsed);
     }
 
-    private TextBlock(char[] text, boolean unparsed) {
+    TextBlock(char[] text, boolean unparsed) {
         this.text = text;
         this.unparsed = unparsed;
     }
@@ -109,10 +109,7 @@ public final class TextBlock extends TemplateElement {
             return this;
         }
         TemplateElement parentElement = getParentElement();
-        if (
-                (parentElement == null
-                        || parentElement.getParentElement() == null && parentElement instanceof MixedContent)
-                && previousSibling() == null) {
+        if (isTopLevelTextIfParentIs(parentElement) && previousSibling() == null) {
             return this;
         }
         if (!deliberateLeftTrim) {
@@ -224,7 +221,7 @@ public final class TextBlock extends TemplateElement {
                                     break;
                                 }
                             }
-                            if (trimTrailingPart) trailingPart = EMPTY_CHAR_ARRAY;
+                            if (trimTrailingPart) trailingPart = CollectionUtils.EMPTY_CHAR_ARRAY;
                         }
                         this.text = concat(printablePart, trailingPart);
                     }
@@ -354,8 +351,7 @@ public final class TextBlock extends TemplateElement {
                 return false;
             }
             TemplateElement parentElement = getParentElement();
-            boolean atTopLevel = (parentElement == null
-                    || parentElement.getParentElement() == null && parentElement instanceof MixedContent);
+            boolean atTopLevel = isTopLevelTextIfParentIs(parentElement);
             TemplateElement prevSibling = previousSibling();
             TemplateElement nextSibling = nextSibling();
             return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling))
@@ -364,6 +360,11 @@ public final class TextBlock extends TemplateElement {
             return false;
         }
     }
+
+    private boolean isTopLevelTextIfParentIs(TemplateElement parentElement) {
+        return parentElement == null
+                || parentElement.getParentElement() == null && parentElement instanceof MixedContent;
+    }
     
 
     private boolean nonOutputtingType(TemplateElement element) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/TransformBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TransformBlock.java b/src/main/java/freemarker/core/TransformBlock.java
index 4412664..5cfddc4 100644
--- a/src/main/java/freemarker/core/TransformBlock.java
+++ b/src/main/java/freemarker/core/TransformBlock.java
@@ -48,10 +48,10 @@ final class TransformBlock extends TemplateElement {
      */
     TransformBlock(Expression transformExpression, 
                    Map namedArgs,
-                   TemplateElement nestedBlock) {
+                   TemplateElements children) {
         this.transformExpression = transformExpression;
         this.namedArgs = namedArgs;
-        setChildrenFromElement(nestedBlock);
+        setChildren(children);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/java/freemarker/core/UnifiedCall.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/UnifiedCall.java b/src/main/java/freemarker/core/UnifiedCall.java
index 831b7bc..6608b0a 100644
--- a/src/main/java/freemarker/core/UnifiedCall.java
+++ b/src/main/java/freemarker/core/UnifiedCall.java
@@ -50,24 +50,21 @@ final class UnifiedCall extends TemplateElement implements DirectiveCallPlace {
 
     UnifiedCall(Expression nameExp,
          Map namedArgs,
-         TemplateElement nestedBlock,
+         TemplateElements children,
          List bodyParameterNames) {
         this.nameExp = nameExp;
         this.namedArgs = namedArgs;
-        setChildrenFromElement(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;
-        }
-        setChildrenFromElement(nestedBlock);
+        setChildren(children);
         this.bodyParameterNames = bodyParameterNames;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d7e6627/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index a10552f..76533f3 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -2444,37 +2444,37 @@ TemplateElement If() :
 {
     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);
             }
     ]
@@ -2488,12 +2488,12 @@ TemplateElement If() :
 AttemptBlock Attempt() :
 {
     Token start, end;
-    TemplateElement block;
+    TemplateElements children;
     RecoveryBlock recoveryBlock;
 }
 {
     start = <ATTEMPT>
-    block = OptionalBlock()
+    children = MixedContentElements()
     recoveryBlock = Recover()
     (
         end = <END_RECOVER>
@@ -2501,7 +2501,7 @@ AttemptBlock Attempt() :
         end = <END_ATTEMPT>
     )
     {
-        AttemptBlock result = new AttemptBlock(block, recoveryBlock);
+        AttemptBlock result = new AttemptBlock(children, recoveryBlock);
         result.setLocation(template, start, end);
         return result;
     }
@@ -2510,14 +2510,14 @@ AttemptBlock Attempt() :
 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;
     }
 }
@@ -2526,7 +2526,7 @@ TemplateElement List() :
 {
     Expression exp;
     Token loopVar = null, start, end;
-    TemplateElement mainBlock;
+    TemplateElements childrendBeforeElse;
     ElseOfList elseOfList = null;
     ParserIteratorBlockContext iterCtx;
 }
@@ -2546,7 +2546,7 @@ TemplateElement List() :
         }
     }
     
-    mainBlock = OptionalBlock()
+    childrendBeforeElse = MixedContentElements()
     {
         if (loopVar != null) {
             breakableDirectiveNesting--;
@@ -2564,7 +2564,7 @@ TemplateElement List() :
     
     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;
@@ -2581,14 +2581,14 @@ TemplateElement List() :
 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;
         }
 }
@@ -2597,7 +2597,7 @@ IteratorBlock ForEach() :
 {
     Expression exp;
     Token loopVar, start, end;
-    TemplateElement block;
+    TemplateElements children;
 }
 {
     start = <FOREACH>
@@ -2612,14 +2612,14 @@ IteratorBlock ForEach() :
         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;
     }
@@ -2628,7 +2628,7 @@ IteratorBlock ForEach() :
 Items Items() :
 {
     Token loopVar, start, end;
-    TemplateElement block;
+    TemplateElements children;
     ParserIteratorBlockContext iterCtx;
 }
 {
@@ -2657,14 +2657,14 @@ Items Items() :
         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;
     }
@@ -2673,7 +2673,7 @@ Items Items() :
 Sep Sep() :
 {
     Token loopVar, start, end = null;
-    TemplateElement block;
+    TemplateElements children;
 }
 {
     start = <SEP>
@@ -2684,17 +2684,17 @@ Sep Sep() :
                     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;
     }
@@ -2919,7 +2919,7 @@ TemplateElement Assign() :
     String varName;
     ArrayList assignments = new ArrayList();
     Assignment ass;
-    TemplateElement block;
+    TemplateElements children;
 }
 {
     (
@@ -3049,7 +3049,7 @@ TemplateElement Assign() :
 	            }
 	        ]
 	        <DIRECTIVE_END>
-	        block = OptionalBlock()
+	        children = MixedContentElements()
 	        (
 	            end = <END_LOCAL>
 	            {
@@ -3073,7 +3073,7 @@ TemplateElement Assign() :
 	        )
 	        {
 	            BlockAssignment ba = new BlockAssignment(
-	                   block, varName, scope, nsExp,
+	                   children, varName, scope, nsExp,
 	                   getMarkupOutputFormat());
 	            ba.setLocation(template, start, end);
 	            return ba;
@@ -3157,7 +3157,7 @@ Macro Macro() :
     Expression defValue = null;
     List lastIteratorBlockContexts;
     int lastBreakableDirectiveNesting;
-    TemplateElement block;
+    TemplateElements children;
     boolean isFunction = false, hasDefaults = false;
     boolean isCatchAll = false;
     String catchAll = null;
@@ -3233,16 +3233,16 @@ Macro Macro() :
             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);
     	}
     )
     {
@@ -3252,7 +3252,7 @@ Macro Macro() :
         }
         
         inMacro = inFunction = false;
-        Macro result = new Macro(name, argNames, args, catchAll, isFunction, block);
+        Macro result = new Macro(name, argNames, args, catchAll, isFunction, children);
         result.setLocation(template, start, end);
         template.addMacro(result);
         return result;
@@ -3261,15 +3261,15 @@ Macro Macro() :
 
 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;
     }
@@ -3281,7 +3281,7 @@ TemplateElement UnifiedMacroTransform() :
     HashMap namedArgs = null;
     ArrayList positionalArgs = null, bodyParameters = null;
     Expression startTagNameExp;
-    TemplateElement nestedBlock = null;
+    TemplateElements children;
     Expression exp;
     int pushedCtxCount = 0;
 }
@@ -3313,7 +3313,7 @@ TemplateElement UnifiedMacroTransform() :
         ]
     ]
     (
-        end = <EMPTY_DIRECTIVE_END>
+        end = <EMPTY_DIRECTIVE_END> { children = TemplateElements.EMPTY; }
         |
         (
             <DIRECTIVE_END> {
@@ -3340,7 +3340,7 @@ TemplateElement UnifiedMacroTransform() :
                    }
                 }
             }
-            nestedBlock = OptionalBlock()
+            children = MixedContentElements()
             end = <UNIFIED_CALL_END>
             {
                 for (int i = 0; i < pushedCtxCount; i++) {
@@ -3363,8 +3363,8 @@ TemplateElement UnifiedMacroTransform() :
     )
     {
         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;
     }
@@ -3397,9 +3397,9 @@ TemplateElement Call() :
     {
         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);
@@ -3489,7 +3489,7 @@ TransformBlock Transform() :
 {
     Token start, end, argName;
     Expression exp, argExp;
-    TemplateElement content = null;
+    TemplateElements children = null;
     HashMap args = null;
 }
 {
@@ -3510,12 +3510,12 @@ TransformBlock Transform() :
         |
         (
             <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;
     }
@@ -3563,7 +3563,7 @@ SwitchBlock Switch() :
 Case Case() :
 {
     Expression exp;
-    TemplateElement block;
+    TemplateElements children;
     Token start;
 }
 {
@@ -3573,10 +3573,10 @@ Case Case() :
         |
         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;
     }
 }
@@ -3585,7 +3585,7 @@ EscapeBlock Escape() :
 {
     Token variable, start, end;
     Expression escapeExpr;
-    TemplateElement content;
+    TemplateElements children;
 }
 {
     start = <ESCAPE>
@@ -3606,9 +3606,9 @@ EscapeBlock Escape() :
         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>
@@ -3621,7 +3621,7 @@ EscapeBlock Escape() :
 NoEscapeBlock NoEscape() :
 {
     Token start, end;
-    TemplateElement content;
+    TemplateElements children;
 }
 {
     start = <NOESCAPE>
@@ -3631,11 +3631,11 @@ NoEscapeBlock NoEscape() :
         }
         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;
     }
@@ -3645,7 +3645,7 @@ OutputFormatBlock OutputFormat() :
 {
     Token start, end;
     Expression paramExp;
-    TemplateElement content;
+    TemplateElements children;
     OutputFormat lastOutputFormat;
 }
 {
@@ -3718,10 +3718,10 @@ OutputFormatBlock OutputFormat() :
             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;
@@ -3733,7 +3733,7 @@ OutputFormatBlock OutputFormat() :
 AutoEscBlock AutoEsc() :
 {
     Token start, end;
-    TemplateElement content;
+    TemplateElements children;
     int lastAutoEscapingPolicy;
 }
 {
@@ -3744,10 +3744,10 @@ AutoEscBlock AutoEsc() :
         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; 
@@ -3759,7 +3759,7 @@ AutoEscBlock AutoEsc() :
 NoAutoEscBlock NoAutoEsc() :
 {
     Token start, end;
-    TemplateElement content;
+    TemplateElements children;
     int lastAutoEscapingPolicy;
 }
 {
@@ -3769,10 +3769,10 @@ NoAutoEscBlock NoAutoEsc() :
         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;
@@ -3827,7 +3827,7 @@ TemplateElement FreemarkerDirective() :
 }
 {
     // 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()
         |
@@ -3925,7 +3925,7 @@ TextBlock PCData() :
         }
     )+
     {
-        if (stripText && mixedContentNesting == 1) return TextBlock.EMPTY_BLOCK;
+        if (stripText && mixedContentNesting == 1) return new TextBlock(CollectionUtils.EMPTY_CHAR_ARRAY, false);
 
         TextBlock result = new TextBlock(buf.toString(), false);
         result.setLocation(template, start, t);
@@ -3960,10 +3960,11 @@ Token UnparsedContent(Token start, StringBuilder buf) :
     }
 }
 
-MixedContent MixedContent() :
+TemplateElements MixedContentElements() :
 {
-    MixedContent mixedContent = new MixedContent();
-    TemplateElement elem, begin = null;
+    TemplateElement[] childBuffer = null;
+    int childCount = 0;
+    TemplateElement elem;
     mixedContentNesting++;
 }
 {
@@ -3979,16 +3980,22 @@ MixedContent MixedContent() :
             elem = FreemarkerDirective()
         )
         {
-            if (begin == null) {
-                begin = elem;
+            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;
             }
-            mixedContent.addElement(elem);
+            childBuffer[childCount - 1] = elem;
         }
-    )+
+    )*
     {
         mixedContentNesting--;
-        mixedContent.setLocation(template, begin, elem);
-        return mixedContent;
+        return childBuffer != null ? new TemplateElements(childBuffer, childCount) : TemplateElements.EMPTY;
     }
 }
 
@@ -4014,7 +4021,7 @@ TemplateElement FreeMarkerText() :
             if (begin == null) {
             	begin = elem;
             }
-            nodes.addElement(elem);
+            nodes.addChild(elem);
         }
     )+
     {
@@ -4023,25 +4030,6 @@ TemplateElement FreeMarkerText() :
     }
 }
 
-/**
- * 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;
@@ -4270,20 +4258,21 @@ List<Object> StaticTextAndInterpolations() :
  */
 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;
     }
 }


Mime
View raw message