Author: cbrisson
Date: Tue Aug 30 16:18:33 2016
New Revision: 1758416

URL: http://svn.apache.org/viewvc?rev=1758416&view=rev
Log:
[engine] Add a configurable space gobbling feature, to control indentation in 
the generated code.

Possible values for the 'space.gobbling' configuration key:

 - none : no space gobbling at all
 - bc : Velocity 1.x backward compatible space gobbling
 - lines (the default) : gobbles whitespaces and endline from lines containing 
a single VTL directive
 - structured (beta stage) : like 'lines', but also fixes indentation in 
embedded text blocks

The commit also includes some lookahead optimizations and cleaning in the 
javacc parser code.


Added:
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java
    
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/SpaceGobblingTestCase.java
      - copied, changed from r1754151, 
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java
    velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.BC
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.NONE
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.SMART
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.STRUCTURED
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.BC
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.NONE
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.SMART
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.STRUCTURED
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.BC
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.NONE
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.SMART
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.STRUCTURED
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.BC
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.NONE
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.SMART
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.STRUCTURED
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.BC
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.NONE
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.SMART
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.STRUCTURED
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.BC
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.NONE
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.SMART
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.STRUCTURED
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/foreach_smart.vtl
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/foreach_structured.vtl
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/if.vtl
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/macro.vtl
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/set.vtl
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/gobbling/structured.vtl
Modified:
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/BaseVisitor.java
    velocity/engine/trunk/velocity-engine-core/src/main/parser/Parser.jjt
    
velocity/engine/trunk/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
    
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/InlineScopeVMTestCase.java
    
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/ScopeTestCase.java
    
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestCase.java
    
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity615TestCase.java
    
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity631TestCase.java
    
velocity/engine/trunk/velocity-engine-core/src/test/resources/conversion/compare/matrix.cmp

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
 Tue Aug 30 16:18:33 2016
@@ -266,6 +266,19 @@ public interface RuntimeConstants
      */
     String PARSER_POOL_SIZE = "parser.pool.size";
 
+    /**
+     * Space gobbling mode
+     */
+    String SPACE_GOBBLING = "space.gobbling";
+
+    /**
+     * Space gobbling modes
+     */
+    public enum SpaceGobbling
+    {
+        NONE, BC, LINES, STRUCTURED
+    }
+
     /*
      * ----------------------------------------------------------------------
      * These constants are used internally by the Velocity runtime i.e.

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java
 Tue Aug 30 16:18:33 2016
@@ -55,7 +55,6 @@ import org.apache.velocity.util.introspe
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
@@ -66,6 +65,7 @@ import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Properties;
 
 /**
@@ -193,6 +193,11 @@ public class RuntimeInstance implements
     private Uberspect uberSpect;
     private String encoding;
 
+    /*
+     * Space gobbling mode
+     */
+    private SpaceGobbling spaceGobbling;
+
     /**
      * Creates a new RuntimeInstance object.
      */
@@ -334,7 +339,19 @@ public class RuntimeInstance implements
      */
     private void initializeSelfProperties()
     {
+        /* initialize string interning (defaults to false) */
         stringInterning = getBoolean(RUNTIME_STRING_INTERNING, true);
+
+        /* initialize indentation mode (defaults to 'lines') */
+        String im = getString(SPACE_GOBBLING, "lines");
+        try
+        {
+            spaceGobbling = SpaceGobbling.valueOf(im.toUpperCase());
+        }
+        catch (NoSuchElementException nse)
+        {
+            spaceGobbling = SpaceGobbling.LINES;
+        }
     }
 
     /**
@@ -1847,4 +1864,13 @@ public class RuntimeInstance implements
     {
         return stringInterning;
     }
+
+    /**
+     * get space gobbling mode
+     * @return indentation mode
+     */
+    public SpaceGobbling getSpaceGobbling()
+    {
+        return spaceGobbling;
+    }
 }

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
 Tue Aug 30 16:18:33 2016
@@ -26,6 +26,7 @@ import org.apache.velocity.context.Conte
 import org.apache.velocity.exception.MethodInvocationException;
 import org.apache.velocity.exception.ParseErrorException;
 import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
 import org.apache.velocity.runtime.directive.Directive;
 import org.apache.velocity.runtime.directive.Macro;
 import org.apache.velocity.runtime.parser.ParseException;
@@ -34,7 +35,6 @@ import org.apache.velocity.runtime.parse
 import org.apache.velocity.runtime.parser.node.SimpleNode;
 import org.apache.velocity.runtime.resource.ContentResource;
 import org.apache.velocity.util.ExtProperties;
-import org.apache.velocity.util.introspection.Introspector;
 import org.apache.velocity.util.introspection.Uberspect;
 import org.slf4j.Logger;
 
@@ -466,4 +466,6 @@ public interface RuntimeServices
     public Directive getDirective(String name);
 
     public boolean useStringInterning();
+
+    public SpaceGobbling getSpaceGobbling();
 }

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java
 Tue Aug 30 16:18:33 2016
@@ -24,6 +24,7 @@ import org.apache.velocity.exception.Met
 import org.apache.velocity.exception.ParseErrorException;
 import org.apache.velocity.exception.ResourceNotFoundException;
 import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
 import org.apache.velocity.runtime.parser.Parser;
 
 import java.io.IOException;
@@ -35,6 +36,12 @@ import java.io.Writer;
  */
 public class ASTBlock extends SimpleNode
 {
+    private String prefix = "";
+    private String postfix = "";
+
+    // used during parsing
+    public boolean endsWithNewline = false;
+    
     /**
      * @param id
      */
@@ -61,29 +68,76 @@ public class ASTBlock extends SimpleNode
     }
 
     /**
+     * @throws TemplateInitException
+     * @see 
org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter,
 java.lang.Object)
+     */
+    public Object init( InternalContextAdapter context, Object data) throws 
TemplateInitException
+    {
+        Object obj = super.init(context, data);
+        cleanupParserAndTokens(); // drop reference to Parser and all JavaCC 
Tokens
+        return obj;
+    }
+
+    /**
+     * set indentation prefix
+     * @param prefix
+     */
+    public void setPrefix(String prefix)
+    {
+        this.prefix = prefix;
+    }
+
+    /**
+     * get indentation prefix
+     * @return indentation prefix
+     */
+    public String getPrefix()
+    {
+        return prefix;
+    }
+
+    /**
+     * set indentation postfix
+     * @param postfix
+     */
+    public void setPostfix(String postfix)
+    {
+        this.postfix = postfix;
+    }
+
+    /**
+     * get indentation postfix
+     * @return indentation prefix
+     */
+    public String getPostfix()
+    {
+        return postfix;
+    }
+
+    /**
      * @see 
org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter,
 java.io.Writer)
      */
     public boolean render( InternalContextAdapter context, Writer writer)
         throws IOException, MethodInvocationException,
                ResourceNotFoundException, ParseErrorException
     {
+        SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling();
+
+        if (spaceGobbling == SpaceGobbling.NONE)
+        {
+            writer.write(prefix);
+        }
+
         int i, k = jjtGetNumChildren();
 
         for (i = 0; i < k; i++)
             jjtGetChild(i).render(context, writer);
 
+        if (spaceGobbling.compareTo(SpaceGobbling.LINES) < 0)
+        {
+            writer.write(postfix);
+        }
+
         return true;
     }
-    
-    /**
-     * @throws TemplateInitException
-     * @see 
org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter,
 java.lang.Object)
-     */
-    public Object init( InternalContextAdapter context, Object data) throws 
TemplateInitException
-    {
-       Object obj = super.init(context, data);
-       cleanupParserAndTokens(); // drop reference to Parser and all JavaCC 
Tokens
-       return obj;
-    }
-    
 }

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java
 Tue Aug 30 16:18:33 2016
