What?  You would dare to impose your view of configurable whitespace
gobbling upon the rest of us after only a mere decade of discussion?

What will we talk about for the next 10 years? :-)

On Tue, Aug 30, 2016 at 12:18 PM,  <cbris...@apache.org> wrote:
> 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()
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@velocity.apache.org
For additional commands, e-mail: dev-h...@velocity.apache.org

Reply via email to