@@ -25,11 +25,14 @@ import org.apache.velocity.exception.Par
 import org.apache.velocity.exception.ResourceNotFoundException;
 import org.apache.velocity.exception.TemplateInitException;
 import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
 import org.apache.velocity.runtime.directive.BlockMacro;
 import org.apache.velocity.runtime.directive.Directive;
 import org.apache.velocity.runtime.directive.RuntimeMacro;
 import org.apache.velocity.runtime.parser.ParseException;
 import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.ParserConstants;
+import org.apache.velocity.runtime.parser.Token;
 
 import java.io.IOException;
 import java.io.Writer;
@@ -55,6 +58,9 @@ public class ASTDirective extends Simple
     private boolean isDirective;
     private boolean isInitialized;
 
+    private String prefix = "";
+    private String postfix = "";
+
     /**
      * @param id
      */
@@ -121,7 +127,9 @@ public class ASTDirective extends Simple
                             e);
                 }
 
-                directive.setLocation(getLine(), getColumn(), getTemplate());
+                Token t = first;
+                if (t.kind == ParserConstants.WHITESPACE) t = t.next;
+                directive.setLocation(t.beginLine, t.beginColumn, 
getTemplate());
                 directive.init(rsvc, context, this);
             }
             else if( directiveName.startsWith("@") )
@@ -191,28 +199,87 @@ public class ASTDirective extends Simple
             saveTokenImages();
             cleanupParserAndTokens();
         }
+
+        if (rsvc.getSpaceGobbling() == SpaceGobbling.STRUCTURED && 
isInitialized && isDirective && directive.getType() == Directive.BLOCK)
+        {
+            NodeUtils.fixIndentation(this, prefix);
+        }
         
         return data;
     }
 
     /**
+     * set indentation prefix
+     * @param prefix
+     */
+    public void setPrefix(String prefix)
+    {
+        this.prefix = prefix;
+    }
+
+    /**
+     * get indentation prefix
+     * @return indentation prefix
+     */
+    public String getPrefix()
+    {
+        return prefix;
+    }
+
+    /**
+     * set indentation postfix
+     * @param postfix
+     */
+    public void setPostfix(String postfix)
+    {
+        this.postfix = postfix;
+    }
+
+    public int getDirectiveType()
+    {
+        return directive.getType();
+    }
+
+    /**
+     * get indentation postfix
+     * @return indentation prefix
+     */
+    public String getPostfix()
+    {
+        return postfix;
+    }
+
+    /**
      * @see 
org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter,
 java.io.Writer)
      */
     public boolean render( InternalContextAdapter context, Writer writer)
         throws IOException,MethodInvocationException, 
ResourceNotFoundException, ParseErrorException
     {
+        SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling();
         /*
          *  normal processing
          */
 
         if (isDirective)
         {
+            if (spaceGobbling.compareTo(SpaceGobbling.LINES) < 0)
+            {
+                writer.write(prefix);
+            }
+
             directive.render(context, writer, this);
+
+            if (spaceGobbling == SpaceGobbling.NONE)
+            {
+                writer.write(postfix);
+            }
         }
         else
         {
+            writer.write(prefix);
             writer.write( "#");
-            writer.write( directiveName );
+            writer.write(directiveName);
+            writer.write(postfix);
         }
 
         return true;

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java
 Tue Aug 30 16:18:33 2016
@@ -24,6 +24,7 @@ import org.apache.velocity.exception.Met
 import org.apache.velocity.exception.ParseErrorException;
 import org.apache.velocity.exception.ResourceNotFoundException;
 import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants;
 import org.apache.velocity.runtime.parser.Parser;
 
 import java.io.IOException;
@@ -59,6 +60,17 @@ public class ASTElseIfStatement extends
     }
 
     /**
+     * @throws TemplateInitException
+     * @see 
org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter,
 java.lang.Object)
+     */
+    public Object init( InternalContextAdapter context, Object data) throws 
TemplateInitException
+    {
+        Object obj = super.init(context, data);
+         cleanupParserAndTokens(); // drop reference to Parser and all JavaCC 
Tokens
+        return obj;
+    }
+
+    /**
      * @see 
org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor,
 java.lang.Object)
      */
     public Object jjtAccept(ParserVisitor visitor, Object data)
@@ -92,16 +104,4 @@ public class ASTElseIfStatement extends
     {
         return jjtGetChild(1).render( context, writer );
     }
-    
-    /**
-     * @throws TemplateInitException
-     * @see 
org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter,
 java.lang.Object)
-     */
-    public Object init( InternalContextAdapter context, Object data) throws 
TemplateInitException
-    {
-       Object obj = super.init(context, data);
-       cleanupParserAndTokens(); // drop reference to Parser and all JavaCC 
Tokens
-       return obj;
-    }
-    
 }

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java
 Tue Aug 30 16:18:33 2016
@@ -34,6 +34,7 @@ import org.apache.velocity.exception.Met
 import org.apache.velocity.exception.ParseErrorException;
 import org.apache.velocity.exception.ResourceNotFoundException;
 import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
 import org.apache.velocity.runtime.parser.Parser;
 
 import java.io.IOException;
@@ -45,6 +46,9 @@ import java.io.Writer;
  */
 public class ASTIfStatement extends SimpleNode
 {
+    private String prefix = "";
+    private String postfix = "";
+
     /**
      * @param id
      */
@@ -62,7 +66,6 @@ public class ASTIfStatement extends Simp
         super(p, id);
     }
 
-
     /**
      * @see 
org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor,
 java.lang.Object)
      */
@@ -72,12 +75,73 @@ public class ASTIfStatement extends Simp
     }
 
     /**
+     * @throws TemplateInitException
+     * @see 
org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter,
 java.lang.Object)
+     */
+    public Object init( InternalContextAdapter context, Object data) throws 
TemplateInitException
+    {
+        Object obj = super.init(context, data);
+
+        /* handle structured space gobbling */
+        if (rsvc.getSpaceGobbling() == SpaceGobbling.STRUCTURED && 
postfix.length() > 0)
+        {
+            NodeUtils.fixIndentation(this, prefix);
+        }
+
+        cleanupParserAndTokens(); // drop reference to Parser and all JavaCC 
Tokens
+        return obj;
+    }
+
+    /**
+     * set indentation prefix
+     * @param prefix
+     */
+    public void setPrefix(String prefix)
+    {
+        this.prefix = prefix;
+    }
+
+    /**
+     * get indentation prefix
+     * @return prefix
+     */
+    public String getPrefix()
+    {
+        return prefix;
+    }
+
+    /**
+     * set indentation postfix
+     * @param postfix
+     */
+    public void setPostfix(String postfix)
+    {
+        this.postfix = postfix;
+    }
+
+    /**
+     * get indentation postfix
+     * @return postfix
+     */
+    public String getPostfix()
+    {
+        return postfix;
+    }
+
+    /**
      * @see 
org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter,
 java.io.Writer)
      */
     public boolean render( InternalContextAdapter context, Writer writer)
         throws IOException,MethodInvocationException,
                ResourceNotFoundException, ParseErrorException
     {
+        SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling();
+
+        if (spaceGobbling.compareTo(SpaceGobbling.LINES) < 0)
+        {
+            writer.write(prefix);
+        }
+
         /*
          * Check if the #if(expression) construct evaluates to true:
          * if so render and leave immediately because there
@@ -105,15 +169,20 @@ public class ASTIfStatement extends Simp
             if (jjtGetChild(i).evaluate(context))
             {
                 jjtGetChild(i).render(context, writer);
-                return true;
+                break;
             }
         }
 
+        if (spaceGobbling == SpaceGobbling.NONE)
+        {
+            writer.write(postfix);
+        }
+
         /*
-         * This is reached when an ASTIfStatement
-         * consists of an if/elseif sequence where
-         * none of the nodes evaluate to true.
+         * This is reached without rendering anything when an ASTIfStatement
+         * consists of an if/elseif sequence where none of the nodes evaluate 
to true.
          */
+
         return true;
     }
 
@@ -124,15 +193,4 @@ public class ASTIfStatement extends Simp
     public void process( InternalContextAdapter context, ParserVisitor visitor)
     {
     }
-    
-    /**
-     * @throws TemplateInitException
-     * @see 
org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter,
 java.lang.Object)
-     */
-    public Object init( InternalContextAdapter context, Object data) throws 
TemplateInitException
-    {
-       Object obj = super.init(context, data);
-       cleanupParserAndTokens(); // drop reference to Parser and all JavaCC 
Tokens
-       return obj;
-    }
 }
\ No newline at end of file

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java
 Tue Aug 30 16:18:33 2016
@@ -24,6 +24,7 @@ import org.apache.velocity.context.Inter
 import org.apache.velocity.exception.MethodInvocationException;
 import org.apache.velocity.exception.TemplateInitException;
 import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
 import org.apache.velocity.runtime.parser.Parser;
 import org.apache.velocity.util.introspection.Info;
 
@@ -43,6 +44,8 @@ public class ASTSetDirective extends Sim
     private Node right = null;
     private ASTReference left = null;
     private boolean isInitialized;
+    private String prefix = "";
+    private String postfix = "";
 
     /**
      *  This is really immutable after the init, so keep one for this node
@@ -111,7 +114,29 @@ public class ASTSetDirective extends Sim
              *  grab this now.  No need to redo each time
              */
             leftReference = left.firstImage.substring(1);
-        
+
+            /* handle backward compatible space gobbling if asked so */
+            if (rsvc.getSpaceGobbling() == SpaceGobbling.BC)
+            {
+                Node previousNode = null;
+                for (int brother = 0; brother < parent.jjtGetNumChildren(); 
++brother)
+                {
+                    Node node = parent.jjtGetChild(brother);
+                    if (node == this) break;
+                    previousNode = node;
+                }
+                if (previousNode == null) prefix = "";
+                else if (previousNode instanceof ASTText)
+                {
+                    ASTText text = (ASTText)previousNode;
+                    if (text.getCtext().matches("[ \t]*"))
+                    {
+                        text.setCtext("");
+                    }
+                }
+                else prefix = "";
+            }
+
             isInitialized = true;
             
             cleanupParserAndTokens();
@@ -121,6 +146,42 @@ public class ASTSetDirective extends Sim
     }
 
     /**
+     * set indentation prefix
+     * @param prefix
+     */
+    public void setPrefix(String prefix)
+    {
+        this.prefix = prefix;
+    }
+
+    /**
+     * get indentation prefix
+     * @return indentation prefix
+     */
+    public String getPrefix()
+    {
+        return prefix;
+    }
+
+    /**
+     * set indentation postfix
+     * @param postfix
+     */
+    public void setPostfix(String postfix)
+    {
+        this.postfix = postfix;
+    }
+
+    /**
+     * get indentation postfix
+     * @return indentation prefix
+     */
+    public String getPostfix()
+    {
+        return postfix;
+    }
+
+    /**
      *   puts the value of the RHS into the context under the key of the LHS
      * @param context
      * @param writer
@@ -131,6 +192,18 @@ public class ASTSetDirective extends Sim
     public boolean render( InternalContextAdapter context, Writer writer)
         throws IOException, MethodInvocationException
     {
+        SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling();
+
+        /* Velocity 1.x space gobbling for #set is rather wacky:
+           prefix is eaten *only* if previous token is not a text node.
+           We handle this by appropriately emptying the prefix in BC mode.
+         */
+
+        if (spaceGobbling.compareTo(SpaceGobbling.LINES) < 0)
+        {
+            writer.write(prefix);
+        }
+
         /*
          *  get the RHS node, and its value
          */
@@ -146,7 +219,13 @@ public class ASTSetDirective extends Sim
             }
             EventHandlerUtil.invalidSetMethod(rsvc, context, leftReference, 
rightReference, uberInfo);
         }
-        
+
+        if (spaceGobbling == SpaceGobbling.NONE)
+        {
+            writer.write(postfix);
+        }
+
+
         return left.setValue(context, value);
     }
     

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java
 Tue Aug 30 16:18:33 2016
@@ -32,7 +32,7 @@ import java.io.Writer;
  */
 public class ASTText extends SimpleNode
 {
-    private char[] ctext;
+    private String ctext;
 
     /**
      * @param id
@@ -52,6 +52,24 @@ public class ASTText extends SimpleNode
     }
 
     /**
+     * text getter
+     * @return ctext
+     */
+    public String getCtext()
+    {
+        return ctext;
+    }
+
+    /**
+     * text setter
+     * @param ctext
+     */
+    public void setCtext(String ctext)
+    {
+        this.ctext = ctext;
+    }
+
+    /**
      * @see 
org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor,
 java.lang.Object)
      */
     public Object jjtAccept(ParserVisitor visitor, Object data)
@@ -65,11 +83,14 @@ public class ASTText extends SimpleNode
     public Object init( InternalContextAdapter context, Object data)
     throws TemplateInitException
     {
+        StringBuilder builder = new StringBuilder();
         Token t = getFirstToken();
-
-        String text = NodeUtils.tokenLiteral( t );
-
-        ctext = text.toCharArray();
+        for (; t != getLastToken(); t = t.next)
+        {
+            builder.append(NodeUtils.tokenLiteral(t));
+        }
+        builder.append(NodeUtils.tokenLiteral(t));
+        ctext = builder.toString();
         
         cleanupParserAndTokens();
 

Added: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java?rev=1758416&view=auto
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java
 (added)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java
 Tue Aug 30 16:18:33 2016
@@ -0,0 +1,362 @@
+package org.apache.velocity.runtime.parser.node;
+
+import org.apache.velocity.runtime.directive.Directive;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class to fix indentation in structured mode.
+ */
+
+public class IndentationFixer implements ParserVisitor
+{
+    protected String parentIndentation = null;
+    protected String extraIndentation = null;
+    protected Pattern fix = null;
+
+    protected void fillExtraIndentation(String prefix)
+    {
+        Pattern captureExtraIndentation = Pattern.compile("^" + 
parentIndentation + "(\\s+)");
+        Matcher matcher = captureExtraIndentation.matcher(prefix);
+        if (matcher.find())
+        {
+            extraIndentation = matcher.group(1);
+            fix = Pattern.compile("^" + parentIndentation + extraIndentation, 
Pattern.MULTILINE);
+        }
+        else
+        {
+            extraIndentation = "";
+        }
+    }
+
+    public IndentationFixer(String parentIndentation)
+    {
+        this.parentIndentation = parentIndentation;
+    }
+
+    @Override
+    public Object visit(SimpleNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTprocess node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTText node, Object data)
+    {
+        String text = node.getCtext();
+        if (extraIndentation == null)
+        {
+            fillExtraIndentation(text);
+        }
+        if (extraIndentation.length() > 0)
+        {
+            Matcher matcher = fix.matcher(text);
+            node.setCtext(matcher.replaceAll(parentIndentation));
+        }
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTEscapedDirective node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTEscape node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTComment node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTTextblock node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTFloatingPointLiteral node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTIntegerLiteral node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTStringLiteral node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTIdentifier node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTWord node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTDirectiveAssign node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTDirective node, Object data)
+    {
+        String prefix = node.getPrefix();
+        if (prefix.length() > 0)
+        {
+            if (extraIndentation == null)
+            {
+                fillExtraIndentation(prefix);
+            }
+            if (extraIndentation.length() > 0)
+            {
+                Matcher matcher = fix.matcher(prefix);
+                node.setPrefix(matcher.replaceAll(parentIndentation));
+                if (node.getDirectiveType() == Directive.BLOCK)
+                {
+                    node.childrenAccept(this, null);
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTBlock node, Object data)
+    {
+        String prefix = node.getPrefix();
+        if (prefix.length() > 0)
+        {
+            node.childrenAccept(this, null);
+        }
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTMap node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTObjectArray node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTIntegerRange node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTMethod node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTIndex node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTReference node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTTrue node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTFalse node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTIfStatement node, Object data)
+    {
+        String prefix = node.getPrefix();
+        if (prefix.length() > 0)
+        {
+            if (extraIndentation == null)
+            {
+                fillExtraIndentation(prefix);
+            }
+            if (extraIndentation.length() > 0)
+            {
+                Matcher matcher = fix.matcher(prefix);
+                node.setPrefix(matcher.replaceAll(parentIndentation));
+                node.childrenAccept(this, null);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTElseStatement node, Object data)
+    {
+        if (extraIndentation != null && extraIndentation.length() > 0)
+        {
+            node.childrenAccept(this, null);
+        }
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTElseIfStatement node, Object data)
+    {
+        if (extraIndentation != null && extraIndentation.length() > 0)
+        {
+            node.childrenAccept(this, null);
+        }
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTSetDirective node, Object data)
+    {
+        String prefix = node.getPrefix();
+        if (prefix.length() > 0)
+        {
+            if (extraIndentation == null)
+            {
+                fillExtraIndentation(prefix);
+            }
+            if (extraIndentation.length() > 0)
+            {
+                Matcher matcher = fix.matcher(prefix);
+                node.setPrefix(matcher.replaceAll(parentIndentation));
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTExpression node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTAssignment node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTOrNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTAndNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTEQNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTNENode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTLTNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTGTNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTLENode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTGENode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTAddNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTSubtractNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTMulNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTDivNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTModNode node, Object data)
+    {
+        return null;
+    }
+
+    @Override
+    public Object visit(ASTNotNode node, Object data)
+    {
+        return null;
+    }
+}

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java
 Tue Aug 30 16:18:33 2016
@@ -147,4 +147,17 @@ public class NodeUtils
             return t.image;
         }
     }
+
+    /**
+     * Fix children indentation in structured space gobbling mode.
+     * @param parent
+     * @param parentIndentation
+     * @param extraIndentation
+     * @return
+     */
+    public static void fixIndentation(SimpleNode parent, String 
parentIndentation)
+    {
+        IndentationFixer fixer = new IndentationFixer(parentIndentation);
+        parent.childrenAccept(fixer, null);
+    }
 }

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java
 Tue Aug 30 16:18:33 2016
@@ -41,6 +41,7 @@ public interface ParserVisitor
    * @param data
    * @return The object rendered by this node.
    */
+
   public Object visit(ASTprocess node, Object data);
 
   /**
@@ -48,6 +49,13 @@ public interface ParserVisitor
    * @param data
    * @return The object rendered by this node.
    */
+  public Object visit(ASTText node, Object data);
+
+  /**
+   * @param node
+   * @param data
+   * @return The object rendered by this node.
+   */
   public Object visit(ASTEscapedDirective node, Object data);
 
   /**
@@ -63,6 +71,12 @@ public interface ParserVisitor
    * @return The object rendered by this node.
    */
   public Object visit(ASTComment node, Object data);
+  /**
+   * @param node
+   * @param data
+   * @return The object rendered by this node.
+   */
+  public Object visit(ASTTextblock node, Object data);
 
   /**
    * @param node
@@ -104,6 +118,13 @@ public interface ParserVisitor
    * @param data
    * @return The object rendered by this node.
    */
+
+  public Object visit(ASTDirectiveAssign node, Object data);
+  /**
+   * @param node
+   * @param data
+   * @return The object rendered by this node.
+   */
   public Object visit(ASTDirective node, Object data);
 
   /**
@@ -146,28 +167,28 @@ public interface ParserVisitor
    * @param data
    * @return The object rendered by this node.
    */
-  public Object visit(ASTReference node, Object data);
+  public Object visit(ASTIndex node, Object data);
 
   /**
    * @param node
    * @param data
    * @return The object rendered by this node.
    */
-  public Object visit(ASTTrue node, Object data);
+  public Object visit(ASTReference node, Object data);
 
   /**
    * @param node
    * @param data
    * @return The object rendered by this node.
    */
-  public Object visit(ASTFalse node, Object data);
+  public Object visit(ASTTrue node, Object data);
 
   /**
    * @param node
    * @param data
    * @return The object rendered by this node.
    */
-  public Object visit(ASTText node, Object data);
+  public Object visit(ASTFalse node, Object data);
 
   /**
    * @param node

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/BaseVisitor.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/BaseVisitor.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/BaseVisitor.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/BaseVisitor.java
 Tue Aug 30 16:18:33 2016
@@ -266,6 +266,15 @@ public abstract class BaseVisitor implem
     }
 
     /**
+     * @see 
org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIndex,
 java.lang.Object)
+     */
+    public Object visit(ASTIndex node, Object data)
+    {
+        data = node.childrenAccept(this, data);
+        return data;
+    }
+
+    /**
      * @see 
org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTReference,
 java.lang.Object)
      */
     public Object visit(ASTReference node, Object data)
@@ -347,6 +356,15 @@ public abstract class BaseVisitor implem
     }
 
     /**
+     * @see 
org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTTextblock,
 java.lang.Object)
+     */
+    public Object visit(ASTTextblock node, Object data)
+    {
+        data = node.childrenAccept(this, data);
+        return data;
+    }
+
+    /**
      * @see 
org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTObjectArray,
 java.lang.Object)
      */
     public Object visit(ASTObjectArray node, Object data)
@@ -371,6 +389,15 @@ public abstract class BaseVisitor implem
     {
         data = node.childrenAccept(this, data);
         return data;
+    }
+
+    /**
+     * @see 
org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTDirectiveAssign,
 java.lang.Object)
+     */
+    public Object visit(ASTDirectiveAssign node, Object data)
+    {
+        data = node.childrenAccept(this, data);
+        return data;
     }
 
     /**

Modified: velocity/engine/trunk/velocity-engine-core/src/main/parser/Parser.jjt
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/parser/Parser.jjt?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/parser/Parser.jjt 
(original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/parser/Parser.jjt Tue 
Aug 30 16:18:33 2016
@@ -70,8 +70,9 @@ options
     /**
      *  for debugging purposes.  Keep false
      */
-    DEBUG_PARSER=false;
-    DEBUG_TOKEN_MANAGER=false;
+    DEBUG_PARSER = false;
+    DEBUG_LOOKAHEAD = false;
+    DEBUG_TOKEN_MANAGER = false;
 }
 
 PARSER_BEGIN(Parser)
@@ -393,7 +394,6 @@ TOKEN_MGR_DECLS:
     public boolean debugPrint = false;
 
     private boolean inReference;
-    public boolean inDirective;
     private boolean inComment;
     public  boolean inSet;
 
@@ -468,7 +468,6 @@ TOKEN_MGR_DECLS:
         lparen = 0;
         rparen = 0;
         inReference = false;
-        inDirective = false;
         inComment = false;
         inSet = false;
 
@@ -544,13 +543,9 @@ TOKEN_MGR_DECLS:
  *
  * Tokens
  *
- *  Note : we now have another state, REFMODIFIER.  This is sort of a
- *  type of REFERENCE state, simply use to use the DIRECTIVE token
- *  set when we are processing a $foo.bar() construct
- *
  * ------------------------------------------------------------------------- */
 
-<REFERENCE, REFMODIFIER>
+<REFERENCE, REFMODIFIER, REFMOD3>
 TOKEN:
 {
    <INDEX_LBRACKET: "[">
@@ -623,10 +618,7 @@ TOKEN:
 <DIRECTIVE>
 TOKEN:
 {
-    /*
-     *  We will eat any whitespace upto and including a newline for directives
-     */
-    <RPAREN: ")" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ))?>
+    <RPAREN: ")">
     {
        RPARENHandler();
     }
@@ -648,7 +640,7 @@ TOKEN:
          * $foo.bar().blargh().woogie().doogie()
          */
 
-        SwitchTo( REFERENCE );
+        SwitchTo( REFMOD3 );
     }
 }
 
@@ -676,11 +668,6 @@ TOKEN:
 
 
 /*
- * needed because #set is so wacky in it's desired behavior.  We want set
- * to eat any preceeding whitespace so it is invisible in formatting.
- * (As it should be.)  If this works well, I am going to chuck the whole MORE:
- * token abomination.
- *
  * We added the lexical states REFERENCE, REFMODIFIER, REFMOD2 to
  * address JIRA issue VELOCITY-631. With SET_DIRECTIVE only in the
  * DEFAULT lexical state the following VTL fails "$a#set($b = 1)"
@@ -695,12 +682,10 @@ TOKEN:
 <DEFAULT, REFERENCE, REFMODIFIER, REFMOD2>
 TOKEN:
 {
-  <SET_DIRECTIVE: (" "|"\t")*  ("#set" | "#{set}")  (" "|"\t")* "(">
+  <SET_DIRECTIVE: ("#set" | "#{set}")  (" "|"\t")* "(">
     {
         if (! inComment)
         {
-            inDirective = true;
-
             if ( debugPrint )
                 System.out.print("#set :  going to " + DIRECTIVE );
 
@@ -833,8 +818,6 @@ MORE :
                 stateStackPop();
             }
 
-            inDirective = true;
-
             if ( debugPrint )
                 System.out.print("# :  going to " + DIRECTIVE );
 
@@ -844,7 +827,6 @@ MORE :
     }
 }
 
-
 // treat the single line comment case separately
 // to avoid ##<EOF> errors
 <DEFAULT,PRE_DIRECTIVE,DIRECTIVE,REFERENCE>
@@ -867,14 +849,6 @@ TOKEN :
      }
 }
 
-TOKEN :
-{
-    <DOUBLE_ESCAPE : "\\\\">
-|   <ESCAPE: "\\" >
-|   <TEXT: (~["$", "#", "\\"])+ >
-}
-
-
 /* -----------------------------------------------------------------------
  *
  *   *_COMMENT Lexical tokens
@@ -939,10 +913,20 @@ MORE :
  *
  * ---------------------------------------------------------------------- */
 
-<DIRECTIVE,REFMOD2,REFINDEX>
+<DEFAULT,REFINDEX,REFMOD2,DIRECTIVE>
 TOKEN:
 {
-    <WHITESPACE : ([" ","\t", "\n", "\r"])+ >
+    <WHITESPACE : ([" ","\t"])+>
+|   <NEWLINE : ("\n" | "\r" | "\r\n") >
+    {
+        if ( debugPrint )
+            System.out.println(" NEWLINE :");
+
+//        stateStackPop();
+
+        if (inSet)
+            inSet = false;
+    }
 }
 
 <DIRECTIVE,REFMOD2,REFINDEX>
@@ -993,25 +977,6 @@ TOKEN:
 |   <FALSE: "false">
 }
 
-<DIRECTIVE>
-TOKEN :
-{
-    <NEWLINE: "\n" | "\r" | "\r\n" >
-    {
-        if ( debugPrint )
-            System.out.println(" NEWLINE :");
-
-        stateStackPop();
-
-        if (inSet)
-            inSet = false;
-
-        if (inDirective)
-            inDirective = false;
-    }
-}
-
-
 <DIRECTIVE,REFMOD2>
 TOKEN :
 {
@@ -1035,10 +1000,8 @@ TOKEN :
 <PRE_DIRECTIVE>
 TOKEN :
 {
-    <END: ( "end" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? )
-          | ("{end}" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? ) >
+    <END: ( "end" | "{end}" )>
     {
-        inDirective = false;
         stateStackPop();
     }
 
@@ -1047,16 +1010,13 @@ TOKEN :
         SwitchTo(DIRECTIVE);
     }
 
-|   <ELSEIF_DIRECTIVE: "elseif" | "{elseif}">
+|   <ELSEIF: "elseif" | "{elseif}">
     {
         SwitchTo(DIRECTIVE);
     }
 
-|   <ELSE_DIRECTIVE:
-          ( "else" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? )
-        | ( "{else}" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? )  >
-     {
-        inDirective = false;
+|   <ELSE: "else" | "{else}">
+    {
         stateStackPop();
     }
 }
@@ -1145,17 +1105,17 @@ TOKEN:
  *  for each state can be different.
  *
  *  $foo.bar( "arg" )
- *  ^   ^   ^
- *  |   |   |
- *  ----------- >  REFERENCE : state initiated by the '$' character.  Continues
- *      |   |       until end of the reference, or the . character.
- *      |------ >  REFMODIFIER : state switched to when the <DOT> is 
encountered.
- *          |       note that this is a switch, not a push. See notes at bottom
- *          |       re stateStack.
- *          |-- >  REFMOD2 : state switch to when the LPAREN is encountered.
- *                  again, this is a switch, not a push.
+ *  ^   ^   ^       ^
+ *  |   |   |       |
+ *  |_________________ >  REFERENCE : state initiated by the '$' character.  
Continues
+ *      |   |       |     until end of the reference, or the . character.
+ *      |_____________ >  REFMODIFIER : state switched to when the <DOT> is 
encountered.
+ *          |       |     note that this is a switch, not a push. See notes at 
bottom.
+ *          |_________ >  REFMOD2 : state switch to when the LPAREN is 
encountered.
+ *                  |     again, this is a switch, not a push.
+ *                  |_ >  REFMOD3 : state only checking for a possible '.' or 
'['  continuation.
  *
- *  During the REFERENCE or REFMODIFIER lex states we will switch to
+ *  During the REFERENCE, REFMODIFIER or REFMOD3 lex states we will switch to
  *  REFINDEX if a bracket is encountered '['.  for example:  $foo[1]
  *  or $foo.bar[1], $foo.bar( "arg" )[1]
  * 
---------------------------------------------------------------------------- */
@@ -1166,7 +1126,12 @@ TOKEN :
     <#ALPHA_CHAR: ["a"-"z", "A"-"Z", "_"] >
 |   <#IDENTIFIER_CHAR: [ "a"-"z", "A"-"Z", "0"-"9", "_" ] >
 |   <IDENTIFIER:  ( <ALPHA_CHAR> ) (<IDENTIFIER_CHAR>)* >
-|   <DOT: "." <ALPHA_CHAR>>
+}
+
+<REFERENCE,REFMODIFIER,REFMOD2,REFMOD3>
+TOKEN:
+{
+   <DOT: "." <ALPHA_CHAR>>
     {
         /*
          * push the alpha char back into the stream so the following identifier
@@ -1189,8 +1154,7 @@ TOKEN :
     }
 }
 
-
-<REFERENCE,REFMODIFIER>
+<REFERENCE,REFMODIFIER,REFMOD3>
 TOKEN :
 {
     <LCURLY: "{">
@@ -1200,7 +1164,7 @@ TOKEN :
     }
 }
 
-<REFERENCE,REFMODIFIER,REFMOD>
+<REFERENCE,REFMODIFIER,REFMOD,REFMOD3>
 SPECIAL_TOKEN :
 {
     <REFERENCE_TERMINATOR: ~[] >
@@ -1229,11 +1193,25 @@ SPECIAL_TOKEN :
             System.out.print("DIRECTIVE_TERM :");
 
         input_stream.backup(1);
-        inDirective = false;
         stateStackPop();
     }
 }
 
+/* TEXT must end with a newline, and contain at least one non-whitespace 
character in the first line,
+   so that the <WHITESPACE> <NEWLINE> sequence is not read as a TEXT (needed 
for space gobbling)
+*/
+TOKEN :
+{
+    <DOUBLE_ESCAPE : "\\\\">
+|   <ESCAPE: "\\" >
+|   <TEXT: (~["$", "#", "\\", "\r", "\n"])* (~["$", "#", "\\", "\r", "\n", " 
", "\t"])+ (~["$", "#", "\\", "\r", "\n"])* <NEWLINE> ((~["$", "#", "\\", "\r", 
"\n"])* <NEWLINE>)* >
+}
+
+TOKEN :
+{
+    <INLINE_TEXT: (~["$", "#", "\\", "\r", "\n"])+ >
+}
+
 /**
  * This method is what starts the whole parsing
  * process. After the parsing is complete and
@@ -1243,9 +1221,12 @@ SPECIAL_TOKEN :
  * which implements the ParserVisitor interface
  * which is generated automatically by JavaCC
  */
-SimpleNode process() : {}
+SimpleNode process() :
+{
+    boolean afterNewline = true;
+}
 {
-   ( Statement() )* <EOF>
+   ( LOOKAHEAD({ getToken(1).kind != EOF }) afterNewline = 
Statement(afterNewline) )* <EOF>
    { return jjtThis; }
 }
 
@@ -1253,17 +1234,23 @@ SimpleNode process() : {}
  * These are the types of statements that
  * are acceptable in Velocity templates.
  */
-void Statement() #void : {}
+boolean Statement(boolean afterNewline) #void :
 {
-    IfStatement()
-|   LOOKAHEAD(2) Reference()
-|   Comment()
-|   Textblock()
-|   SetDirective()
-|   EscapedDirective()
-|   Escape()
-|   Directive()
-|   Text()
+    boolean b = false;
+}
+{
+    LOOKAHEAD( { getToken(1).kind == IF_DIRECTIVE || afterNewline && 
getToken(1).kind == WHITESPACE && getToken(2).kind == IF_DIRECTIVE } ) b = 
IfStatement() { return b ; }
+|   LOOKAHEAD(2) Reference() { return false; }
+|   LOOKAHEAD(2) Comment() { return false; }
+|   Textblock() { return false; }
+|   LOOKAHEAD( { getToken(1).kind == SET_DIRECTIVE || afterNewline && 
getToken(1).kind == WHITESPACE && getToken(2).kind == SET_DIRECTIVE } ) b = 
SetDirective() { return b; }
+|   EscapedDirective() { return false; }
+|   Escape() { return false; }
+|   LOOKAHEAD( { getToken(1).kind == WORD || getToken(1).kind == 
BRACKETED_WORD || afterNewline && getToken(1).kind == WHITESPACE && ( 
getToken(2).kind == WORD || getToken(2).kind == BRACKETED_WORD ) } ) b = 
Directive() { return b; }
+|   b = Text() { return b; }
+|   (<NEWLINE>) #Text { return true; }
+|   (<INLINE_TEXT> ((<TEXT>)? { b = true; }) ) #Text { return b; }
+|   (<WHITESPACE>) #Text { return false; }
 }
 
 /**
@@ -1314,8 +1301,8 @@ void Escape() : {}
          */
         switch(t.next.kind ) {
             case IF_DIRECTIVE :
-            case ELSE_DIRECTIVE :
-            case ELSEIF_DIRECTIVE :
+            case ELSE :
+            case ELSEIF :
             case END :
                 control = true;
                 break;
@@ -1414,7 +1401,7 @@ int DirectiveArg() #void : {}
     /*
      * Need to put this before the floating point expansion
      */
-|   LOOKAHEAD(  <LBRACKET> [<WHITESPACE>] ( Reference() | IntegerLiteral())    
 [<WHITESPACE>] <DOUBLEDOT> ) IntegerRange()
+|   LOOKAHEAD(  <LBRACKET> (<WHITESPACE> | <NEWLINE>)* ( Reference() | 
IntegerLiteral())     (<WHITESPACE> | <NEWLINE>)* <DOUBLEDOT> ) IntegerRange()
     {
         return ParserTreeConstants.JJTINTEGERRANGE;
     }
@@ -1449,10 +1436,11 @@ void DirectiveAssign() : {}
 /**
  *   Supports the Pluggable Directives
  *     #foo( arg+ )
+ * @returns true if ends with a newline
  */
-SimpleNode Directive() :
+boolean Directive() :
 {
-    Token t = null;
+    Token id = null, t = null, u = null;
     int argType;
     int argPos = 0;
     Directive d;
@@ -1460,23 +1448,32 @@ SimpleNode Directive() :
     boolean isVM = false;
     boolean isMacro = false;
     ArrayList argtypes = new ArrayList(4);
+    boolean newlineAtEnd = false, newlineBeforeStatement = false;
+    String blockPrefix = "";
+    ASTBlock block = null;
 }
 {
-
+    [
+      (t = <WHITESPACE>)
+      {
+          jjtThis.setPrefix(t.image);
+          t = null;
+      }
+    ]
     /*
      * note that if we were escaped, that is now handled by
      * EscapedDirective()
      */
-    ((t = <WORD>) | (t = <BRACKETED_WORD>))
+    ((id = <WORD>) | (id = <BRACKETED_WORD>))
     {
         String directiveName;
-        if (t.kind == ParserConstants.BRACKETED_WORD)
+        if (id.kind == ParserConstants.BRACKETED_WORD)
         {
-            directiveName = t.image.substring(2, t.image.length() - 1);
+            directiveName = id.image.substring(2, id.image.length() - 1);
         }
         else
         {
-            directiveName = t.image.substring(1);
+            directiveName = id.image.substring(1);
         }
 
         d = getDirective(directiveName);
@@ -1526,7 +1523,6 @@ SimpleNode Directive() :
          */
 
         token_source.SwitchTo(DIRECTIVE);
-
         argPos = 0;
     }
 
@@ -1538,14 +1534,16 @@ SimpleNode Directive() :
     /*
      *  if this is indeed a token, match the #foo ( arg ) pattern
      */
-    ([<WHITESPACE>] <LPAREN> ( LOOKAHEAD(2) [<WHITESPACE>] [<COMMA> 
[<WHITESPACE>]]
+    ((<WHITESPACE> | <NEWLINE>)* <LPAREN> ( LOOKAHEAD(2) (<WHITESPACE> | 
<NEWLINE>)* [<COMMA> (<WHITESPACE> | <NEWLINE>)*]
        (
            [LOOKAHEAD( { isMacro && isAssignment() })
-              DirectiveAssign() [<WHITESPACE>] <EQUALS> [<WHITESPACE>]
+            DirectiveAssign() (<WHITESPACE> | <NEWLINE>)* <EQUALS> ( 
<WHITESPACE> | <NEWLINE> )*
            {
                argtypes.add(ParserTreeConstants.JJTDIRECTIVEASSIGN);
            }
            ]
+           LOOKAHEAD( { getToken(1).kind != RPAREN } )
+           (
             argType = DirectiveArg()
             {
                 argtypes.add(argType);
@@ -1554,48 +1552,84 @@ SimpleNode Directive() :
                     if (isVM)
                     {
                         throw new MacroParseException("Invalid argument "
-                        + (argPos+1) + " in macro call " + t.image, 
currentTemplate.getName(), t);
+                          + (argPos+1) + " in macro call " + id.image, 
currentTemplate.getName(), id);
                     }
                 }
 
                 argPos++;
             }
+           )
         |
         {
           if (!isMacro)
           {
               // We only allow line comments in macro definitions for now
-              throw new MacroParseException("A Line comment is not allowed in 
" + t.image
-              + " arguments", currentTemplate.getName(), t);
+              throw new MacroParseException("A Line comment is not allowed in 
" + id.image
+                + " arguments", currentTemplate.getName(), id);
           }
         }
 
          <SINGLE_LINE_COMMENT_START> [<SINGLE_LINE_COMMENT>]
       )
-    )* [<WHITESPACE>] <RPAREN>)
+    )* (<WHITESPACE> | <NEWLINE>)* <RPAREN>
+     [
+       LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+       {
+           if (directiveType == Directive.LINE)
+           {
+               jjtThis.setPostfix(t == null ? u.image : t.image + u.image);
+               newlineAtEnd = true;
+           }
+           else
+           {
+               blockPrefix = (t == null ? u.image : t.image + u.image);
+               newlineBeforeStatement = true;
+           }
+           t = u = null;
+       }
+     ]
+     )
     |
     {
         token_source.stateStackPop();
-        token_source.inDirective = false;
     }
     )
     {
         if (d != null)
         {
-            d.checkArgs(argtypes, t, currentTemplate.getName());
+            d.checkArgs(argtypes, id, currentTemplate.getName());
         }
-
         if (directiveType  == Directive.LINE)
         {
-            return jjtThis;
+            return newlineAtEnd;
         }
     }
     /*
      *  and the following block if the PD needs it
      */
-
-    ( Statement() )* #Block
-    <END>
+    ((( 
+        LOOKAHEAD( { getToken(1).kind != END && ( !newlineBeforeStatement || 
getToken(1).kind != WHITESPACE || getToken(2).kind != END ) }) 
newlineBeforeStatement = Statement(newlineBeforeStatement))* 
+        {
+            block = jjtThis;
+            block.setPrefix(blockPrefix);
+        })
+        #Block)
+    [ LOOKAHEAD( 1, { newlineBeforeStatement })
+     (t = <WHITESPACE>)
+      {
+          block.setPostfix(t.image);
+          t = null;
+      }
+    ]
+    (<END>
+     [ LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+     {
+         jjtThis.setPostfix(t == null ? u.image : t.image + u.image);
+         t = u = null;
+         newlineAtEnd = true;
+     }
+     ]
+    )
     {
         /*
          *  VM : if we are processing a #macro directive, we need to
@@ -1615,14 +1649,14 @@ SimpleNode Directive() :
 
         if (d != null)
         {
-            d.checkArgs(argtypes, t, currentTemplate.getName());
+            d.checkArgs(argtypes, id, currentTemplate.getName());
         }
 
         /*
          *  VM : end
          */
 
-        return jjtThis;
+        return newlineAtEnd;
     }
 }
 
@@ -1637,7 +1671,7 @@ void Map() : {}
     (
       LOOKAHEAD(2) Parameter() <COLON> Parameter() (<COMMA> Parameter() 
<COLON> Parameter() )*
       |
-      [ <WHITESPACE> ]
+      ( <WHITESPACE> | <NEWLINE> )*
      )
 
      /** note: need both tokens as they are generated in different states **/
@@ -1657,11 +1691,11 @@ void ObjectArray() : {}
  */
 void IntegerRange() : {}
 {
-    <LBRACKET> [<WHITESPACE>]
+    <LBRACKET> (<WHITESPACE> | <NEWLINE>)*
     ( Reference() | IntegerLiteral())
-    [<WHITESPACE>] <DOUBLEDOT> [<WHITESPACE>]
+    (<WHITESPACE>|<NEWLINE>)* <DOUBLEDOT> (<WHITESPACE>|<NEWLINE>)*
     (Reference() | IntegerLiteral())
-    [<WHITESPACE>] <RBRACKET>
+    (<WHITESPACE>|<NEWLINE>)* <RBRACKET>
 }
 
 
@@ -1670,7 +1704,7 @@ void IntegerRange() : {}
  */
 void IndexParameter() #void: {}
 {
-    [<WHITESPACE>]
+    (<WHITESPACE>|<NEWLINE>)*
     (
         StringLiteral()
         | IntegerLiteral()
@@ -1678,7 +1712,7 @@ void IndexParameter() #void: {}
         | False()
         | Reference()
         )
-    [ <WHITESPACE>]
+    (<WHITESPACE>|<NEWLINE>)*
 }
 
 
@@ -1689,11 +1723,11 @@ void IndexParameter() #void: {}
  */
 void Parameter() #void: {}
 {
-    [<WHITESPACE>]
+    (<WHITESPACE>|<NEWLINE>)*
     (
         StringLiteral()
         | IntegerLiteral()
-        | LOOKAHEAD(  <LBRACKET> [<WHITESPACE>]    ( Reference() | 
IntegerLiteral())     [<WHITESPACE>] <DOUBLEDOT> ) IntegerRange()
+        | LOOKAHEAD(  <LBRACKET> ( <WHITESPACE> | <NEWLINE> )*    ( 
Reference() | IntegerLiteral())     ( <WHITESPACE> | <NEWLINE> )* <DOUBLEDOT> ) 
IntegerRange()
         | Map()
         | ObjectArray()
         | True()
@@ -1768,20 +1802,21 @@ TOKEN :
  * This method is responsible for allowing
  * all non-grammar text to pass through
  * unscathed.
+ * @returns true if last read token was a newline
  */
-void Text() : {}
+boolean Text() : {}
 {
-    <TEXT>
-|   <DOT>
-|   <RPAREN>
-|   <LPAREN>
-|   <INTEGER_LITERAL>
-|   <FLOATING_POINT_LITERAL>
-|   <STRING_LITERAL>
-|   <ESCAPE>
-|   <LCURLY>
-|   <RCURLY>
-|   <EMPTY_INDEX>
+    <TEXT> { return true; }
+  |  <DOT> { return false; }
+  |  <RPAREN> { return false; }
+  |  <LPAREN> { return false; }
+  |  <INTEGER_LITERAL> { return false; }
+  |  <FLOATING_POINT_LITERAL> { return false; }
+  |  <STRING_LITERAL> { return false; }
+  |  <ESCAPE> { return false; }
+  |  <LCURLY> { return false; }
+  |  <RCURLY> { return false; }
+  |  <EMPTY_INDEX> { return false; }
 }
 
 /* -----------------------------------------------------------------------
@@ -1790,26 +1825,130 @@ void Text() : {}
  *
  * ----------------------------------------------------------------------*/
 
-void IfStatement() : {}
+boolean IfStatement() :
 {
-    <IF_DIRECTIVE> [<WHITESPACE>] <LPAREN> Expression() <RPAREN>
-    ( Statement() )* #Block
-    [ LOOKAHEAD(1) ( ElseIfStatement() )+ ]
-    [ LOOKAHEAD(1) ElseStatement() ]
+    Token t = null, u = null;
+    ASTBlock lastBlock = null;
+    boolean afterNewline = false, newlineAtEnd = false;
+}
+{
+    [ ( t = <WHITESPACE> )
+        {
+            jjtThis.setPrefix(t.image);
+            t = null;
+        }
+    ]
+    <IF_DIRECTIVE> ( <WHITESPACE> | <NEWLINE> )* <LPAREN> Expression() <RPAREN>
+    (
+      [
+        LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+        {
+            jjtThis.setPrefix(t == null ? u.image : t.image + u.image);
+            t = u = null;
+            afterNewline = true;
+        }
+      ]
+      ( LOOKAHEAD(
+        {
+            (getToken(1).kind != ELSEIF && getToken(1).kind != ELSE && 
getToken(1).kind != END) && 
+              (!afterNewline || getToken(1).kind != WHITESPACE || 
(getToken(2).kind != ELSEIF && getToken(2).kind != ELSE && getToken(2).kind != 
END))
+        }) 
+        afterNewline = Statement(afterNewline) )*
+        {
+            lastBlock = jjtThis;
+        }
+    ) #Block
+    [ LOOKAHEAD( { getToken(1).kind == ELSEIF || (afterNewline && 
getToken(1).kind == WHITESPACE && getToken(2).kind == ELSEIF) })
+      ( LOOKAHEAD( { getToken(1).kind == ELSEIF || (afterNewline && 
getToken(1).kind == WHITESPACE && getToken(2).kind == ELSEIF) }) ( lastBlock = 
ElseIfStatement(lastBlock) { afterNewline = lastBlock.endsWithNewline; } ))+ ]
+    [ LOOKAHEAD( { getToken(1).kind == ELSE || (afterNewline && 
getToken(1).kind == WHITESPACE && getToken(2).kind == ELSE) } ) lastBlock = 
ElseStatement(lastBlock) { afterNewline = lastBlock.endsWithNewline; } ]
+    [ LOOKAHEAD( 1, { afterNewline } ) ( t = <WHITESPACE> )
+        {
+            lastBlock.setPostfix(t.image);
+            t = null;
+        }
+    ]
     <END>
+    [
+        LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+        {
+             jjtThis.setPostfix(t == null ? u.image : t.image + u.image);
+             newlineAtEnd = true;
+        }
+    ]
+    {
+        return newlineAtEnd;
+    }
 }
 
-void ElseStatement() : {}
+ASTBlock ElseStatement(ASTBlock previousBlock) :
 {
-   <ELSE_DIRECTIVE>
-    ( Statement() )* #Block
+    Token t = null, u = null;
+    ASTBlock block = null;
+    boolean afterNewline = false;
+}
+{
+   [ ( t = <WHITESPACE> )
+     {
+         previousBlock.setPostfix(t.image);
+         t = null;
+     }
+   ]
+   <ELSE>
+   (
+     [
+       LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+       {
+           jjtThis.setPrefix(t == null ? u.image : t.image + u.image);
+           t = u = null;
+           afterNewline = true;
+       }
+     ]
+     ( LOOKAHEAD( { getToken(1).kind != END && (!afterNewline || 
getToken(1).kind != WHITESPACE || getToken(2).kind != END) }) afterNewline = 
Statement(afterNewline) )*
+     {
+         block = jjtThis;
+         block.endsWithNewline = afterNewline;
+     }
+    )
+    #Block
+    {
+        return block;
+    }
 }
 
-void ElseIfStatement() : {}
+ASTBlock ElseIfStatement(ASTBlock previousBlock) :
 {
-    <ELSEIF_DIRECTIVE> [<WHITESPACE>]
-    <LPAREN> Expression() <RPAREN>
-    ( Statement() )* #Block
+    Token t = null, u = null;
+    ASTBlock block = null;
+    boolean afterNewline = false;
+}
+{
+  [ ( t = <WHITESPACE> )
+    {
+        previousBlock.setPostfix(t.image);
+        t = null;
+    }
+  ]
+  <ELSEIF> ( <WHITESPACE> | <NEWLINE> )*
+  <LPAREN> Expression() <RPAREN>
+  (
+    [
+      LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+      {
+          jjtThis.setPrefix(t == null ? u.image : t.image + u.image);
+          t = u = null;
+          afterNewline = true;
+      }
+    ]
+    ( LOOKAHEAD( { (getToken(1).kind != ELSEIF && getToken(1).kind != ELSE && 
getToken(1).kind != END) && (!afterNewline || getToken(1).kind != WHITESPACE || 
(getToken(2).kind != ELSEIF && getToken(2).kind != ELSE && getToken(2).kind != 
END)) }) afterNewline = Statement(afterNewline) )*
+    {
+        block = jjtThis;
+        block.endsWithNewline = afterNewline;
+    }
+  )
+  #Block
+  {
+      return block;
+  }
 }
 
 /**
@@ -1817,9 +1956,19 @@ void ElseIfStatement() : {}
  *   #set( expr )
  *   #set expr
  */
-void SetDirective() : {}
+boolean SetDirective() :
+{
+    Token t = null, u = null;
+    boolean endsWithNewline = false;
+}
 {
-    <SET_DIRECTIVE>([<WHITESPACE>] Reference() [<WHITESPACE>] <EQUALS>  
Expression() <RPAREN>
+    [ ( t = <WHITESPACE> )
+        {
+            jjtThis.setPrefix(t.image);
+            t = null;
+        }
+    ]
+    <SET_DIRECTIVE>(( <WHITESPACE> | <NEWLINE> )* Reference() ( <WHITESPACE> | 
<NEWLINE> )* <EQUALS>  Expression() <RPAREN>
     {
         /*
          * ensure that inSet is false.  Leads to some amusing bugs...
@@ -1827,7 +1976,16 @@ void SetDirective() : {}
 
         token_source.inSet = false;
     }
-    [<NEWLINE>] )
+    [
+        LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+        {
+             jjtThis.setPostfix(t == null ? u.image : t.image + u.image);
+             endsWithNewline = true;
+        }
+    ] )
+    {
+        return endsWithNewline;
+    }
 }
 
 /* -----------------------------------------------------------------------
@@ -1902,18 +2060,21 @@ void MultiplicativeExpression() #void :
 
 void UnaryExpression() #void : {}
 {
-     LOOKAHEAD(2)  [<WHITESPACE>]  <LOGICAL_NOT>  UnaryExpression() #NotNode(1)
-|   PrimaryExpression()
+    ( <WHITESPACE> | <NEWLINE> )*
+    (
+          <LOGICAL_NOT>  UnaryExpression() #NotNode(1)
+      |   PrimaryExpression()
+    )
 }
 
 void PrimaryExpression() #void : {}
 {
-    [<WHITESPACE>]
+    ( <WHITESPACE> | <NEWLINE> )*
     (
      StringLiteral()
     | Reference()
     | IntegerLiteral()
-    | LOOKAHEAD(  <LBRACKET> [<WHITESPACE>]    ( Reference() | 
IntegerLiteral())     [<WHITESPACE>] <DOUBLEDOT> ) IntegerRange()
+    | LOOKAHEAD(  <LBRACKET> ( <WHITESPACE> | <NEWLINE> )*    ( Reference() | 
IntegerLiteral())     ( <WHITESPACE> | <NEWLINE> )* <DOUBLEDOT> ) IntegerRange()
     | FloatingPointLiteral()
     | Map()
     | ObjectArray()
@@ -1921,7 +2082,7 @@ void PrimaryExpression() #void : {}
     | False()
     | <LPAREN>  Expression()  <RPAREN>
      )
-    [<WHITESPACE>]
+    ( <WHITESPACE> | <NEWLINE> )*
 }
 
 

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
 Tue Aug 30 16:18:33 2016
@@ -223,4 +223,10 @@ introspector.restrict.classes = java.lan
 introspector.restrict.classes = java.lang.ThreadGroup
 introspector.restrict.classes = java.lang.ThreadLocal
 
+# ----------------------------------------------------------------------------
+# SPACE GOBBLING
+# ----------------------------------------------------------------------------
+# Possible values: none, bc (aka Backward Compatible), lines, structured
+# ----------------------------------------------------------------------------
 
+space.gobbling = lines

Modified: 
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/InlineScopeVMTestCase.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/InlineScopeVMTestCase.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/InlineScopeVMTestCase.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/InlineScopeVMTestCase.java
 Tue Aug 30 16:18:33 2016
@@ -60,7 +60,7 @@ public class InlineScopeVMTestCase exten
             Velocity.VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL, "true");
 
         Velocity.setProperty(
-            Velocity.VM_PERM_INLINE_LOCAL, "true");
+                Velocity.VM_PERM_INLINE_LOCAL, "true");
 
         Velocity.setProperty(
             Velocity.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
@@ -68,6 +68,8 @@ public class InlineScopeVMTestCase exten
         Velocity.setProperty(
                 Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
 
+        Velocity.setProperty("space.gobbling", "bc");
+
         Velocity.init();
     }
 

Modified: 
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/ScopeTestCase.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/ScopeTestCase.java?rev=1758416&r1=1758415&r2=1758416&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/ScopeTestCase.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/ScopeTestCase.java
 Tue Aug 30 16:18:33 2016
@@ -43,6 +43,7 @@ public class ScopeTestCase extends BaseT
         engine.setProperty("macro.provide.scope.control", "true");
         engine.setProperty("template.provide.scope.control", "true");
         engine.setProperty("vm.provide.scope.control", "true");
+        engine.setProperty("space.gobbling", "bc");
     }
 
     public void testScopeGetLeakIntoInner()


Reply via email to