You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2018/03/19 21:29:47 UTC

[01/12] incubator-freemarker git commit: Version history additions

Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3 0c69b1983 -> edefaa2f6


Version history additions


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

Branch: refs/heads/2.3
Commit: 12616107209fd8d898c25efff8b7d5192a9f3c4e
Parents: 0c77097
Author: ddekany <dd...@apache.org>
Authored: Mon Mar 12 08:04:59 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Mar 12 08:04:59 2018 +0100

----------------------------------------------------------------------
 src/manual/en_US/book.xml | 13 +++++++++++++
 1 file changed, 13 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/12616107/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 4b89a2f..3bd2562 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -27531,6 +27531,14 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>Avoided possible performance bottleneck when executing
+              templates on many threads, caused be that
+              <literal>java.beans.PropertyDescriptor.getReadMethod()</literal>
+              is synchronized (<link
+              xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-80">FREEMARKER-80</link>).</para>
+            </listitem>
+
+            <listitem>
               <para>Added
               <literal>TemplateModelUtils.getKeyValuePairIterator(TemplateHashModelEx)</literal>
               static utility class, which can be used to get a
@@ -27550,6 +27558,11 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               it unwraps into a <literal>LinkedHashMap</literal> instead of
               into a plain <literal>HashMap</literal>.</para>
             </listitem>
+
+            <listitem>
+              <para><literal>freemarker.ext.beans.HashAdapter.size()</literal>
+              was overridden for better performance.</para>
+            </listitem>
           </itemizedlist>
         </section>
       </section>


[04/12] incubator-freemarker git commit: Manual: Noting some common pitfalls when using extended Java decimal format

Posted by dd...@apache.org.
Manual: Noting some common pitfalls when using extended Java decimal format


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

Branch: refs/heads/2.3
Commit: c630a41ab68059ba2e658aaea4281cd512b2f38e
Parents: 54bd25b
Author: ddekany <dd...@apache.org>
Authored: Tue Mar 13 09:48:39 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Tue Mar 13 09:48:39 2018 +0100

----------------------------------------------------------------------
 src/manual/en_US/book.xml | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c630a41a/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 231b607..52f67af 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -15568,6 +15568,13 @@ German people write: 12.345.678,00</programlisting>
               <primary>extended Java decimal format</primary>
             </indexterm>
 
+            <note>
+              <para>You need at least FreeMarker 2.3.24 for these to work.
+              Before that, extended Java decimal format parts are just
+              silently ignored by
+              <literal>java.text.DecimalFormat</literal>.</para>
+            </note>
+
             <para>FreeMarker extends the Java decimal format patterns with
             extra options. These options are name-value pairs, specified after
             two semicolons (<literal>;;</literal>) at the end of the format
@@ -15581,6 +15588,14 @@ Extended decimal format: ${10002.5?string[",000<emphasis>;; roundingMode=halfUp
             <programlisting role="output">Standard decimal format: 10,002
 Extended decimal format: 10<emphasis>_</emphasis>00<emphasis>3</emphasis></programlisting>
 
+            <warning>
+              <para>A very easy mistake to make is just using a single
+              semicolon instead of two. It won't even result in an error, as
+              <literal>java.text.DecimalFormat</literal> thinks you have just
+              specified some weird format for negative numbers. So remember to
+              use two semicolons.</para>
+            </warning>
+
             <para>Above, in the extended decimal format, we have specified
             half-up rounding mode and group separator <literal>"_"</literal>.
             The table of all options follows (note that these are defined by


[05/12] incubator-freemarker git commit: Added new ParserConfiguration setting, interpolation_syntax. It has 3 possible values:

Posted by dd...@apache.org.
Added new ParserConfiguration setting, interpolation_syntax. It has 3 possible values:

- legacy (the default): Interpolations look like ${...} or #{...}. Note that #{...} is deprecated for a long time now.
- dollar: Interpolations look like ${...}. With this syntax, #{...} will be just static text.
- square_bracket: Interpolations look like [=...]. With this syntax ${...} and #{...} will be just static text. So it's useful if you generate output in a format where those (typically ${...}) are already used, such as to generate JSP pages, or to generate FreeMarker templates that use the default syntax.


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

Branch: refs/heads/2.3
Commit: ca1ecf78e7eebdb83c2b147d5fb9aadb1c25f93a
Parents: c630a41
Author: ddekany <dd...@apache.org>
Authored: Fri Mar 16 00:36:31 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Fri Mar 16 00:36:31 2018 +0100

----------------------------------------------------------------------
 src/main/java/freemarker/core/Configurable.java |   7 +-
 .../LegacyConstructorParserConfiguration.java   |   9 +-
 .../freemarker/core/ParserConfiguration.java    |   7 +
 .../java/freemarker/core/StringLiteral.java     |  14 +-
 .../freemarker/core/TemplateConfiguration.java  |  27 ++
 ..._ParserConfigurationWithInheritedFormat.java |   4 +
 .../java/freemarker/template/Configuration.java |  59 +++-
 .../java/freemarker/template/_TemplateAPI.java  |  10 +
 src/main/javacc/FTL.jj                          | 116 +++++---
 src/manual/en_US/book.xml                       | 276 ++++++++++++++-----
 .../core/InterpolationSyntaxTest.java           |  88 ++++++
 .../core/TemplateConfigurationTest.java         |   9 +
 .../freemarker/template/ConfigurationTest.java  |  28 ++
 13 files changed, 542 insertions(+), 112 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index f1a905b..a2b5d29 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -2365,7 +2365,12 @@ public class Configurable {
      *   <li><p>{@code "tag_syntax"}:
      *       See {@link Configuration#setTagSyntax(int)}.
      *       <br>String value: Must be one of
-     *       {@code "auto_detect"}, {@code "angle_bracket"}, and {@code "square_bracket"}. 
+     *       {@code "auto_detect"}, {@code "angle_bracket"}, and {@code "square_bracket"}.
+     *        
+     *   <li><p>{@code "interpolation_syntax"} (since 2.3.28):
+     *       See {@link Configuration#setInterpolationSyntax(int)}.
+     *       <br>String value: Must be one of
+     *       {@code "legacy"}, {@code "dollar"}, and {@code "square_bracket"}. 
      *       
      *   <li><p>{@code "naming_convention"}:
      *       See {@link Configuration#setNamingConvention(int)}.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/LegacyConstructorParserConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/LegacyConstructorParserConfiguration.java b/src/main/java/freemarker/core/LegacyConstructorParserConfiguration.java
index 479af9a..a9a1e8e 100644
--- a/src/main/java/freemarker/core/LegacyConstructorParserConfiguration.java
+++ b/src/main/java/freemarker/core/LegacyConstructorParserConfiguration.java
@@ -28,6 +28,7 @@ import freemarker.template.Version;
 class LegacyConstructorParserConfiguration implements ParserConfiguration {
 
     private final int tagSyntax;
+    private final int interpolationSyntax;
     private final int namingConvention;
     private final boolean whitespaceStripping;
     private final boolean strictSyntaxMode;
@@ -38,11 +39,13 @@ class LegacyConstructorParserConfiguration implements ParserConfiguration {
     private Integer tabSize;
     private final Version incompatibleImprovements;
 
-    public LegacyConstructorParserConfiguration(boolean strictSyntaxMode, boolean whitespaceStripping, int tagSyntax,
+    LegacyConstructorParserConfiguration(boolean strictSyntaxMode, boolean whitespaceStripping,
+            int tagSyntax, int interpolationSyntax,
             int namingConvention, Integer autoEscaping, OutputFormat outputFormat,
             Boolean recognizeStandardFileExtensions, Integer tabSize,
             Version incompatibleImprovements, ArithmeticEngine arithmeticEngine) {
         this.tagSyntax = tagSyntax;
+        this.interpolationSyntax = interpolationSyntax;
         this.namingConvention = namingConvention;
         this.whitespaceStripping = whitespaceStripping;
         this.strictSyntaxMode = strictSyntaxMode;
@@ -57,6 +60,10 @@ class LegacyConstructorParserConfiguration implements ParserConfiguration {
     public int getTagSyntax() {
         return tagSyntax;
     }
+    
+    public int getInterpolationSyntax() {
+        return interpolationSyntax;
+    }
 
     public int getNamingConvention() {
         return namingConvention;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/ParserConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ParserConfiguration.java b/src/main/java/freemarker/core/ParserConfiguration.java
index 0f097c9..8c125da 100644
--- a/src/main/java/freemarker/core/ParserConfiguration.java
+++ b/src/main/java/freemarker/core/ParserConfiguration.java
@@ -36,6 +36,13 @@ public interface ParserConfiguration {
     int getTagSyntax();
 
     /**
+     * See {@link Configuration#getInterpolationSyntax()}.
+     *
+     * @since 2.3.28
+     */
+    int getInterpolationSyntax();
+    
+    /**
      * See {@link Configuration#getNamingConvention()}.
      */
     int getNamingConvention();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/StringLiteral.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/StringLiteral.java b/src/main/java/freemarker/core/StringLiteral.java
index 7c25623..6d53c17 100644
--- a/src/main/java/freemarker/core/StringLiteral.java
+++ b/src/main/java/freemarker/core/StringLiteral.java
@@ -22,6 +22,7 @@ package freemarker.core;
 import java.io.StringReader;
 import java.util.List;
 
+import freemarker.template.Configuration;
 import freemarker.template.SimpleScalar;
 import freemarker.template.Template;
 import freemarker.template.TemplateException;
@@ -47,10 +48,15 @@ final class StringLiteral extends Expression implements TemplateScalarModel {
     void parseValue(FMParser parentParser, OutputFormat outputFormat) throws ParseException {
         // The way this works is incorrect (the literal should be parsed without un-escaping),
         // but we can't fix this backward compatibly.
-        if (value.length() > 3 && (value.indexOf("${") >= 0 || value.indexOf("#{") >= 0)) {
-            Template parentTemplate = getTemplate();
-            ParserConfiguration pcfg = parentTemplate.getParserConfiguration();
-
+        Template parentTemplate = getTemplate();
+        ParserConfiguration pcfg = parentTemplate.getParserConfiguration();
+        int intSyn = pcfg.getInterpolationSyntax();
+        if (value.length() > 3 && (
+                    (intSyn == Configuration.LEGACY_INTERPOLATION_SYNTAX
+                        || intSyn == Configuration.DOLLAR_INTERPOLATION_SYNTAX) 
+                        && (value.indexOf("${") >= 0
+                    || intSyn == Configuration.LEGACY_INTERPOLATION_SYNTAX && value.indexOf("#{") >= 0)
+                    || intSyn == Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX && value.indexOf("[=") >= 0)) {
             try {
                 SimpleCharStream simpleCharacterStream = new SimpleCharStream(
                         new StringReader(value),

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/TemplateConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateConfiguration.java b/src/main/java/freemarker/core/TemplateConfiguration.java
index 0835239..ad7b1f8 100644
--- a/src/main/java/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/freemarker/core/TemplateConfiguration.java
@@ -78,6 +78,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC
 
     private boolean parentConfigurationSet;
     private Integer tagSyntax;
+    private Integer interpolationSyntax;
     private Integer namingConvention;
     private Boolean whitespaceStripping;
     private Boolean strictSyntaxMode;
@@ -234,6 +235,9 @@ public final class TemplateConfiguration extends Configurable implements ParserC
         if (tc.isTagSyntaxSet()) {
             setTagSyntax(tc.getTagSyntax());
         }
+        if (tc.isInterpolationSyntaxSet()) {
+            setInterpolationSyntax(tc.getInterpolationSyntax());
+        }
         if (tc.isTemplateExceptionHandlerSet()) {
             setTemplateExceptionHandler(tc.getTemplateExceptionHandler());
         }
@@ -413,6 +417,29 @@ public final class TemplateConfiguration extends Configurable implements ParserC
     }
 
     /**
+     * See {@link Configuration#setInterpolationSyntax(int)}.
+     */
+    public void setInterpolationSyntax(int interpolationSyntax) {
+        _TemplateAPI.valideInterpolationSyntaxValue(interpolationSyntax);
+        this.interpolationSyntax = Integer.valueOf(interpolationSyntax);
+    }
+    
+    /**
+     * The getter pair of {@link #setInterpolationSyntax(int)}.
+     */
+    public int getInterpolationSyntax() {
+        return interpolationSyntax != null ? interpolationSyntax.intValue()
+                : getNonNullParentConfiguration().getInterpolationSyntax();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     */
+    public boolean isInterpolationSyntaxSet() {
+        return interpolationSyntax != null;
+    }
+    
+    /**
      * See {@link Configuration#setNamingConvention(int)}.
      */
     public void setNamingConvention(int namingConvention) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/_ParserConfigurationWithInheritedFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/_ParserConfigurationWithInheritedFormat.java b/src/main/java/freemarker/core/_ParserConfigurationWithInheritedFormat.java
index 8524d62..72152ce 100644
--- a/src/main/java/freemarker/core/_ParserConfigurationWithInheritedFormat.java
+++ b/src/main/java/freemarker/core/_ParserConfigurationWithInheritedFormat.java
@@ -44,6 +44,10 @@ public final class _ParserConfigurationWithInheritedFormat implements ParserConf
         return wrappedPCfg.getTagSyntax();
     }
 
+    public int getInterpolationSyntax() {
+        return wrappedPCfg.getInterpolationSyntax();
+    }
+
     public boolean getStrictSyntaxMode() {
         return wrappedPCfg.getStrictSyntaxMode();
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/template/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 5f35435..34b055e 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -252,6 +252,13 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     public static final String TAG_SYNTAX_KEY_CAMEL_CASE = "tagSyntax";
     /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
     public static final String TAG_SYNTAX_KEY = TAG_SYNTAX_KEY_SNAKE_CASE;
+
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.28 */
+    public static final String INTERPOLATION_SYNTAX_KEY_SNAKE_CASE = "interpolation_syntax";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.28 */
+    public static final String INTERPOLATION_SYNTAX_KEY_CAMEL_CASE = "interpolationSyntax";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String INTERPOLATION_SYNTAX_KEY = INTERPOLATION_SYNTAX_KEY_SNAKE_CASE;
     
     /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
     public static final String NAMING_CONVENTION_KEY_SNAKE_CASE = "naming_convention";
@@ -315,6 +322,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         CACHE_STORAGE_KEY_SNAKE_CASE,
         DEFAULT_ENCODING_KEY_SNAKE_CASE,
         INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE,
+        INTERPOLATION_SYNTAX_KEY_SNAKE_CASE,
         LOCALIZED_LOOKUP_KEY_SNAKE_CASE,
         NAMING_CONVENTION_KEY_SNAKE_CASE,
         OUTPUT_FORMAT_KEY_SNAKE_CASE,
@@ -337,6 +345,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         CACHE_STORAGE_KEY_CAMEL_CASE,
         DEFAULT_ENCODING_KEY_CAMEL_CASE,
         INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE,
+        INTERPOLATION_SYNTAX_KEY_CAMEL_CASE,
         LOCALIZED_LOOKUP_KEY_CAMEL_CASE,
         NAMING_CONVENTION_KEY_CAMEL_CASE,
         OUTPUT_FORMAT_KEY_CAMEL_CASE,
@@ -371,6 +380,13 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     public static final int ANGLE_BRACKET_TAG_SYNTAX = 1;
     public static final int SQUARE_BRACKET_TAG_SYNTAX = 2;
 
+    /** <code>${expression}</code> and the deprecated <code>#{expression; numFormat}</code> @since 2.3.28 */
+    public static final int LEGACY_INTERPOLATION_SYNTAX = 0;
+    /** <code>${expression}</code> only (not <code>#{expression; numFormat}</code>) @since 2.3.28 */
+    public static final int DOLLAR_INTERPOLATION_SYNTAX = 1;
+    /** <code>[=expression]</code> @since 2.3.28 */
+    public static final int SQUARE_BRACKET_INTERPOLATION_SYNTAX = 2;
+    
     public static final int AUTO_DETECT_NAMING_CONVENTION = 10;
     public static final int LEGACY_NAMING_CONVENTION = 11;
     public static final int CAMEL_CASE_NAMING_CONVENTION = 12;
@@ -494,6 +510,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     private Map<String, ? extends OutputFormat> registeredCustomOutputFormats = Collections.emptyMap(); 
     private Version incompatibleImprovements;
     private int tagSyntax = ANGLE_BRACKET_TAG_SYNTAX;
+    private int interpolationSyntax = LEGACY_INTERPOLATION_SYNTAX;
     private int namingConvention = AUTO_DETECT_NAMING_CONVENTION;
     private int tabSize = 8;  // Default from JavaCC 3.x
     private boolean preventStrippings;
@@ -2356,9 +2373,8 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     }
 
     /**
-     * Determines the syntax of the template files (angle bracket VS square bracket)
-     * that has no {@code #ftl} in it. The {@code tagSyntax}
-     * parameter must be one of:
+     * Determines the tag syntax (like {@code <#if x>} VS {@code [#if x]}) of the template files 
+     * that has no {@code #ftl} header to decide that. The {@code tagSyntax} parameter must be one of:
      * <ul>
      *   <li>{@link Configuration#AUTO_DETECT_TAG_SYNTAX}:
      *     use the syntax of the first FreeMarker tag (can be anything, like <tt>#list</tt>,
@@ -2377,6 +2393,8 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      * <p>This setting is ignored for the templates that have {@code ftl} directive in
      * it. For those templates the syntax used for the {@code ftl} directive determines
      * the syntax.
+     * 
+     * @see #setInterpolationSyntax(int)
      */
     public void setTagSyntax(int tagSyntax) {
         _TemplateAPI.valideTagSyntaxValue(tagSyntax);
@@ -2391,6 +2409,30 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     }
 
     /**
+     * Determines the interpolation syntax (like <code>${x}</code> VS <code>[=x]</code>) of the template files in which
+     * there's no {@code #ftl} hader with {@code interpolation_syntax} parameter. The
+     * {@code interpolationSyntax} parameter must be one of {@link Configuration#LEGACY_INTERPOLATION_SYNTAX},
+     * {@link Configuration#DOLLAR_INTERPOLATION_SYNTAX}, and {@link Configuration#SQUARE_BRACKET_INTERPOLATION_SYNTAX}.
+     * 
+     * @see #setTagSyntax(int)
+     * 
+     * @since 2.3.28
+     */
+    public void setInterpolationSyntax(int interpolationSyntax) {
+        _TemplateAPI.valideInterpolationSyntaxValue(interpolationSyntax);
+        this.interpolationSyntax = interpolationSyntax;
+    }
+    
+    /**
+     * The getter pair of {@link #setInterpolationSyntax(int)}.
+     * 
+     * @since 2.3.28
+     */
+    public int getInterpolationSyntax() {
+        return interpolationSyntax;
+    }
+    
+    /**
      * Sets the naming convention used for the identifiers that are part of the template language. The available naming
      * conventions are legacy (directive (tag) names are all-lower-case {@code likethis}, others are snake case
      * {@code like_this}), and camel case ({@code likeThis}). The default is auto-detect, which detects the naming
@@ -3220,6 +3262,17 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
                 } else {
                     throw invalidSettingValueException(name, value);
                 }
+            } else if (INTERPOLATION_SYNTAX_KEY_SNAKE_CASE.equals(name)
+                    || INTERPOLATION_SYNTAX_KEY_CAMEL_CASE.equals(name)) {
+                if ("legacy".equals(value)) {
+                    setInterpolationSyntax(LEGACY_INTERPOLATION_SYNTAX);
+                } else if ("dollar".equals(value)) {
+                    setInterpolationSyntax(DOLLAR_INTERPOLATION_SYNTAX);
+                } else if ("square_bracket".equals(value) || "squareBracket".equals(value)) {
+                    setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX);
+                } else {
+                    throw invalidSettingValueException(name, value);
+                }
             } else if (NAMING_CONVENTION_KEY_SNAKE_CASE.equals(name) || NAMING_CONVENTION_KEY_CAMEL_CASE.equals(name)) {
                 if ("auto_detect".equals(value) || "autoDetect".equals(value)) {
                     setNamingConvention(AUTO_DETECT_NAMING_CONVENTION);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/template/_TemplateAPI.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/_TemplateAPI.java b/src/main/java/freemarker/template/_TemplateAPI.java
index 9a6ee58..9b2c22d 100644
--- a/src/main/java/freemarker/template/_TemplateAPI.java
+++ b/src/main/java/freemarker/template/_TemplateAPI.java
@@ -158,6 +158,16 @@ public class _TemplateAPI {
                     + "or Configuration.SQUARE_BRACKET_SYNTAX");
         }
     }
+
+    public static void valideInterpolationSyntaxValue(int interpolationSyntax) {
+        if (interpolationSyntax != Configuration.LEGACY_INTERPOLATION_SYNTAX
+            && interpolationSyntax != Configuration.DOLLAR_INTERPOLATION_SYNTAX
+            && interpolationSyntax != Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX) {
+            throw new IllegalArgumentException("\"interpolation_syntax\" can only be set to one of these: "
+                    + "Configuration.LEGACY_INTERPOLATION_SYNTAX, Configuration.DOLLAR_INTERPOLATION_SYNTAX, "
+                    + "or Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX");
+        }
+    }
     
     public static Expression getBlamedExpression(TemplateException e) {
         return e.getBlamedExpression();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index 3e87032..f1fa595 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -33,6 +33,7 @@ import freemarker.template.*;
 import freemarker.template.utility.*;
 import java.io.*;
 import java.util.*;
+import static freemarker.template.Configuration.*;
 
 /**
  * This class is generated by JavaCC from a grammar file.
@@ -165,7 +166,7 @@ public class FMParser {
         this(template, reader,
                 new LegacyConstructorParserConfiguration(
                         strictSyntaxMode, whitespaceStripping,
-                        tagSyntax, namingConvention,
+                        tagSyntax, LEGACY_INTERPOLATION_SYNTAX, namingConvention,
                         template != null ? template.getParserConfiguration().getAutoEscapingPolicy()
                                 : Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY,
                         template != null ? template.getParserConfiguration().getOutputFormat()
@@ -256,6 +257,8 @@ public class FMParser {
             throw new IllegalArgumentException("Illegal argument for tagSyntax: " + tagSyntax);
         }
 
+        token_source.interpolationSyntax = pCfg.getInterpolationSyntax();
+
         int namingConvention = pCfg.getNamingConvention();
         switch (namingConvention) {
         case Configuration.AUTO_DETECT_NAMING_CONVENTION:
@@ -612,12 +615,12 @@ TOKEN_MGR_DECLS:
      */
     String noparseTag;
 
+    private FMParser parser;
+    private int postInterpolationLexState = -1;
     /**
      * Keeps track of how deeply nested we have the hash literals. This is necessary since we need to be able to
      * distinguish the } used to close a hash literal and the one used to close a ${
      */
-    private FMParser parser;
-    private int postInterpolationLexState = -1;
     private int hashLiteralNesting;
     private int parenthesisNesting;
     private int bracketNesting;
@@ -627,6 +630,7 @@ TOKEN_MGR_DECLS:
             autodetectTagSyntax,
             directiveSyntaxEstablished,
             inInvocation;
+    int interpolationSyntax;
     int initialNamingConvention;
     int namingConvention;
     Token namingConventionEstabilisher;
@@ -793,25 +797,25 @@ TOKEN_MGR_DECLS:
         }
     }
 
-    private void closeBracket(Token tok) {
-        if (bracketNesting > 0) {
-            --bracketNesting;
-        } else {
-            tok.kind = DIRECTIVE_END;
-            if (inFTLHeader) {
-                eatNewline();
-                inFTLHeader = false;
-            }
-            SwitchTo(DEFAULT);
-        }
-    }
-    
     private void startInterpolation(Token tok) {
+        if ( 
+                interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX
+                    && tok.kind == SQUARE_BRACKET_INTERPOLATION_OPENING
+                || interpolationSyntax == DOLLAR_INTERPOLATION_SYNTAX
+                    && tok.kind != DOLLAR_INTERPOLATION_OPENING
+                || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX
+                    && tok.kind != SQUARE_BRACKET_INTERPOLATION_OPENING) {
+            tok.kind = STATIC_TEXT_NON_WS;
+            return;
+        }
+        
         if (postInterpolationLexState != -1) {
+            // This certainly never occurs, as starting an interpolation in expression mode fails earlier.
             char c = tok.image.charAt(0);
             throw new TokenMgrError(
-                    "You can't start an interpolation (" + c + "{...}) here "
-                    + "as you are inside another interpolation.)",
+                    "You can't start an interpolation (" + tok.image + "..."
+                    + (interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX ? "]" : "}")
+                    + ") here as you are inside another interpolation.)",
                     TokenMgrError.LEXICAL_ERROR,
                     tok.beginLine, tok.beginColumn,
                     tok.endLine, tok.endColumn);
@@ -1156,6 +1160,8 @@ TOKEN:
     <DOLLAR_INTERPOLATION_OPENING : "${"> { startInterpolation(matchedToken); }
     |
     <HASH_INTERPOLATION_OPENING : "#{"> { startInterpolation(matchedToken); }
+    |
+    <SQUARE_BRACKET_INTERPOLATION_OPENING : "[="> { startInterpolation(matchedToken); }
 }
 
 <FM_EXPRESSION, IN_PAREN, NAMED_PARAMETER_EXPRESSION> SKIP :
@@ -1291,7 +1297,20 @@ TOKEN:
     |
     <CLOSE_BRACKET : "]">
     {
-        closeBracket(matchedToken);
+        if (bracketNesting > 0) {
+            --bracketNesting;
+        } else if (interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX
+                && (postInterpolationLexState != -1 || !squBracTagSyntax)) {
+            endInterpolation(matchedToken);
+        } else {
+            // Glitch where you can close any tag with `]`, like `<#if x]`. We keep it for backward compatibility.
+            matchedToken.kind = DIRECTIVE_END;
+            if (inFTLHeader) {
+                eatNewline();
+                inFTLHeader = false;
+            }
+            SwitchTo(DEFAULT);
+        }
     }
     |
     <OPEN_PAREN : "(">
@@ -1343,15 +1362,16 @@ TOKEN:
         }
     }
     |
-    <OPEN_MISPLACED_INTERPOLATION : "${" | "#{">
+    <OPEN_MISPLACED_INTERPOLATION : "${" | "#{" | "[=">
     {
         if ("".length() == 0) {  // prevents unreachabe "break" compilation error in generated Java
-            char c = matchedToken.image.charAt(0);
+            char closerC = matchedToken.image.charAt(0) != '[' ? '}' : ']';
             throw new TokenMgrError(
-                    "You can't use \"" + c + "{\" here as you are already in FreeMarker-expression-mode. Thus, instead "
-                    + "of " + c + "{myExpression}, just write myExpression. "
-                    + "(" + c + "{...} is only needed where otherwise static text is expected, i.e, outside " 
-                    + "FreeMarker tags and ${...}-s.)",
+                    "You can't use " + matchedToken.image + "..." + closerC + " (an interpolation) here as you are "
+                    + "already in FreeMarker-expression-mode. Thus, instead of " + matchedToken.image + "myExpression"
+                    + closerC + ", just write myExpression. (" + matchedToken.image + "..." + closerC + " is only "
+                    + "used where otherwise static text is expected, i.e., outside FreeMarker tags and "
+                    + "interpolations, or inside string literals.)",
                     TokenMgrError.LEXICAL_ERROR,
                     matchedToken.beginLine, matchedToken.beginColumn,
                     matchedToken.endLine, matchedToken.endColumn);
@@ -2306,7 +2326,16 @@ StringLiteral StringLiteral(boolean interpolate) :
         result.setLocation(template, t, t);
         if (interpolate && !raw) {
             // TODO: This logic is broken. It can't handle literals that contains both ${...} and $\{...}. 
-            if (t.image.indexOf("${") >= 0 || t.image.indexOf("#{") >= 0) result.parseValue(this, outputFormat);
+            int interpolationSyntax = pCfg.getInterpolationSyntax();
+            if ((interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX
+                    || interpolationSyntax == DOLLAR_INTERPOLATION_SYNTAX)
+	                    && t.image.indexOf("${") >= 0
+	                || interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX
+	                    && t.image.indexOf("#{") >= 0
+	                || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX
+	                    && t.image.indexOf("[=") >= 0) {
+                result.parseValue(this, outputFormat);
+            }
         }
         return result;
     }
@@ -2369,8 +2398,7 @@ HashLiteral HashLiteral() :
 }
 
 /**
- * A production representing the ${...}
- * that outputs a variable.
+ * A production representing the ${...} or [=...] that outputs a variable.
  */
 DollarVariable StringOutput() :
 {
@@ -2378,13 +2406,29 @@ DollarVariable StringOutput() :
     Token begin, end;
 }
 {
-    begin = <DOLLAR_INTERPOLATION_OPENING>
-    exp = Expression()
-    {
-        notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
-        notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
-    }
-    end = <CLOSING_CURLY_BRACKET>
+    (
+	    (
+	        begin = <DOLLAR_INTERPOLATION_OPENING>
+	        exp = Expression()
+	            {
+	                notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
+	                notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
+	            }
+	
+	        end = <CLOSING_CURLY_BRACKET>
+	    )
+	    |
+	    (
+	        begin = <SQUARE_BRACKET_INTERPOLATION_OPENING>
+	        exp = Expression()
+	            {
+	                notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
+	                notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
+	            }
+	
+	        end = <CLOSE_BRACKET>
+	    )
+    )
     {
         DollarVariable result = new DollarVariable(
                 exp, escapedExpression(exp),
@@ -4449,7 +4493,7 @@ List<Object> StaticTextAndInterpolations() :
 	    }
 	    |
 	    (
-	        LOOKAHEAD(<DOLLAR_INTERPOLATION_OPENING>)
+	        LOOKAHEAD(<DOLLAR_INTERPOLATION_OPENING>|<SQUARE_BRACKET_INTERPOLATION_OPENING>)
 		    (
 		        interpolation = StringOutput()
 	        )

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 52f67af..cbda1a8 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -4199,6 +4199,13 @@ ${("green " + "mouse")?upper_case}  &lt;#-- GREEN MOUSE --&gt;
           <literal><replaceable>expression</replaceable></literal> can be all
           kind of expression (e.g. <literal>${100 + x}</literal>).</para>
 
+          <note>
+            <para>Actually, FreeMarker can be configured to use
+            <literal>[=<replaceable>expression</replaceable>]</literal> syntax
+            instead. <link linkend="dgui_misc_alternativesyntax">See more
+            about alternative syntaxes...</link></para>
+          </note>
+
           <para>The interpolation is used to insert the value of the
           <literal><replaceable>expression</replaceable></literal> converted
           to text (to string). Interpolations can be used only on two places:
@@ -6221,46 +6228,57 @@ That's all.</programlisting>
           <primary>square bracket syntax</primary>
         </indexterm>
 
-        <note>
-          <para>This feature exists since FreeMarker 2.3.4.</para>
-        </note>
+        <para>Both FreeMarker tags (such as <literal>&lt;#if x&gt;</literal>)
+        and FreeMarker interpolations (such as <literal>${x}</literal>) can be
+        set <emphasis>independently</emphasis> to use so called square bracket
+        syntax (<literal>[#if x]</literal>, and <literal>[=x]</literal>,
+        respectively). This is mostly useful to prevent clashes with the
+        generated content (such as when generating JSP files), or to work
+        around cases where some other tool is confused by the default
+        symbols.</para>
 
-        <para>FreeMarker supports an alternative syntax, where
-        <literal>[</literal> and <literal>]</literal> is used instead of
-        <literal>&lt;</literal> and <literal>&gt;</literal> in FreeMarker
-        directives and comments, for example:</para>
+        <section xml:id="dgui_misc_alternativesyntax_tag">
+          <title>Square bracket tag syntax</title>
 
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>Calling predefined directive: <literal>[#list animals as
-            animal]<replaceable>...</replaceable>[/#list]</literal></para>
-          </listitem>
+          <note>
+            <para>This feature exists since FreeMarker 2.3.4.</para>
+          </note>
 
-          <listitem>
-            <para>Calling user-defined directive: <literal>[@myMacro
-            /]</literal></para>
-          </listitem>
+          <para>FreeMarker supports an alternative tag syntax, where
+          <literal>[</literal> and <literal>]</literal> is used instead of
+          <literal>&lt;</literal> and <literal>&gt;</literal> in FreeMarker
+          directives and comments, for example:</para>
 
-          <listitem>
-            <para>Comment: <literal>[#-- the comment --]</literal></para>
-          </listitem>
-        </itemizedlist>
+          <itemizedlist spacing="compact">
+            <listitem>
+              <para>Calling predefined directive: <literal>[#list animals as
+              animal]<replaceable>...</replaceable>[/#list]</literal></para>
+            </listitem>
+
+            <listitem>
+              <para>Calling user-defined directive: <literal>[@myMacro
+              /]</literal></para>
+            </listitem>
+
+            <listitem>
+              <para>Comment: <literal>[#-- the comment --]</literal></para>
+            </listitem>
+          </itemizedlist>
+
+          <para>To use the alternative tag syntax instead of the default one,
+          the programmers should configure FreeMarker so <phrase
+          role="forProgrammers">(see
+          <literal>Configuraton.setTagSyntax</literal>, or the
+          <literal>tag_syntax</literal> setting)</phrase>. However, the tag
+          syntax can also be enforced in the template, with the <link
+          linkend="ref_directive_ftl"><literal>ftl</literal> directive</link>
+          (see later).</para>
 
-        <para>To use the alternative syntax instead of the default one, start
-        the template with the <link
-        linkend="ref_directive_ftl"><literal>ftl</literal> directive</link>
-        using the alternative syntax. If you don't know what is the
-        <literal>ftl</literal> directive, just start the template with
-        <literal>[#ftl]</literal>, and remember that it should be the very
-        first thing in the file (except that <link
-        linkend="gloss.whiteSpace">white-space</link> can precede it). For
-        example, this is how the last example of the <link
-        linkend="dgui_quickstart_template">Getting Started</link> looks with
-        the alternative syntax (assuming it's a complete template, not just a
-        fragment):</para>
-
-        <programlisting role="template"><emphasis>[#ftl]</emphasis>
-&lt;p&gt;We have these animals:
+          <para>For example, this is how the last example of the <link
+          linkend="dgui_quickstart_template">Getting Started</link> looks with
+          the alternative syntax:</para>
+
+          <programlisting role="template">&lt;p&gt;We have these animals:
 &lt;table border=1&gt;
   &lt;tr&gt;&lt;th&gt;Name&lt;th&gt;Price
   <emphasis>[#list animals as animal]</emphasis>
@@ -6273,33 +6291,79 @@ That's all.</programlisting>
   <emphasis>[/#list]</emphasis>
 &lt;/table&gt;</programlisting>
 
-        <para>The alternative (square bracket) and the default (angle bracket)
-        syntax are mutually exclusive within a template. That is, either the
-        whole template uses alternative syntax, or the whole template uses the
-        default syntax. If the template uses alternative syntax, things like
-        <literal>&lt;#if <replaceable>...</replaceable>&gt;</literal> are
-        count as static text, not as FTL tags. Similarly, if the template uses
-        the default syntax, things like <literal>[#if
-        <replaceable>...</replaceable>]</literal> count as static text, not as
-        FTL tags.</para>
-
-        <para>If you start the file with <literal>[#ftl
-        <replaceable>...</replaceable>]</literal> (where the
-        <literal><replaceable>...</replaceable></literal> stands for the
-        optional parameters; of course <literal>[#ftl]</literal> works too)
-        the file will surely use the alternative (square bracket) syntax. If
-        you start the file with <literal>&lt;#ftl
-        <replaceable>...</replaceable>&gt;</literal> the file will surely use
-        the normal (angle bracket) syntax. If there is no
-        <literal>ftl</literal> directive in the file, then the programmer
-        decides what the syntax will be by configuring FreeMarker <phrase
-        role="forProgrammers">(programmers see
-        <literal>Configuration.setTagSyntax(int)</literal> in the API
-        javadocs)</phrase>. Most probably the programmers use the factory
-        default however. The factory default in 2.3.x is using the normal
-        syntax. The factory default in 2.4.x will be auto-detection, which
-        means that the first FreeMarker tag determines the syntax (it can be
-        anything, not just <literal>ftl</literal>).</para>
+          <para>The alternative (square bracket) and the default (angle
+          bracket) syntax are mutually exclusive within a template. That is,
+          either the whole template uses square bracket tag syntax, or the
+          whole template uses the angle bracket tag syntax. If the template
+          uses square bracket tag syntax, then things like <literal>&lt;#if
+          <replaceable>...</replaceable>&gt;</literal> are count as static
+          text, not as FTL tags. Similarly, if the template uses the angle
+          brackets tag syntax, things like <literal>[#if
+          <replaceable>...</replaceable>]</literal> count as static text, not
+          as FTL tags.</para>
+
+          <para>If you start the file with <literal>[#ftl
+          <replaceable>...</replaceable>]</literal> (where the
+          <literal><replaceable>...</replaceable></literal> stands for the
+          optional parameters; of course <literal>[#ftl]</literal> works too)
+          the file will surely use the square bracket syntax. If you start the
+          file with <literal>&lt;#ftl
+          <replaceable>...</replaceable>&gt;</literal> the file will surely
+          use the normal (angle bracket) syntax. If there is no
+          <literal>ftl</literal> directive in the file, then the programmer
+          decides what the syntax will be by configuring FreeMarker <phrase
+          role="forProgrammers">(programmers see
+          <literal>Configuration.setTagSyntax(int)</literal> in the API
+          javadocs)</phrase>. Most probably the programmers use the factory
+          default however. The factory default in 2.3.x is using the normal
+          syntax. The factory default in 2.4.x will be auto-detection, which
+          means that the first FreeMarker tag determines the syntax (it can be
+          anything, not just <literal>ftl</literal>).</para>
+        </section>
+
+        <section xml:id="dgui_misc_alternativesyntax_interpolation">
+          <title>Square bracket interpolation syntax</title>
+
+          <note>
+            <para>This feature exists since FreeMarker 2.3.28</para>
+          </note>
+
+          <para>In this case instead of
+          <literal>${<replaceable>expression</replaceable>}</literal> (and
+          <literal>#{<replaceable>expression</replaceable>}</literal>) you
+          write <literal>[=<replaceable>expression</replaceable>]</literal>.
+          This syntax is usually activated by the programmers from the
+          configuration <phrase role="forProgrammers">(see
+          <literal>Configuration.setInterpolationSyntax</literal> in the Java
+          API)</phrase>. It can be used both together with, and without the
+          square bracket <emphasis>tag</emphasis> syntax (see that in the
+          <link linkend="dgui_misc_alternativesyntax_tag">previous
+          section</link>), although it's more likely to be used together with
+          it. Assuming both interpolation and tag syntax was set to square
+          bracket, the earlier example template will look like this:</para>
+
+          <programlisting role="template">&lt;p&gt;We have these animals:
+&lt;table border=1&gt;
+  &lt;tr&gt;&lt;th&gt;Name&lt;th&gt;Price
+  [#list animals as animal]
+  &lt;tr&gt;
+    &lt;td&gt;
+      [#if animal.size == "large"]&lt;b&gt;[/#if]
+      <emphasis>[=animal.name]</emphasis>
+      [#if animal.size == "large"]&lt;/b&gt;[/#if]
+    &lt;td&gt;<emphasis>[=animal.price]</emphasis> Euros
+  [/#list]
+&lt;/table&gt;</programlisting>
+
+          <para>When square bracket interpolation syntax is used,
+          <literal>${<replaceable>expression</replaceable>}</literal> and
+          <literal>#{<replaceable>expression</replaceable>}</literal> in the
+          template will be just static text, which is printed as is. This is
+          mostly useful if you generate output that should contain those
+          (especially
+          <literal>${<replaceable>expression</replaceable>}</literal> is
+          frequent), such as when generating JSP files.</para>
+        </section>
       </section>
     </chapter>
   </part>
@@ -24325,6 +24389,17 @@ some test
         linkend="ref_builtin_c"><literal>c</literal> built-in</link> (like
         <literal><replaceable>number</replaceable>?c</literal>).</para>
 
+        <para>While by default
+        <literal>#{<replaceable>...</replaceable>}</literal> is interpreted,
+        that can be disabled by setting the
+        <literal>interpolation_syntax</literal> configuration setting
+        (<literal>Configuration.setInterpolationSyntax</literal> in the Java
+        API) to <literal>dollar</literal>. Then
+        <literal>#{<replaceable>...</replaceable>}</literal> will be just
+        static text, and only
+        <literal>${<replaceable>...</replaceable>}</literal> will operate as
+        interpolation.</para>
+
         <section>
           <title>Synopsis</title>
 
@@ -26513,11 +26588,38 @@ End book</programlisting>
           </question>
 
           <answer>
-            <para>Starting from FreeMarker 2.3.4 you can use
-            <literal>[</literal> and <literal>]</literal> instead of
-            <literal>&lt;</literal> and <literal>&gt;</literal>. For more
-            details <link linkend="dgui_misc_alternativesyntax">read
-            this...</link></para>
+            <para>You can use <literal>[</literal> and <literal>]</literal>
+            instead of <literal>&lt;</literal> and <literal>&gt;</literal>;
+            see more about square bracket tag syntax <link
+            linkend="dgui_misc_alternativesyntax">here.</link> The comparison
+            operators, like <literal>&lt;</literal>, also have an alternative
+            syntax (<literal>lt</literal> and <literal>&amp;lt;</literal> in
+            this case); see more about them <link
+            linkend="dgui_template_exp_comparison">here</link>. Also, the
+            <literal>&amp;&amp;</literal> operator (which is not well-format
+            HTML/XML) can be written as <literal>\and</literal> or
+            <literal>&amp;amp;&amp;amp;</literal> since 2.3.27.</para>
+          </answer>
+        </qandaentry>
+
+        <qandaentry xml:id="faq_alternative_syntax_interpolation">
+          <question>
+            <para><literal>${<replaceable>...</replaceable>}</literal> and/or
+            <literal>#{<replaceable>...</replaceable>}</literal> is used in
+            the output I have to generate a lot, and FreeMarker tries to
+            resolve them. What to do?</para>
+          </question>
+
+          <answer>
+            <para>You can escape them like
+            <literal>${'$'}{<replaceable>...</replaceable>}</literal>, however
+            that's impractical if you have to do that often. So starting from
+            FreeMarker 2.3.28 you can use
+            <literal>[=<replaceable>...</replaceable></literal><literal>]</literal>
+            instead; see more about the square bracket interpolation syntax
+            <link linkend="dgui_misc_alternativesyntax">here.</link> If you
+            are going to generate JSP files or even FreeMarker templates, this
+            is very useful.</para>
           </answer>
         </qandaentry>
 
@@ -27479,6 +27581,46 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
 
           <itemizedlist>
             <listitem>
+              <para>Added new <literal>ParserConfiguration</literal> (such as
+              <literal>Configuration</literal> and
+              <literal>TemplateConfiguration</literal>) setting,
+              <literal>interpolation_syntax</literal>. It has 3 possible
+              values:</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para><literal>legacy</literal> (the default):
+                  Interpolations look like
+                  <literal>${<replaceable>...</replaceable>}</literal> or
+                  <literal>#{<replaceable>...</replaceable>}</literal>. Note
+                  that <literal>#{<replaceable>...</replaceable>}</literal> is
+                  deprecated for a long time now.</para>
+                </listitem>
+
+                <listitem>
+                  <para><literal>dollar</literal>: Interpolations look like
+                  <literal>${<replaceable>...</replaceable>}</literal>. With
+                  this syntax,
+                  <literal>#{<replaceable>...</replaceable>}</literal> will be
+                  just static text.</para>
+                </listitem>
+
+                <listitem>
+                  <para><literal>square_bracket</literal>: Interpolations look
+                  like <literal>[=<replaceable>...</replaceable>]</literal>.
+                  With this syntax
+                  <literal>${<replaceable>...</replaceable>}</literal> and
+                  <literal>#{<replaceable>...</replaceable>}</literal> will be
+                  just static text. So it's useful if you generate output in a
+                  format where those (typically
+                  <literal>${<replaceable>...</replaceable>}</literal>) are
+                  already used, such as to generate JSP pages, or to generate
+                  FreeMarker templates that use the default syntax.</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
+
+            <listitem>
               <para>Added new property to
               <literal>BeansWrapper.MethodAppearanceDecision</literal>:
               <literal>replaceExistingProperty</literal>. This is useful when

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/test/java/freemarker/core/InterpolationSyntaxTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/InterpolationSyntaxTest.java b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
new file mode 100644
index 0000000..3bd49be
--- /dev/null
+++ b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
@@ -0,0 +1,88 @@
+package freemarker.core;
+
+import static freemarker.template.Configuration.*;
+
+import org.junit.Test;
+
+import freemarker.test.TemplateTest;
+
+public class InterpolationSyntaxTest extends TemplateTest {
+
+    @Test
+    public void legacyInterpolationSyntaxTest() throws Exception {
+        // The default is: getConfiguration().setInterpolationSyntax(Configuration.LEGACY_INTERPOLATION_SYNTAX);
+        
+        assertOutput("${1} #{1} [=1]", "1 1 [=1]");
+        assertOutput(
+                "${{'x': 1}['x']} #{{'x': 1}['x']} [={'x': 1}['x']]",
+                "1 1 [={'x': 1}['x']]");
+    }
+
+    @Test
+    public void dollarInterpolationSyntaxTest() throws Exception {
+        getConfiguration().setInterpolationSyntax(DOLLAR_INTERPOLATION_SYNTAX);
+        
+        assertOutput("${1} #{1} [=1]", "1 #{1} [=1]");
+        assertOutput(
+                "${{'x': 1}['x']} #{{'x': 1}['x']} [={'x': 1}['x']]",
+                "1 #{{'x': 1}['x']} [={'x': 1}['x']]");
+    }
+
+    @Test
+    public void squareBracketInterpolationSyntaxTest() throws Exception {
+        getConfiguration().setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX);
+        
+        assertOutput("${1} #{1} [=1]", "${1} #{1} 1");
+        assertOutput(
+                "${{'x': 1}['x']} #{{'x': 1}['x']} [={'x': 1}['x']]",
+                "${{'x': 1}['x']} #{{'x': 1}['x']} 1");
+
+        assertOutput("[=1]][=2]]", "1]2]");
+        assertOutput("[= 1 ][= <#-- c --> 2 <#-- c --> ]", "12");
+        assertOutput("[ =1]", "[ =1]");
+        
+        assertErrorContains("<#if [true][0]]></#if>", "\"]\"", "nothing open");
+        assertOutput("[#ftl][#if [true][0]]>[/#if]", ">");
+        
+        assertOutput("[='a[=1]b']", "a1b");
+    }
+
+    @Test
+    public void squareBracketTagSyntaxStillWorks() throws Exception {
+        getConfiguration().setTagSyntax(SQUARE_BRACKET_TAG_SYNTAX);
+        for (int syntax : new int[] {
+                LEGACY_INTERPOLATION_SYNTAX, DOLLAR_INTERPOLATION_SYNTAX, SQUARE_BRACKET_INTERPOLATION_SYNTAX }) {
+            assertOutput("[#if [true][0]]t[#else]f[/#if]", "t");
+        }
+    }
+    
+    @Test
+    public void legacyTagSyntaxGlitchStillWorksTest() throws Exception {
+        String ftl = "<#if [true][0]]t<#else]f</#if]";
+        
+        for (int syntax : new int[] { LEGACY_INTERPOLATION_SYNTAX, DOLLAR_INTERPOLATION_SYNTAX }) {
+            getConfiguration().setInterpolationSyntax(syntax);
+            assertOutput(ftl, "t");
+        }
+        
+        // Glitch is not emulated with this:
+        getConfiguration().setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX);
+        assertErrorContains(ftl, "\"]\"");
+    }
+
+    @Test
+    public void errorMessagesAreSquareBracketInterpolationSyntaxAwareTest() throws Exception {
+        assertErrorContains("<#if ${x}></#if>", "${...}", "${myExpression}");
+        assertErrorContains("<#if #{x}></#if>", "#{...}", "#{myExpression}");
+        assertErrorContains("<#if [=x]></#if>", "[=...]", "[=myExpression]");
+    }
+
+    @Test
+    public void unclosedSyntaxErrorTest() throws Exception {
+        assertErrorContains("${1", "unclosed \"{\"");
+        
+        getConfiguration().setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX);
+        assertErrorContains("[=1", "unclosed \"[\"");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/test/java/freemarker/core/TemplateConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/TemplateConfigurationTest.java b/src/test/java/freemarker/core/TemplateConfigurationTest.java
index 665f270..76199ad 100644
--- a/src/test/java/freemarker/core/TemplateConfigurationTest.java
+++ b/src/test/java/freemarker/core/TemplateConfigurationTest.java
@@ -183,6 +183,7 @@ public class TemplateConfigurationTest {
 
         // Parser-only settings:
         SETTING_ASSIGNMENTS.put("tagSyntax", Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+        SETTING_ASSIGNMENTS.put("interpolationSyntax", Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX);
         SETTING_ASSIGNMENTS.put("namingConvention", Configuration.LEGACY_NAMING_CONVENTION);
         SETTING_ASSIGNMENTS.put("whitespaceStripping", false);
         SETTING_ASSIGNMENTS.put("strictSyntaxMode", false);
@@ -604,6 +605,14 @@ public class TemplateConfigurationTest {
             assertOutputWithoutAndWithTC(tc, "[#if true]y[/#if]", "[#if true]y[/#if]", "y");
             testedProps.add(Configuration.TAG_SYNTAX_KEY_CAMEL_CASE);
         }
+
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            tc.setInterpolationSyntax(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX);
+            assertOutputWithoutAndWithTC(tc, "${1}#{2}[=3]", "12[=3]", "${1}#{2}3");
+            testedProps.add(Configuration.INTERPOLATION_SYNTAX_KEY_CAMEL_CASE);
+        }
         
         {
             TemplateConfiguration tc = new TemplateConfiguration();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/test/java/freemarker/template/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index cb46a94..fa6b508 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -1616,6 +1616,34 @@ public class ConfigurationTest extends TestCase {
         assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, cfg.getNamingConvention());
     }
 
+    public void testInterpolationSyntaxSetting() throws TemplateException {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
+
+        // Default is "legacy":
+        assertEquals(Configuration.LEGACY_INTERPOLATION_SYNTAX, cfg.getInterpolationSyntax());
+        
+        cfg.setSetting("interpolation_syntax", "dollar");
+        assertEquals(Configuration.DOLLAR_INTERPOLATION_SYNTAX, cfg.getInterpolationSyntax());
+        
+        cfg.setSetting("interpolation_syntax", "square_bracket");
+        assertEquals(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX, cfg.getInterpolationSyntax());
+        
+        cfg.setSetting("interpolation_syntax", "legacy");
+        assertEquals(Configuration.LEGACY_INTERPOLATION_SYNTAX, cfg.getInterpolationSyntax());
+        
+        // Camel case:
+        cfg.setSetting("interpolationSyntax", "squareBracket");
+        assertEquals(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX, cfg.getInterpolationSyntax());
+        
+        try {
+            cfg.setSetting("interpolation_syntax", "no_such_syntax");
+            fail();
+        } catch (TemplateException e) {
+            assertThat(e.getMessage(), containsString("no_such_syntax"));
+        }
+        
+    }
+    
     public void testLazyImportsSetSetting() throws TemplateException {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
 


[07/12] incubator-freemarker git commit: Added some more interpolation_syntax tests

Posted by dd...@apache.org.
Added some more interpolation_syntax tests


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

Branch: refs/heads/2.3
Commit: aeaafe5132fdd8fdde773b29d3584921b2f81d8d
Parents: eb6781b
Author: ddekany <dd...@apache.org>
Authored: Fri Mar 16 18:48:51 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Fri Mar 16 18:48:51 2018 +0100

----------------------------------------------------------------------
 .../core/InterpolationSyntaxTest.java           | 29 +++++++++++++++++++-
 1 file changed, 28 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/aeaafe51/src/test/java/freemarker/core/InterpolationSyntaxTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/InterpolationSyntaxTest.java b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
index 3bd49be..c1721c8 100644
--- a/src/test/java/freemarker/core/InterpolationSyntaxTest.java
+++ b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
@@ -16,6 +16,13 @@ public class InterpolationSyntaxTest extends TemplateTest {
         assertOutput(
                 "${{'x': 1}['x']} #{{'x': 1}['x']} [={'x': 1}['x']]",
                 "1 1 [={'x': 1}['x']]");
+        
+        assertOutput("${'a[=1]b'}", "a[=1]b");
+        assertOutput("${'a${1}#{2}b'}", "a12b");
+        assertOutput("${'a${1}#{2}b[=3]'}", "a12b[=3]");
+        
+        assertOutput("<@r'${1} #{1} [=1]'?interpret />", "1 1 [=1]");
+        assertOutput("${'\"${1} #{1} [=1]\"'?eval}", "1 1 [=1]");
     }
 
     @Test
@@ -26,6 +33,13 @@ public class InterpolationSyntaxTest extends TemplateTest {
         assertOutput(
                 "${{'x': 1}['x']} #{{'x': 1}['x']} [={'x': 1}['x']]",
                 "1 #{{'x': 1}['x']} [={'x': 1}['x']]");
+        
+        assertOutput("${'a[=1]b'}", "a[=1]b");
+        assertOutput("${'a${1}#{2}b'}", "a1#{2}b");
+        assertOutput("${'a${1}#{2}b[=3]'}", "a1#{2}b[=3]");
+        
+        assertOutput("<@r'${1} #{1} [=1]'?interpret />", "1 #{1} [=1]");
+        assertOutput("${'\"${1} #{1} [=1]\"'?eval}", "1 #{1} [=1]");
     }
 
     @Test
@@ -41,10 +55,22 @@ public class InterpolationSyntaxTest extends TemplateTest {
         assertOutput("[= 1 ][= <#-- c --> 2 <#-- c --> ]", "12");
         assertOutput("[ =1]", "[ =1]");
         
+        // Legacy tag closing glitch is not emulated with this:
         assertErrorContains("<#if [true][0]]></#if>", "\"]\"", "nothing open");
+        
+        getConfiguration().setTagSyntax(SQUARE_BRACKET_TAG_SYNTAX);
+        assertOutput("[#if [true][0]]>[/#if]", ">");
+        assertOutput("[=1][=2]${3}", "12${3}");
+        getConfiguration().setTagSyntax(ANGLE_BRACKET_TAG_SYNTAX);
         assertOutput("[#ftl][#if [true][0]]>[/#if]", ">");
+        assertOutput("[#ftl][=1][=2]${3}", "12${3}");
         
         assertOutput("[='a[=1]b']", "a1b");
+        assertOutput("[='a${1}#{2}b']", "a${1}#{2}b");
+        assertOutput("[='a${1}#{2}b[=3]']", "a${1}#{2}b3");
+        
+        assertOutput("<@r'${1} #{1} [=1]'?interpret />", "${1} #{1} 1");
+        assertOutput("[='\"${1} #{1} [=1]\"'?eval]", "${1} #{1} 1");
     }
 
     @Test
@@ -53,6 +79,7 @@ public class InterpolationSyntaxTest extends TemplateTest {
         for (int syntax : new int[] {
                 LEGACY_INTERPOLATION_SYNTAX, DOLLAR_INTERPOLATION_SYNTAX, SQUARE_BRACKET_INTERPOLATION_SYNTAX }) {
             assertOutput("[#if [true][0]]t[#else]f[/#if]", "t");
+            assertOutput("[@r'[#if [true][0]]t[#else]f[/#if]'?interpret /]", "t");
         }
     }
     
@@ -65,7 +92,7 @@ public class InterpolationSyntaxTest extends TemplateTest {
             assertOutput(ftl, "t");
         }
         
-        // Glitch is not emulated with this:
+        // Legacy tag closing glitch is not emulated with this:
         getConfiguration().setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX);
         assertErrorContains(ftl, "\"]\"");
     }


[12/12] incubator-freemarker git commit: Merge remote-tracking branch 'origin/2.3-gae' into 2.3

Posted by dd...@apache.org.
Merge remote-tracking branch 'origin/2.3-gae' into 2.3


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

Branch: refs/heads/2.3
Commit: edefaa2f6cdf00e1f979700b384f0b966873bde4
Parents: 0c69b19 0129453
Author: ddekany <dd...@apache.org>
Authored: Mon Mar 19 22:21:23 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Mar 19 22:21:23 2018 +0100

----------------------------------------------------------------------
 src/main/java/freemarker/core/Configurable.java |   7 +-
 .../java/freemarker/core/DollarVariable.java    |  11 +-
 .../core/ExtendedDecimalFormatParser.java       |   5 +-
 .../LegacyConstructorParserConfiguration.java   |   9 +-
 .../java/freemarker/core/NumericalOutput.java   |   5 +-
 .../freemarker/core/ParserConfiguration.java    |   7 +
 .../java/freemarker/core/StringLiteral.java     |  14 +-
 .../freemarker/core/TemplateConfiguration.java  |  27 ++
 ..._ParserConfigurationWithInheritedFormat.java |   4 +
 .../ext/beans/OverloadedNumberUtil.java         |   2 +-
 .../java/freemarker/template/Configuration.java |  81 +++-
 src/main/java/freemarker/template/Template.java |  22 +
 .../java/freemarker/template/_TemplateAPI.java  |  14 +-
 .../freemarker/template/utility/StringUtil.java |  22 +-
 src/main/javacc/FTL.jj                          | 258 +++++++----
 src/manual/en_US/book.xml                       | 440 +++++++++++++++----
 .../core/InterpolationSyntaxTest.java           | 154 +++++++
 .../core/ParsingErrorMessagesTest.java          |  66 +--
 .../core/TemplateConfigurationTest.java         |   9 +
 .../freemarker/template/ConfigurationTest.java  |  70 +++
 .../template/utility/StringUtilTest.java        |  24 +
 .../templates/string-builtins3.ftl              |   4 +-
 22 files changed, 1015 insertions(+), 240 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/edefaa2f/src/main/java/freemarker/template/Template.java
----------------------------------------------------------------------


[03/12] incubator-freemarker git commit: Fixed incorrect listing of valid roundingMode-s in extended Java decimal format parsing error message

Posted by dd...@apache.org.
Fixed incorrect listing of valid roundingMode-s in extended Java decimal format parsing error message


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

Branch: refs/heads/2.3
Commit: 54bd25b0ec51f40ba6e231edf21bef56aa6a8330
Parents: 40ced2d
Author: ddekany <dd...@apache.org>
Authored: Tue Mar 13 09:41:35 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Tue Mar 13 09:41:35 2018 +0100

----------------------------------------------------------------------
 .../java/freemarker/core/ExtendedDecimalFormatParser.java     | 5 ++++-
 src/manual/en_US/book.xml                                     | 7 +++++++
 2 files changed, 11 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/54bd25b0/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java b/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java
index c31171b..0c3286a 100644
--- a/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java
+++ b/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java
@@ -80,7 +80,10 @@ class ExtendedDecimalFormatParser {
                 } else if (value.equals(PARAM_VALUE_RND_UNNECESSARY)) {
                     parsedValue = RoundingMode.UNNECESSARY;
                 } else {
-                    throw new InvalidParameterValueException("Should be one of: u, d, c, f, hd, he, hu, un");
+                    throw new InvalidParameterValueException("Should be one of: "
+                            + PARAM_VALUE_RND_UP + ", " + PARAM_VALUE_RND_DOWN + ", " + PARAM_VALUE_RND_CEILING + ", "
+                            + PARAM_VALUE_RND_FLOOR + ", " + PARAM_VALUE_RND_HALF_DOWN + ", "
+                            + PARAM_VALUE_RND_HALF_EVEN + ", " + PARAM_VALUE_RND_UNNECESSARY);
                 }
 
                 if (_JavaVersions.JAVA_6 == null) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/54bd25b0/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 3d8e321..231b607 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -27578,6 +27578,13 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               <para><literal>freemarker.ext.beans.HashAdapter.size()</literal>
               was overridden for better performance.</para>
             </listitem>
+
+            <listitem>
+              <para>Fixed incorrect listing of valid
+              <literal>roundingMode</literal>-s in <link
+              linkend="topic.extendedJavaDecimalFormat">extended Java decimal
+              format</link> parsing error message</para>
+            </listitem>
           </itemizedlist>
         </section>
       </section>


[06/12] incubator-freemarker git commit: Changed Configuration.xxx_INTERPOLATION_SYNTAX int values so that they don't overlap with ..._TAG_SYNTAX values. Also, added test case to ConfigurationTest for tag_syntax, fixing incorrect error message along the

Posted by dd...@apache.org.
Changed Configuration.xxx_INTERPOLATION_SYNTAX int values so that they don't overlap with ..._TAG_SYNTAX values. Also, added test case to ConfigurationTest for tag_syntax, fixing incorrect error message along the way.


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

Branch: refs/heads/2.3
Commit: eb6781b4444d774c536f3da235972f1c34cd68bd
Parents: ca1ecf7
Author: ddekany <dd...@apache.org>
Authored: Fri Mar 16 18:46:11 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Fri Mar 16 18:46:11 2018 +0100

----------------------------------------------------------------------
 .../java/freemarker/template/Configuration.java |  6 +--
 .../java/freemarker/template/_TemplateAPI.java  |  4 +-
 .../freemarker/template/ConfigurationTest.java  | 44 +++++++++++++++++++-
 3 files changed, 48 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eb6781b4/src/main/java/freemarker/template/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 34b055e..53fcd49 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -381,11 +381,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     public static final int SQUARE_BRACKET_TAG_SYNTAX = 2;
 
     /** <code>${expression}</code> and the deprecated <code>#{expression; numFormat}</code> @since 2.3.28 */
-    public static final int LEGACY_INTERPOLATION_SYNTAX = 0;
+    public static final int LEGACY_INTERPOLATION_SYNTAX = 20;
     /** <code>${expression}</code> only (not <code>#{expression; numFormat}</code>) @since 2.3.28 */
-    public static final int DOLLAR_INTERPOLATION_SYNTAX = 1;
+    public static final int DOLLAR_INTERPOLATION_SYNTAX = 21;
     /** <code>[=expression]</code> @since 2.3.28 */
-    public static final int SQUARE_BRACKET_INTERPOLATION_SYNTAX = 2;
+    public static final int SQUARE_BRACKET_INTERPOLATION_SYNTAX = 22;
     
     public static final int AUTO_DETECT_NAMING_CONVENTION = 10;
     public static final int LEGACY_NAMING_CONVENTION = 11;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eb6781b4/src/main/java/freemarker/template/_TemplateAPI.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/_TemplateAPI.java b/src/main/java/freemarker/template/_TemplateAPI.java
index 9b2c22d..124677a 100644
--- a/src/main/java/freemarker/template/_TemplateAPI.java
+++ b/src/main/java/freemarker/template/_TemplateAPI.java
@@ -154,8 +154,8 @@ public class _TemplateAPI {
             && tagSyntax != Configuration.SQUARE_BRACKET_TAG_SYNTAX
             && tagSyntax != Configuration.ANGLE_BRACKET_TAG_SYNTAX) {
             throw new IllegalArgumentException("\"tag_syntax\" can only be set to one of these: "
-                    + "Configuration.AUTO_DETECT_TAG_SYNTAX, Configuration.ANGLE_BRACKET_SYNTAX, "
-                    + "or Configuration.SQUARE_BRACKET_SYNTAX");
+                    + "Configuration.AUTO_DETECT_TAG_SYNTAX, Configuration.ANGLE_BRACKET_TAG_SYNTAX, "
+                    + "or Configuration.SQUARE_BRACKET_TAG_SYNTAX");
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eb6781b4/src/test/java/freemarker/template/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index fa6b508..72c1291 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -1616,6 +1616,41 @@ public class ConfigurationTest extends TestCase {
         assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, cfg.getNamingConvention());
     }
 
+    public void testTagSyntaxSetting() throws TemplateException {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
+
+        // Default is "angle brackets":
+        assertEquals(Configuration.ANGLE_BRACKET_TAG_SYNTAX, cfg.getTagSyntax());
+
+        cfg.setSetting("tag_syntax", "angle_bracket");
+        assertEquals(Configuration.ANGLE_BRACKET_TAG_SYNTAX, cfg.getTagSyntax());
+        
+        cfg.setSetting("tag_syntax", "square_bracket");
+        assertEquals(Configuration.SQUARE_BRACKET_TAG_SYNTAX, cfg.getTagSyntax());
+        
+        cfg.setSetting("tag_syntax", "auto_detect");
+        assertEquals(Configuration.AUTO_DETECT_TAG_SYNTAX, cfg.getTagSyntax());
+        
+        // Camel case:
+        cfg.setSetting("tagSyntax", "squareBracket");
+        assertEquals(Configuration.SQUARE_BRACKET_TAG_SYNTAX, cfg.getTagSyntax());
+        
+        try {
+            cfg.setSetting("tag_syntax", "no_such_syntax");
+            fail();
+        } catch (TemplateException e) {
+            assertThat(e.getMessage(), containsString("no_such_syntax"));
+        }
+        
+        // Catch an oversight that's easy to do:
+        try {
+            cfg.setTagSyntax(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), containsString("SQUARE_BRACKET_TAG_SYNTAX"));
+        }
+    }
+    
     public void testInterpolationSyntaxSetting() throws TemplateException {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
 
@@ -1641,7 +1676,14 @@ public class ConfigurationTest extends TestCase {
         } catch (TemplateException e) {
             assertThat(e.getMessage(), containsString("no_such_syntax"));
         }
-        
+
+        // Catch an oversight that's easy to do:
+        try {
+            cfg.setInterpolationSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), containsString("SQUARE_BRACKET_INTERPOLATION_SYNTAX"));
+        }
     }
     
     public void testLazyImportsSetSetting() throws TemplateException {


[11/12] incubator-freemarker git commit: Cleaned up more lexer/parser logic related to the handling of tag-closer delimiters ('>' and ']'). This has yielded the following two change log entries (and some improvements in error message quality, but that's

Posted by dd...@apache.org.
Cleaned up more lexer/parser logic related to the handling of tag-closer delimiters ('>' and ']'). This has yielded the following two change log entries (and some improvements in error message quality, but that's hardly noticeable):

1. When the incompatible_improvements setting is set to 2.3.28 (or greater), fixed legacy parser glitch where a tag can be closed with an illegal ] (when it's not part of an expression) despite that the tag syntax is set to angle brackets. For example <#if x] worked just like <#if x>. Note that it doesn't affect the legal usage of ], like <#if x[0]> works correctly without this fix as well.
2. Fixed parser bug that disallowed using > at the top-level inside an interpolation (${...}). It had the same reason why <#if  x > y> doesn't work as naively expected, but there's no real ambiguity in ${x > y}, so now it's allowed. Note that ${(x > y)?c} and ${(x > y)?string('y', 'n')}, which are how booleans are commonly printed, have always worked, as the > operation is not on the top-level inside the interpolation.


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

Branch: refs/heads/2.3
Commit: 01294537b882d06e2ac7df5b5fff590bf45f5227
Parents: f55f9d8
Author: ddekany <dd...@apache.org>
Authored: Mon Mar 19 22:19:37 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Mar 19 22:19:37 2018 +0100

----------------------------------------------------------------------
 .../java/freemarker/template/Configuration.java |  4 +
 src/main/javacc/FTL.jj                          | 77 ++++++++++++++++----
 src/manual/en_US/book.xml                       | 27 +++++++
 .../core/InterpolationSyntaxTest.java           | 33 +++++++--
 .../core/ParsingErrorMessagesTest.java          | 66 ++++++-----------
 .../templates/string-builtins3.ftl              |  4 +-
 6 files changed, 147 insertions(+), 64 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/01294537/src/main/java/freemarker/template/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 3114747..5376dda 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -885,6 +885,10 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      *           (Of course, the parameter default value expression is still evaluated in the context of the called
      *           macro or function.) Similarly, {@code .macro_caller_template_name} (which itself was added in 2.3.28),
      *           when used in a macro call argument, won't be incorrectly evaluated in the context of the called macro.
+     *       <li><p>Fixed legacy parser glitch where a tag can be closed with an illegal {@code ]} (when it's not part
+     *           of an expression) despite that the tag syntax is set to angle brackets. For example {@code <#if x]}
+     *           worked just like {@code <#if x>}. Note that it doesn't affect the legal usage of {@code ]}, like
+     *           {@code <#if x[0]>} works correctly without this fix as well. 
      *     </ul>
      *   </li>
      * </ul>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/01294537/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index 26f3368..630ef39 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -667,7 +667,7 @@ TOKEN_MGR_DECLS:
         
         if (!strictSyntaxMode) {
             // Legacy feature (or bug?): Tag syntax never gets estabilished in non-strict mode.
-            // We do establilish the naming convention though.
+            // We do establish the naming convention though.
             checkNamingConvention(tok, tokenNamingConvention);
             SwitchTo(newLexState);
             return;
@@ -684,6 +684,24 @@ TOKEN_MGR_DECLS:
         // We only get here if this is a strict FTL tag.
         directiveSyntaxEstablished = true;
         
+        if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
+                || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX) {
+	        // For END_xxx tags, as they can't contain expressions, the whole tag is a single token. So this is the only
+	        // chance to check if we got something inconsistent like `</#if]`. (We can't do this at the #CLOSE_TAG1 or
+	        // such, as at that point it's possible that the tag syntax is not yet established.)
+	        char lastChar = image.charAt(image.length() - 1);
+	        // Is it an end tag?
+	        if (lastChar == ']' || lastChar == '>') {
+	            if (!squBracTagSyntax && lastChar != '>' || squBracTagSyntax && lastChar != ']') {
+		            throw new TokenMgrError(
+		                    "The tag shouldn't end with \""+ lastChar + "\".",
+		                    TokenMgrError.LEXICAL_ERROR,
+		                    tok.beginLine, tok.beginColumn,
+		                    tok.endLine, tok.endColumn);
+                }
+            } // if end-tag
+        }
+        
         checkNamingConvention(tok, tokenNamingConvention);
         
         SwitchTo(newLexState);
@@ -825,9 +843,6 @@ TOKEN_MGR_DECLS:
     }
 
     private void endInterpolation(Token closingTk) {
-        if (postInterpolationLexState == -1) {
-            throw newUnexpectedClosingTokenException(closingTk);
-        }
         SwitchTo(postInterpolationLexState);
         postInterpolationLexState = -1;
     }
@@ -1298,11 +1313,19 @@ TOKEN:
     {
         if (bracketNesting > 0) {
             --bracketNesting;
-        } else if (interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX
-                && (postInterpolationLexState != -1 || !squBracTagSyntax)) {
+        } else if (interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX && postInterpolationLexState != -1) {
             endInterpolation(matchedToken);
         } else {
-            // Glitch where you can close any tag with `]`, like `<#if x]`. We keep it for backward compatibility.
+            // There's a legacy glitch where you can always close a tag with `]`, like `<#if x]`. We have to keep that
+            // working for backward compatibility, hence we don't always throw at !squBracTagSyntax:
+            if (!squBracTagSyntax
+                    && (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
+                            || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX)
+                    || postInterpolationLexState != -1 /* We're in an interpolation => We aren't in a tag */) {
+                throw newUnexpectedClosingTokenException(matchedToken);
+            }
+            
+            // Close tag, either legally or to emulate legacy glitch:
             matchedToken.kind = DIRECTIVE_END;
             if (inFTLHeader) {
                 eatNewline();
@@ -1336,7 +1359,7 @@ TOKEN:
     {
         if (curlyBracketNesting > 0) {
             --curlyBracketNesting;
-        } else if (interpolationSyntax != SQUARE_BRACKET_INTERPOLATION_SYNTAX) {
+        } else if (interpolationSyntax != SQUARE_BRACKET_INTERPOLATION_SYNTAX && postInterpolationLexState != -1) {
             endInterpolation(matchedToken);
         } else {
             throw newUnexpectedClosingTokenException(matchedToken);
@@ -1529,19 +1552,47 @@ TOKEN:
 {
     <DIRECTIVE_END : ">">
     {
-        if (inFTLHeader) eatNewline();
-        inFTLHeader = false;
-        if (squBracTagSyntax) {
+        if (inFTLHeader) {
+	        eatNewline();
+	        inFTLHeader = false;
+        }
+        if (squBracTagSyntax || postInterpolationLexState != -1 /* We are in an interpolation */) {
             matchedToken.kind = NATURAL_GT;
         } else {
+            if (directiveSyntaxEstablished && ( incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
+                    || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX)) {
+                if (squBracTagSyntax) {
+                    throw new TokenMgrError(
+                            "The tag shouldn't end with \"" + image.charAt(image.length() - 1) + "\".",
+                            TokenMgrError.LEXICAL_ERROR,
+                            matchedToken.beginLine, matchedToken.beginColumn,
+                            matchedToken.endLine, matchedToken.endColumn);
+                }
+            }
+        
             SwitchTo(DEFAULT);
         }
     }
     |
     <EMPTY_DIRECTIVE_END : "/>" | "/]">
     {
-        if (inFTLHeader) eatNewline();
-        inFTLHeader = false;
+        if (directiveSyntaxEstablished && (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
+                || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX)) {
+            String image = matchedToken.image;
+            char lastChar = image.charAt(image.length() - 1);
+            if (!squBracTagSyntax && lastChar != '>' || squBracTagSyntax && lastChar != ']') {
+                throw new TokenMgrError(
+                        "The tag shouldn't end with \""+ lastChar + "\".",
+                        TokenMgrError.LEXICAL_ERROR,
+                        matchedToken.beginLine, matchedToken.beginColumn,
+                        matchedToken.endLine, matchedToken.endColumn);
+            }
+        }
+    
+        if (inFTLHeader) {
+	        eatNewline();
+	        inFTLHeader = false;
+        }
         SwitchTo(DEFAULT);
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/01294537/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index ac58a2a..f06b759 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -27788,6 +27788,33 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>When the <link
+              linkend="pgui_config_incompatible_improvements_how_to_set"><literal>incompatible_improvements</literal>
+              setting</link> is set to 2.3.28 (or greater), fixed legacy
+              parser glitch where a tag can be closed with an illegal
+              <literal>]</literal> (when it's not part of an expression)
+              despite that the tag syntax is set to angle brackets. For
+              example <literal>&lt;#if x]</literal> worked just like
+              <literal>&lt;#if x&gt;</literal>. Note that it doesn't affect
+              the legal usage of <literal>]</literal>, like <literal>&lt;#if
+              x[0]&gt;</literal> works correctly without this fix as
+              well.</para>
+            </listitem>
+
+            <listitem>
+              <para>Fixed parser bug that disallowed using
+              <literal>&gt;</literal> at the top-level inside an interpolation
+              (<literal>${...}</literal>). It had the same reason why
+              <literal>&lt;#if x &gt; y&gt;</literal> doesn't work as naively
+              expected, but there's no real ambiguity in <literal>${x &gt;
+              y}</literal>, so now it's allowed. Note that <literal>${(x &gt;
+              y)?c}</literal> and <literal>${(x &gt; y)?string('y',
+              'n')}</literal>, which are how booleans are commonly printed,
+              have always worked, as the <literal>&gt;</literal> operation is
+              not on the top-level inside the interpolation.</para>
+            </listitem>
+
+            <listitem>
               <para>Fixed incorrect listing of valid
               <literal>roundingMode</literal>-s in <link
               linkend="topic.extendedJavaDecimalFormat">extended Java decimal

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/01294537/src/test/java/freemarker/core/InterpolationSyntaxTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/InterpolationSyntaxTest.java b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
index 2322d99..9a56f13 100644
--- a/src/test/java/freemarker/core/InterpolationSyntaxTest.java
+++ b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
@@ -7,6 +7,7 @@ import java.io.StringWriter;
 
 import org.junit.Test;
 
+import freemarker.template.Configuration;
 import freemarker.template.Template;
 import freemarker.test.TemplateTest;
 
@@ -27,6 +28,9 @@ public class InterpolationSyntaxTest extends TemplateTest {
         
         assertOutput("<@r'${1} #{1} [=1]'?interpret />", "1 1 [=1]");
         assertOutput("${'\"${1} #{1} [=1]\"'?eval}", "1 1 [=1]");
+        
+        assertOutput("<#setting booleanFormat='y,n'>${2>1}", "y"); // Not an error since 2.3.28
+        assertOutput("[#ftl][#setting booleanFormat='y,n']${2>1}", "y"); // Not an error since 2.3.28
     }
 
     @Test
@@ -78,7 +82,9 @@ public class InterpolationSyntaxTest extends TemplateTest {
         
         assertErrorContains("[=", "end of file");
         assertErrorContains("[=1", "unclosed \"[\"");
-        assertErrorContains("[=1}", "\"}\"", "open");
+
+        assertOutput("<#setting booleanFormat='y,n'>[=2>1]", "y");
+        assertOutput("[#ftl][#setting booleanFormat='y,n'][=2>1]", "y");
         
         assertOutput("[='[\\=1]']", "[=1]");
         assertOutput("[='[\\=1][=2]']", "12"); // Usual legacy interpolation escaping glitch...
@@ -101,18 +107,35 @@ public class InterpolationSyntaxTest extends TemplateTest {
     
     @Test
     public void legacyTagSyntaxGlitchStillWorksTest() throws Exception {
-        String ftl = "<#if [true][0]]t<#else]f</#if]";
+        String badFtl1 = "<#if [true][0]]OK</#if>";
+        String badFtl2 = "<#if true>OK</#if]";
+        String badFtl3 = "<#assign x = 'OK'/]${x}";
+        String badFtl4 = " <#t/]OK\n";
         
+        getConfiguration().setIncompatibleImprovements(Configuration.VERSION_2_3_27);
         for (int syntax : new int[] { LEGACY_INTERPOLATION_SYNTAX, DOLLAR_INTERPOLATION_SYNTAX }) {
             getConfiguration().setInterpolationSyntax(syntax);
-            assertOutput(ftl, "t");
+            assertOutput(badFtl1, "OK");
+            assertOutput(badFtl2, "OK");
+            assertOutput(badFtl3, "OK");
+            assertOutput(badFtl4, "OK");
         }
         
         // Legacy tag closing glitch is not emulated with this:
         getConfiguration().setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX);
-        assertErrorContains(ftl, "\"]\"");
+        assertErrorContains(badFtl1, "\"]\"");
+        assertErrorContains(badFtl2, "\"]\"");
+        assertErrorContains(badFtl3, "\"]\"");
+        assertErrorContains(badFtl4, "\"]\"");
+        
+        getConfiguration().setInterpolationSyntax(LEGACY_INTERPOLATION_SYNTAX);
+        getConfiguration().setIncompatibleImprovements(Configuration.VERSION_2_3_28);
+        assertErrorContains(badFtl1, "\"]\"");
+        assertErrorContains(badFtl2, "\"]\"");
+        assertErrorContains(badFtl3, "\"]\"");
+        assertErrorContains(badFtl4, "\"]\"");
     }
-
+    
     @Test
     public void errorMessagesAreSquareBracketInterpolationSyntaxAwareTest() throws Exception {
         assertErrorContains("<#if ${x}></#if>", "${...}", "${myExpression}");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/01294537/src/test/java/freemarker/core/ParsingErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/ParsingErrorMessagesTest.java b/src/test/java/freemarker/core/ParsingErrorMessagesTest.java
index e471a31..0081662 100644
--- a/src/test/java/freemarker/core/ParsingErrorMessagesTest.java
+++ b/src/test/java/freemarker/core/ParsingErrorMessagesTest.java
@@ -19,23 +19,23 @@
 
 package freemarker.core;
 
-import static org.junit.Assert.*;
-
-import java.io.IOException;
+import static freemarker.template.Configuration.*;
 
 import org.junit.Test;
 
 import freemarker.template.Configuration;
-import freemarker.template.Template;
-import freemarker.template.utility.StringUtil;
+import freemarker.test.TemplateTest;
 
-public class ParsingErrorMessagesTest {
+public class ParsingErrorMessagesTest extends TemplateTest {
 
-    private Configuration cfg = new Configuration(Configuration.VERSION_2_3_21);
-    {
+    @Override
+    protected Configuration createConfiguration() throws Exception {
+        Configuration cfg = super.createConfiguration();
+        cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_21);
         cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
+        return cfg;
     }
-    
+
     @Test
     public void testNeedlessInterpolation() {
         assertErrorContains("<#if ${x} == 3></#if>", "instead of ${");
@@ -77,45 +77,23 @@ public class ParsingErrorMessagesTest {
     }
     
     @Test
-    public void testInterpolatingClosingsErrors() {
-        assertErrorContains("${x", "unclosed");
+    public void testInterpolatingClosingsErrors() throws Exception {
+        assertErrorContains("<#ftl>${x", "unclosed");
         assertErrorContains("<#assign x = x}>", "\"}\"", "open");
-        // TODO assertErrorContains("<#assign x = '${x'>", "unclosed");
-    }
-    
-    private void assertErrorContains(String ftl, String... expectedSubstrings) {
-        assertErrorContains(false, ftl, expectedSubstrings);
-        assertErrorContains(true, ftl, expectedSubstrings);
-    }
-
-    private void assertErrorContains(boolean squareTags, String ftl, String... expectedSubstrings) {
-        try {
-            if (squareTags) {
-                ftl = ftl.replace('<', '[').replace('>', ']');
-            }
-            new Template("adhoc", ftl, cfg);
-            fail("The tempalte had to fail");
-        } catch (ParseException e) {
-            String msg = e.getMessage();
-            for (String needle: expectedSubstrings) {
-                if (needle.startsWith("\\!")) {
-                    String netNeedle = needle.substring(2); 
-                    if (msg.contains(netNeedle)) {
-                        fail("The message shouldn't contain substring " + StringUtil.jQuote(netNeedle) + ":\n" + msg);
-                    }
-                } else if (!msg.contains(needle)) {
-                    fail("The message didn't contain substring " + StringUtil.jQuote(needle) + ":\n" + msg);
-                }
-            }
-            showError(e);
-        } catch (IOException e) {
-            // Won't happen
-            throw new RuntimeException(e);
+        assertOutput("<#assign x = '${x'>", ""); // Legacy glitch... should fail in theory.
+        
+        for (int syntax : new int[] { LEGACY_INTERPOLATION_SYNTAX, DOLLAR_INTERPOLATION_SYNTAX }) {
+            getConfiguration().setInterpolationSyntax(syntax);
+            assertErrorContains("<#ftl>${'x']", "\"]\"", "open");
+            super.assertErrorContains("<#ftl>${'x'>", "end of file");
+            super.assertErrorContains("[#ftl]${'x'>", "end of file");
         }
     }
     
-    private void showError(Throwable e) {
-        //System.out.println(e);
+    protected Throwable assertErrorContains(String ftl, String... expectedSubstrings) {
+        super.assertErrorContains(ftl, expectedSubstrings);
+        ftl = ftl.replace('<', '[').replace('>', ']');
+        return super.assertErrorContains(ftl, expectedSubstrings);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/01294537/src/test/resources/freemarker/test/templatesuite/templates/string-builtins3.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/string-builtins3.ftl b/src/test/resources/freemarker/test/templatesuite/templates/string-builtins3.ftl
index 77389fa..06e7dd7 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/string-builtins3.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/string-builtins3.ftl
@@ -198,7 +198,7 @@
 <@assertEquals expected='aa' actual=27?lower_abc />
 <@assertEquals expected='ab' actual=28?lower_abc />
 <@assertEquals expected='cv' actual=100?lower_abc />
-<@assertFails messageRegexp='0|at least 1']>
+<@assertFails messageRegexp='0|at least 1'>
     ${0?lower_abc}
 </...@assertFails>
 <@assertFails messageRegexp='0|at least 1'>
@@ -214,7 +214,7 @@
 <@assertEquals expected='AA' actual=27?upper_abc />
 <@assertEquals expected='AB' actual=28?upper_abc />
 <@assertEquals expected='CV' actual=100?upper_abc />
-<@assertFails messageRegexp='0|at least 1']>
+<@assertFails messageRegexp='0|at least 1'>
     ${0?upper_abc}
 </...@assertFails>
 <@assertFails messageRegexp='0|at least 1'>


[10/12] incubator-freemarker git commit: (Some more cleanup in FTL.jj... as far as BC allows us)

Posted by dd...@apache.org.
(Some more cleanup in FTL.jj... as far as BC allows us)


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

Branch: refs/heads/2.3
Commit: f55f9d8919aa7fce3b88af37ac55ea04eb701033
Parents: eacd516
Author: ddekany <dd...@apache.org>
Authored: Mon Mar 19 00:24:15 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Mar 19 00:24:15 2018 +0100

----------------------------------------------------------------------
 src/main/javacc/FTL.jj                          | 64 +++++++++++---------
 .../core/InterpolationSyntaxTest.java           |  1 +
 2 files changed, 35 insertions(+), 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f55f9d89/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index d3be697..26f3368 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -116,29 +116,29 @@ public class FMParser {
      *            The template associated with this parser.
      * @param reader
      *            The character stream to use as input
-     * @param strictEscapeSyntax
+     * @param strictSyntaxMode
      *            Whether FreeMarker directives must start with a #
      *
      * @Deprecated This is an internal API of FreeMarker; will be removed in 2.4.
      */
-    public FMParser(Template template, Reader reader, boolean strictEscapeSyntax, boolean stripWhitespace) {
-        this(template, reader, strictEscapeSyntax, stripWhitespace, Configuration.AUTO_DETECT_TAG_SYNTAX);
+    public FMParser(Template template, Reader reader, boolean strictSyntaxMode, boolean stripWhitespace) {
+        this(template, reader, strictSyntaxMode, stripWhitespace, Configuration.AUTO_DETECT_TAG_SYNTAX);
     }
 
     /**
      * @Deprecated This is an internal API of FreeMarker; will be changed in 2.4.
      */
-    public FMParser(Template template, Reader reader, boolean strictEscapeSyntax, boolean stripWhitespace, int tagSyntax) {
-        this(template, reader, strictEscapeSyntax, stripWhitespace, tagSyntax,
+    public FMParser(Template template, Reader reader, boolean strictSyntaxMode, boolean stripWhitespace, int tagSyntax) {
+        this(template, reader, strictSyntaxMode, stripWhitespace, tagSyntax,
                 Configuration.PARSED_DEFAULT_INCOMPATIBLE_ENHANCEMENTS);
     }
 
     /**
      * @Deprecated This is an internal API of FreeMarker; will be changed in 2.4.
      */
-    public FMParser(Template template, Reader reader, boolean strictEscapeSyntax, boolean stripWhitespace,
+    public FMParser(Template template, Reader reader, boolean strictSyntaxMode, boolean stripWhitespace,
             int tagSyntax, int incompatibleImprovements) {
-        this(template, reader, strictEscapeSyntax, stripWhitespace,
+        this(template, reader, strictSyntaxMode, stripWhitespace,
                 tagSyntax, Configuration.AUTO_DETECT_NAMING_CONVENTION, incompatibleImprovements);
     }
 
@@ -240,7 +240,7 @@ public class FMParser {
 
         token_source.setParser(this);
 
-        token_source.strictEscapeSyntax = pCfg.getStrictSyntaxMode();
+        token_source.strictSyntaxMode = pCfg.getStrictSyntaxMode();
 
         int tagSyntax = pCfg.getTagSyntax();
         switch (tagSyntax) {
@@ -287,7 +287,7 @@ public class FMParser {
         token_source.initialNamingConvention = parentTokenSource.initialNamingConvention;
         token_source.namingConvention = parentTokenSource.namingConvention;
         token_source.namingConventionEstabilisher = parentTokenSource.namingConventionEstabilisher;
-        token_source.SwitchTo(NODIRECTIVE);
+        token_source.SwitchTo(NO_DIRECTIVE);
         
         this.outputFormat = outputFormat;
         recalculateAutoEscapingField();                                
@@ -621,11 +621,11 @@ TOKEN_MGR_DECLS:
      * Keeps track of how deeply nested we have the hash literals. This is necessary since we need to be able to
      * distinguish the } used to close a hash literal and the one used to close a ${
      */
-    private int hashLiteralNesting;
+    private int curlyBracketNesting;
     private int parenthesisNesting;
     private int bracketNesting;
     private boolean inFTLHeader;
-    boolean strictEscapeSyntax,
+    boolean strictSyntaxMode,
             squBracTagSyntax,
             autodetectTagSyntax,
             directiveSyntaxEstablished,
@@ -649,7 +649,7 @@ TOKEN_MGR_DECLS:
         
         // Non-strict syntax (deprecated) only supports legacy naming convention.
         // We didn't push this on the tokenizer because it made it slow, so we filter here.
-        if (!strictEscapeSyntax
+        if (!strictSyntaxMode
                 && (tokenNamingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION)
                 && !isStrictTag(image)) {
             tok.kind = STATIC_TEXT_NON_WS;
@@ -665,7 +665,7 @@ TOKEN_MGR_DECLS:
             return;
         }
         
-        if (!strictEscapeSyntax) {
+        if (!strictSyntaxMode) {
             // Legacy feature (or bug?): Tag syntax never gets estabilished in non-strict mode.
             // We do establilish the naming convention though.
             checkNamingConvention(tok, tokenNamingConvention);
@@ -824,22 +824,21 @@ TOKEN_MGR_DECLS:
         SwitchTo(FM_EXPRESSION);
     }
 
-    /**
-     * @param tok
-     *         Assumed to be an '}', or something that is the closing pair of another "mirror image" character.
-     */
-    private void endInterpolation(Token tok) {
+    private void endInterpolation(Token closingTk) {
         if (postInterpolationLexState == -1) {
-            char c = tok.image.charAt(0);
-            throw new TokenMgrError(
-                    "You can't have an \"" + c + "\" here, as there's nothing open that it could close.",
-                    TokenMgrError.LEXICAL_ERROR,
-                    tok.beginLine, tok.beginColumn,
-                    tok.endLine, tok.endColumn);
+            throw newUnexpectedClosingTokenException(closingTk);
         }
         SwitchTo(postInterpolationLexState);
         postInterpolationLexState = -1;
     }
+    
+    private TokenMgrError newUnexpectedClosingTokenException(Token closingTk) {
+            return new TokenMgrError(
+                    "You can't have an \"" + closingTk.image + "\" here, as there's nothing open that it could close.",
+                    TokenMgrError.LEXICAL_ERROR,
+                    closingTk.beginLine, closingTk.beginColumn,
+                    closingTk.endLine, closingTk.endColumn);
+    }
 
     private void eatNewline() {
         int charsRead = 0;
@@ -1095,7 +1094,7 @@ TOKEN:
                 matchedToken.kind = STATIC_TEXT_NON_WS;
             } else if (firstChar == '[' && !squBracTagSyntax) {
                 matchedToken.kind = STATIC_TEXT_NON_WS;
-            } else if (strictEscapeSyntax) {
+            } else if (strictSyntaxMode) {
                 String dn = matchedToken.image;
                 int index = dn.indexOf('#');
                 dn = dn.substring(index + 1);
@@ -1149,7 +1148,7 @@ TOKEN:
     }
 }
 
-<DEFAULT, NODIRECTIVE> TOKEN :
+<DEFAULT, NO_DIRECTIVE> TOKEN :
 {
     <STATIC_TEXT_WS : ("\n" | "\r" | "\t" | " ")+>
     |
@@ -1330,13 +1329,18 @@ TOKEN:
     |
     <OPENING_CURLY_BRACKET : "{">
     {
-        ++hashLiteralNesting;
+        ++curlyBracketNesting;
     }
     |
     <CLOSING_CURLY_BRACKET : "}">
     {
-        if (hashLiteralNesting == 0) endInterpolation(matchedToken);
-        else --hashLiteralNesting;
+        if (curlyBracketNesting > 0) {
+            --curlyBracketNesting;
+        } else if (interpolationSyntax != SQUARE_BRACKET_INTERPOLATION_SYNTAX) {
+            endInterpolation(matchedToken);
+        } else {
+            throw newUnexpectedClosingTokenException(matchedToken);
+        }
     }
     |
     <IN : "in">
@@ -4341,7 +4345,7 @@ void HeaderElement() :
                         } else if (ks.equalsIgnoreCase("STRIP_TEXT") || ks.equals("stripText")) {
                             this.stripText = getBoolean(exp, true);
                         } else if (ks.equalsIgnoreCase("STRICT_SYNTAX") || ks.equals("strictSyntax")) {
-                            this.token_source.strictEscapeSyntax = getBoolean(exp, true);
+                            this.token_source.strictSyntaxMode = getBoolean(exp, true);
                         } else if (ks.equalsIgnoreCase("auto_esc") || ks.equals("autoEsc")) {
                             if (getBoolean(exp, false)) {
                                 autoEscRequester = key;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f55f9d89/src/test/java/freemarker/core/InterpolationSyntaxTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/InterpolationSyntaxTest.java b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
index 8c57d36..2322d99 100644
--- a/src/test/java/freemarker/core/InterpolationSyntaxTest.java
+++ b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
@@ -78,6 +78,7 @@ public class InterpolationSyntaxTest extends TemplateTest {
         
         assertErrorContains("[=", "end of file");
         assertErrorContains("[=1", "unclosed \"[\"");
+        assertErrorContains("[=1}", "\"}\"", "open");
         
         assertOutput("[='[\\=1]']", "[=1]");
         assertOutput("[='[\\=1][=2]']", "12"); // Usual legacy interpolation escaping glitch...


[09/12] incubator-freemarker git commit: Cleanup related to [=...], also some missing functionality added.

Posted by dd...@apache.org.
Cleanup related to [=...], also some missing functionality added.


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

Branch: refs/heads/2.3
Commit: eacd51689fb301d4d128bc26dd9e96781474fd89
Parents: df4dc52
Author: ddekany <dd...@apache.org>
Authored: Sun Mar 18 18:37:23 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Mar 18 18:37:23 2018 +0100

----------------------------------------------------------------------
 .../java/freemarker/core/DollarVariable.java    | 11 ++++--
 .../java/freemarker/core/NumericalOutput.java   |  5 ++-
 .../java/freemarker/core/StringLiteral.java     |  6 +--
 .../ext/beans/OverloadedNumberUtil.java         |  2 +-
 .../java/freemarker/template/Configuration.java |  3 +-
 src/main/java/freemarker/template/Template.java | 22 +++++++++++
 src/main/javacc/FTL.jj                          | 41 ++++++++------------
 .../core/InterpolationSyntaxTest.java           |  8 ++++
 8 files changed, 64 insertions(+), 34 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eacd5168/src/main/java/freemarker/core/DollarVariable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/DollarVariable.java b/src/main/java/freemarker/core/DollarVariable.java
index 7e7bd1f..12fa02a 100644
--- a/src/main/java/freemarker/core/DollarVariable.java
+++ b/src/main/java/freemarker/core/DollarVariable.java
@@ -22,11 +22,15 @@ package freemarker.core;
 import java.io.IOException;
 import java.io.Writer;
 
+import freemarker.template.Configuration;
 import freemarker.template.TemplateException;
 import freemarker.template.utility.StringUtil;
 
 /**
- * An instruction that outputs the value of an <tt>Expression</tt>.
+ * An interpolation like <code>${exp}</code> or {@code [=exp]}. The class name is the remnant of old times, but as
+ * some users are using the package-visible AST API, it wasn't renamed.
+ * 
+ * @see NumericalOutput
  */
 final class DollarVariable extends Interpolation {
 
@@ -99,10 +103,11 @@ final class DollarVariable extends Interpolation {
     @Override
     protected String dump(boolean canonical, boolean inStringLiteral) {
         StringBuilder sb = new StringBuilder();
-        sb.append("${");
+        int syntax = getTemplate().getInterpolationSyntax();
+        sb.append(syntax != Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX ? "${" : "[=");
         final String exprCF = expression.getCanonicalForm();
         sb.append(inStringLiteral ? StringUtil.FTLStringLiteralEnc(exprCF, '"') : exprCF);
-        sb.append("}");
+        sb.append(syntax != Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX ? "}" : "]");
         if (!canonical && expression != escapedExpression) {
             sb.append(" auto-escaped");            
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eacd5168/src/main/java/freemarker/core/NumericalOutput.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/NumericalOutput.java b/src/main/java/freemarker/core/NumericalOutput.java
index 08e8301..c897bd1 100644
--- a/src/main/java/freemarker/core/NumericalOutput.java
+++ b/src/main/java/freemarker/core/NumericalOutput.java
@@ -28,7 +28,10 @@ import freemarker.template.TemplateException;
 import freemarker.template.utility.StringUtil;
 
 /**
- * An instruction that outputs the value of a numerical expression.
+ * An interpolation like <code>#{numericalExp; format}</code>; it's deprecated, but still supported. The class name is
+ * the remnant of old times, but as some users are using the package-visible AST API, it wasn't renamed.
+ * 
+ * @see DollarVariable
  */
 final class NumericalOutput extends Interpolation {
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eacd5168/src/main/java/freemarker/core/StringLiteral.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/StringLiteral.java b/src/main/java/freemarker/core/StringLiteral.java
index 6d53c17..c116498 100644
--- a/src/main/java/freemarker/core/StringLiteral.java
+++ b/src/main/java/freemarker/core/StringLiteral.java
@@ -54,9 +54,9 @@ final class StringLiteral extends Expression implements TemplateScalarModel {
         if (value.length() > 3 && (
                     (intSyn == Configuration.LEGACY_INTERPOLATION_SYNTAX
                         || intSyn == Configuration.DOLLAR_INTERPOLATION_SYNTAX) 
-                        && (value.indexOf("${") >= 0
-                    || intSyn == Configuration.LEGACY_INTERPOLATION_SYNTAX && value.indexOf("#{") >= 0)
-                    || intSyn == Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX && value.indexOf("[=") >= 0)) {
+                        && (value.indexOf("${") != -1
+                    || intSyn == Configuration.LEGACY_INTERPOLATION_SYNTAX && value.indexOf("#{") != -1)
+                    || intSyn == Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX && value.indexOf("[=") != -1)) {
             try {
                 SimpleCharStream simpleCharacterStream = new SimpleCharStream(
                         new StringReader(value),

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eacd5168/src/main/java/freemarker/ext/beans/OverloadedNumberUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/beans/OverloadedNumberUtil.java b/src/main/java/freemarker/ext/beans/OverloadedNumberUtil.java
index 352d1de..fbb2fc0 100644
--- a/src/main/java/freemarker/ext/beans/OverloadedNumberUtil.java
+++ b/src/main/java/freemarker/ext/beans/OverloadedNumberUtil.java
@@ -89,7 +89,7 @@ class OverloadedNumberUtil {
      * @param num the number to coerce
      * @param typeFlags the type flags of the target parameter position; see {@link TypeFlags}
      * 
-     * @returns The original number or a {@link NumberWithFallbackType}, depending on the actual value and the types
+     * @return The original number or a {@link NumberWithFallbackType}, depending on the actual value and the types
      *     indicated in the {@code targetNumTypes} parameter.
      */
     static Number addFallbackType(final Number num, final int typeFlags) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eacd5168/src/main/java/freemarker/template/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 53fcd49..3114747 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -2409,8 +2409,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     }
 
     /**
-     * Determines the interpolation syntax (like <code>${x}</code> VS <code>[=x]</code>) of the template files in which
-     * there's no {@code #ftl} hader with {@code interpolation_syntax} parameter. The
+     * Determines the interpolation syntax (like <code>${x}</code> VS <code>[=x]</code>) of the template files. The
      * {@code interpolationSyntax} parameter must be one of {@link Configuration#LEGACY_INTERPOLATION_SYNTAX},
      * {@link Configuration#DOLLAR_INTERPOLATION_SYNTAX}, and {@link Configuration#SQUARE_BRACKET_INTERPOLATION_SYNTAX}.
      * 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eacd5168/src/main/java/freemarker/template/Template.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Template.java b/src/main/java/freemarker/template/Template.java
index c95be5e..f21fffc 100644
--- a/src/main/java/freemarker/template/Template.java
+++ b/src/main/java/freemarker/template/Template.java
@@ -87,6 +87,7 @@ public class Template extends Configurable {
     private TemplateElement rootElement;
     private String encoding, defaultNS;
     private Object customLookupCondition;
+    private int interpolationSyntax;
     private int actualTagSyntax;
     private int actualNamingConvention;
     private boolean autoEscaping;
@@ -258,6 +259,7 @@ public class Template extends Configurable {
                     rootElement = null;
                 }
                 this.actualTagSyntax = parser._getLastTagSyntax();
+                this.interpolationSyntax = actualParserConfiguration.getInterpolationSyntax();
                 this.actualNamingConvention = parser._getLastNamingConvention();
             } catch (TokenMgrError exc) {
                 // TokenMgrError VS ParseException is not an interesting difference for the user, so we just convert it
@@ -640,6 +642,8 @@ public class Template extends Configurable {
      * returns whatever the default is in the current configuration, so it's maybe
      * {@link Configuration#AUTO_DETECT_TAG_SYNTAX}.
      * 
+     * @see Configuration#setTagSyntax(int)
+     * 
      * @since 2.3.20
      */
     public int getActualTagSyntax() {
@@ -647,12 +651,30 @@ public class Template extends Configurable {
     }
     
     /**
+     * Returns the interpolation syntax the parser has used for this template. Because the interpolation syntax is
+     * never auto-detected, it's not called "getActualInterpolationSyntax" (unlike {@link #getActualTagSyntax()}).
+     * 
+     * @return A constant like {@link Configuration#LEGACY_INTERPOLATION_SYNTAX},
+     *          {@link Configuration#DOLLAR_INTERPOLATION_SYNTAX}, or
+     *          {@link Configuration#SQUARE_BRACKET_INTERPOLATION_SYNTAX}.
+     * 
+     * @see Configuration#setInterpolationSyntax(int)
+     * 
+     * @since 2.3.28
+     */
+    public int getInterpolationSyntax() {
+        return interpolationSyntax;
+    }
+    
+    /**
      * Returns the naming convention the parser has chosen for this template. If it could be determined, it's
      * {@link Configuration#LEGACY_NAMING_CONVENTION} or {@link Configuration#CAMEL_CASE_NAMING_CONVENTION}. If it
      * couldn't be determined (like because there no identifier that's part of the template language was used where
      * the naming convention matters), this returns whatever the default is in the current configuration, so it's maybe
      * {@link Configuration#AUTO_DETECT_TAG_SYNTAX}.
      * 
+     * @see Configuration#setNamingConvention(int)
+     * 
      * @since 2.3.23
      */
     public int getActualNamingConvention() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eacd5168/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index 4134b70..d3be697 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -1194,7 +1194,7 @@ TOKEN:
     <#ESCAPED_CHAR :
         "\\"
         (
-            ("n" | "t" | "r" | "f" | "b" | "g" | "l" | "a" | "\\" | "'" | "\"" | "$" | "{" | "=")
+            ("n" | "t" | "r" | "f" | "b" | "g" | "l" | "a" | "\\" | "'" | "\"" | "{" | "=")
             |
             ("x" ["0"-"9", "A"-"F", "a"-"f"])
         )
@@ -2329,11 +2329,11 @@ StringLiteral StringLiteral(boolean interpolate) :
             int interpolationSyntax = pCfg.getInterpolationSyntax();
             if ((interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX
                     || interpolationSyntax == DOLLAR_INTERPOLATION_SYNTAX)
-	                    && t.image.indexOf("${") >= 0
+	                    && t.image.indexOf("${") != -1
 	                || interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX
-	                    && t.image.indexOf("#{") >= 0
+	                    && t.image.indexOf("#{") != -1
 	                || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX
-	                    && t.image.indexOf("[=") >= 0) {
+	                    && t.image.indexOf("[=") != -1) {
                 result.parseValue(this, outputFormat);
             }
         }
@@ -2400,7 +2400,7 @@ HashLiteral HashLiteral() :
 /**
  * A production representing the ${...} or [=...] that outputs a variable.
  */
-DollarVariable StringOutput() :
+DollarVariable NormalInterpolation() :
 {
     Expression exp;
     Token begin, end;
@@ -2410,26 +2410,19 @@ DollarVariable StringOutput() :
 	    (
 	        begin = <DOLLAR_INTERPOLATION_OPENING>
 	        exp = Expression()
-	            {
-	                notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
-	                notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
-	            }
-	
 	        end = <CLOSING_CURLY_BRACKET>
 	    )
 	    |
 	    (
 	        begin = <SQUARE_BRACKET_INTERPOLATION_OPENING>
 	        exp = Expression()
-	            {
-	                notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
-	                notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
-	            }
-	
 	        end = <CLOSE_BRACKET>
 	    )
     )
     {
+        notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
+        notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC);
+                    
         DollarVariable result = new DollarVariable(
                 exp, escapedExpression(exp),
                 outputFormat,
@@ -2439,7 +2432,7 @@ DollarVariable StringOutput() :
     }
 }
 
-NumericalOutput NumericalOutput() :
+NumericalOutput NumericalInterpolation() :
 {
     Expression exp;
     Token fmt = null, begin, end;
@@ -4140,9 +4133,9 @@ TemplateElements MixedContentElements() :
         (
             elem = PCData()
             |
-            elem = StringOutput()
+            elem = NormalInterpolation()
             |
-            elem = NumericalOutput()
+            elem = NumericalInterpolation()
             |
             elem = FreemarkerDirective()
         )
@@ -4186,9 +4179,9 @@ MixedContent MixedContent() :
         (
             elem = PCData()
             |
-            elem = StringOutput()
+            elem = NormalInterpolation()
             |
-            elem = NumericalOutput()
+            elem = NumericalInterpolation()
             |
             elem = FreemarkerDirective()
         )
@@ -4243,9 +4236,9 @@ TemplateElement FreeMarkerText() :
         (
             elem = PCData()
             |
-            elem = StringOutput()
+            elem = NormalInterpolation()
             |
-            elem = NumericalOutput()
+            elem = NumericalInterpolation()
         )
         {
             if (begin == null) {
@@ -4495,12 +4488,12 @@ List<Object> StaticTextAndInterpolations() :
 	    (
 	        LOOKAHEAD(<DOLLAR_INTERPOLATION_OPENING>|<SQUARE_BRACKET_INTERPOLATION_OPENING>)
 		    (
-		        interpolation = StringOutput()
+		        interpolation = NormalInterpolation()
 	        )
 		    |
             LOOKAHEAD(<HASH_INTERPOLATION_OPENING>)
 		    (
-                interpolation = NumericalOutput()
+                interpolation = NumericalInterpolation()
 		    )
 	    )
 	    {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eacd5168/src/test/java/freemarker/core/InterpolationSyntaxTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/InterpolationSyntaxTest.java b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
index adea7c9..8c57d36 100644
--- a/src/test/java/freemarker/core/InterpolationSyntaxTest.java
+++ b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
@@ -1,9 +1,13 @@
 package freemarker.core;
 
 import static freemarker.template.Configuration.*;
+import static org.junit.Assert.*;
+
+import java.io.StringWriter;
 
 import org.junit.Test;
 
+import freemarker.template.Template;
 import freemarker.test.TemplateTest;
 
 public class InterpolationSyntaxTest extends TemplateTest {
@@ -78,6 +82,10 @@ public class InterpolationSyntaxTest extends TemplateTest {
         assertOutput("[='[\\=1]']", "[=1]");
         assertOutput("[='[\\=1][=2]']", "12"); // Usual legacy interpolation escaping glitch...
         assertOutput("[=r'[=1]']", "[=1]");
+        
+        StringWriter sw = new StringWriter();
+        new Template(null, "[= 1 + '[= 2 ]' ]", getConfiguration()).dump(sw);
+        assertEquals("[=1 + \"[=2]\"]", sw.toString());
     }
 
     @Test


[02/12] incubator-freemarker git commit: Made it more clear that using Configuration.getVersion() for the value of the incompatibleImprovements setting is bad idea.

Posted by dd...@apache.org.
Made it more clear that using Configuration.getVersion() for the value of the incompatibleImprovements setting is bad idea.


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

Branch: refs/heads/2.3
Commit: 40ced2d45fcf92723e6db386e13df9161d5a4608
Parents: 1261610
Author: ddekany <dd...@apache.org>
Authored: Tue Mar 13 08:11:25 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Tue Mar 13 08:11:25 2018 +0100

----------------------------------------------------------------------
 .../java/freemarker/template/Configuration.java | 19 +++++++---
 src/manual/en_US/book.xml                       | 37 ++++++++++++++------
 2 files changed, 41 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/40ced2d4/src/main/java/freemarker/template/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 3673272..5f35435 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -547,8 +547,8 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      * <p><b>About the "incompatible improvements" setting</b>
      *
      * <p>This setting value is the FreeMarker version number where the not 100% backward compatible bug fixes and
-     * improvements that you want to enable were already implemented. In new projects you should set this to the
-     * version of FreeMarker that you start the development with. In older projects it's also usually better to keep
+     * improvements that you want to enable were already implemented. In new projects you should set this to the fixed
+     * FreeMarker version that you start the development with. In older projects it's also usually better to keep
      * this high, however you should check the changes activated (find them below), especially if not only the 3rd
      * version number (the micro version) of {@code incompatibleImprovements} is increased. Generally, as far as you
      * only increase the last version number of this setting, the changes are low risk. The default value is 2.3.0 to
@@ -557,6 +557,9 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      * <p>Bugfixes and improvements that are fully backward compatible, also those that are important security fixes,
      * are enabled regardless of the incompatible improvements setting.
      * 
+     * <p>Do NOT ever use {@link #getVersion()} to set the "incompatible improvements". Always use a fixed value, like
+     * {@link #VERSION_2_3_28}. Otherwise your application can break as you upgrade FreeMarker. 
+     * 
      * <p>An important consequence of setting this setting is that now your application will check if the stated minimum
      * FreeMarker version requirement is met. Like if you set this setting to 2.3.22, but accidentally the application
      * is deployed with FreeMarker 2.3.21, then FreeMarker will fail, telling that a higher version is required. After
@@ -1865,7 +1868,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
 
     /**
      * Use {@link #Configuration(Version)} instead if possible; see the meaning of the parameter there.
-     * If the default value of a setting depends on the {@code incompatibleImprovements} and the value of that setting
+     * 
+     * <p>Do NOT ever use {@link #getVersion()} to set the "incompatible improvements". Always use a fixed value, like
+     * {@link #VERSION_2_3_28}. Otherwise your application can break as you upgrade FreeMarker. 
+     * 
+     * <p>If the default value of a setting depends on the {@code incompatibleImprovements} and the value of that setting
      * was never set in this {@link Configuration} object through the public API, its value will be set to the default
      * value appropriate for the new {@code incompatibleImprovements}. (This adjustment of a setting value doesn't
      * count as setting that setting, so setting {@code incompatibleImprovements} for multiple times also works as
@@ -3393,7 +3400,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     }
     
     /**
-     * Returns the FreeMarker version information, most importantly the major.minor.micro version numbers.
+     * Returns FreeMarker version information, most importantly the major.minor.micro version numbers;
+     * do NOT use this as the value of the {@code incompatible_improvements} setting (as the parameter to
+     * {@link Configuration#Configuration(Version)}), as then your application can break when you upgrade FreeMarker!
+     * Use a constant value, like {@link #VERSION_2_3_28}, to protect your application from fixes/changes that aren't
+     * entirely backward compatible. Fixes and features that are backward compatible are always enabled. 
      * 
      * On FreeMarker version numbering rules:
      * <ul>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/40ced2d4/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 3bd2562..3d8e321 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -10043,17 +10043,21 @@ cfg.setNumberFormat("@ua 0.####;; roundingMode=halfUp");</programlisting>
           implemented. Usually, it's a bad idea to left it on its default,
           which is 2.3.0 (maximum backward compatibility).</para>
 
-          <para>In new projects you should set this to the FreeMarker version
-          that you are actually using. In older projects it's also usually
-          better to keep this high, however you better check the changes
-          activated (find them in <link
+          <para>In new projects you should set this to the fixed FreeMarker
+          version (like <literal>Configuration.VERSION_2_3_28</literal>) that
+          you are actually using when starting the project. In older projects
+          it's also usually better to keep this high , however you should
+          check the changes activated (find them in <link
           xlink:href="https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#Configuration-freemarker.template.Version-">the
           API JavaDoc of the <literal>Configuration(Version)</literal>
-          constructor</link>), at least if not only the 3rd version number
+          constructor</link>), especially if not only the 3rd version number
           (the micro version) of <quote>incompatible improvements</quote>
           setting is increased. Generally, as far as you only increase the
-          last version number of this setting, the changes are low
-          risk.</para>
+          last (3rd) version number of this setting, the changes are low risk,
+          and whether you can afford that risk depends on the concrete
+          application. Never use a dynamic value like
+          <literal>Configuration.getVersion()</literal> though, as that way
+          upgrading FreeMarker can break your application!</para>
 
           <para>Bug fixes and improvements that are fully backward compatible,
           also those that are important security fixes, are enabled regardless
@@ -10081,14 +10085,14 @@ cfg.setNumberFormat("@ua 0.####;; roundingMode=halfUp");</programlisting>
               <literal>freemarker.template.Configuration</literal> object
               like:</para>
 
-              <programlisting role="unspecified">... = new Configuration(Configuration.VERSION_2_3_27)</programlisting>
+              <programlisting role="unspecified">... = new Configuration(Configuration.VERSION_2_3_28)</programlisting>
             </listitem>
 
             <listitem>
               <para>Or, alter the <literal>Configuration</literal> singleton
               where you initialize its other settings like:</para>
 
-              <programlisting role="unspecified">cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_27)</programlisting>
+              <programlisting role="unspecified">cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_28)</programlisting>
             </listitem>
 
             <listitem>
@@ -10096,7 +10100,7 @@ cfg.setNumberFormat("@ua 0.####;; roundingMode=halfUp");</programlisting>
               (<literal>*.properties</literal> file or
               <literal>java.util.Properties</literal> object), add:</para>
 
-              <programlisting role="unspecified">incompatible_improvements=2.3.27</programlisting>
+              <programlisting role="unspecified">incompatible_improvements=2.3.28</programlisting>
             </listitem>
 
             <listitem>
@@ -10107,7 +10111,7 @@ cfg.setNumberFormat("@ua 0.####;; roundingMode=halfUp");</programlisting>
 
               <programlisting role="unspecified">&lt;init-param&gt;
     &lt;param-name&gt;incompatible_improvements&lt;/param-name&gt;
-    &lt;param-value&gt;2.3.27&lt;/param-value&gt;
+    &lt;param-value&gt;2.3.28&lt;/param-value&gt;
 &lt;/init-param&gt;</programlisting>
             </listitem>
           </itemizedlist>
@@ -10139,6 +10143,17 @@ cfg.setNumberFormat("@ua 0.####;; roundingMode=halfUp");</programlisting>
           <literal>DefaultObjectWrapper</literal> (for
           <literal>BeansWrapper</literal> it's the same, only with different
           class name of course).</para>
+
+          <warning>
+            <para>Do not ever use
+            <literal>Configuration.getVersion()</literal> to set the
+            <quote>incompatible improvements</quote> setting. Always use a
+            fixed value, like <literal>Configuration.VERSION_2_3_28</literal>.
+            Otherwise your application can break as you upgrade FreeMarker.
+            The whole point of <quote>incompatible improvements</quote> is to
+            protect you from that, while you still always get the
+            fixes/improvements that are backward compatible.</para>
+          </warning>
         </section>
       </section>
     </chapter>


[08/12] incubator-freemarker git commit: In string literals, \= is now a valid escape sequence, resulting in a =. This is useful when you are using the new [=exp] interpolation syntax, which can be escaped in a string literal like "Literal [\=x]".

Posted by dd...@apache.org.
In string literals, \= is now a valid escape sequence, resulting in a =. This is useful when you are using the new [=exp] interpolation syntax, which can be escaped in a string literal like "Literal [\=x]".

(Also improved [=...] related documentation and test a bit.)


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

Branch: refs/heads/2.3
Commit: df4dc52f2f1799299a9455ac016dfecaecf9004b
Parents: aeaafe5
Author: ddekany <dd...@apache.org>
Authored: Fri Mar 16 23:58:29 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Fri Mar 16 23:58:29 2018 +0100

----------------------------------------------------------------------
 .../freemarker/template/utility/StringUtil.java | 22 +++++--
 src/main/javacc/FTL.jj                          |  2 +-
 src/manual/en_US/book.xml                       | 67 +++++++++++++++++---
 .../core/InterpolationSyntaxTest.java           |  7 ++
 .../template/utility/StringUtilTest.java        | 24 +++++++
 5 files changed, 108 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/df4dc52f/src/main/java/freemarker/template/utility/StringUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/utility/StringUtil.java b/src/main/java/freemarker/template/utility/StringUtil.java
index 33fe03b..a5156aa 100644
--- a/src/main/java/freemarker/template/utility/StringUtil.java
+++ b/src/main/java/freemarker/template/utility/StringUtil.java
@@ -38,6 +38,10 @@ import freemarker.template.Version;
  */
 public class StringUtil {
     
+    /**
+     *  Used to look up if the chars with low code needs to be escaped, but note that it gives bad result for '=', as
+     *  there the it matters if it's after '['.
+     */
     private static final char[] ESCAPES = createEscapes();
     
     private static final char[] LT = new char[] { '&', 'l', 't', ';' };
@@ -426,6 +430,7 @@ public class StringUtil {
         escapes['\''] = '\'';
         escapes['"'] = '"';
         escapes['<'] = 'l';
+        // As '=' is only escaped if it's after '[', we can't handle it here
         escapes['>'] = 'g';
         escapes['&'] = 'a';
         escapes['\b'] = 'b';
@@ -480,10 +485,16 @@ public class StringUtil {
         StringBuilder buf = null;
         for (int i = 0; i < ln; i++) {
             char c = s.charAt(i);
-            char escape =
-                    c < escLn ? ESCAPES[c] :
-                    c == '{' && i > 0 && isInterpolationStart(s.charAt(i - 1)) ? '{' :
-                    0;
+            char escape;
+            if (c == '=') {
+                escape = i > 0 && s.charAt(i - 1) == '[' ? '=' : 0;
+            } else if (c < escLn) {
+                escape = ESCAPES[c]; //
+            } else if (c == '{' && i > 0 && isInterpolationStart(s.charAt(i - 1))) {
+                escape = '{';
+            } else {
+                escape = 0;
+            }
             if (escape == 0 || escape == otherQuotation) {
                 if (buf != null) {
                     buf.append(c);
@@ -605,7 +616,8 @@ public class StringUtil {
                     bidx = idx + 2;
                     break;
                 case '{':
-                    buf.append('{');
+                case '=':
+                    buf.append(c);
                     bidx = idx + 2;
                     break;
                 case 'x': {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/df4dc52f/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index f1fa595..4134b70 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -1194,7 +1194,7 @@ TOKEN:
     <#ESCAPED_CHAR :
         "\\"
         (
-            ("n" | "t" | "r" | "f" | "b" | "g" | "l" | "a" | "\\" | "'" | "\"" | "$" | "{")
+            ("n" | "t" | "r" | "f" | "b" | "g" | "l" | "a" | "\\" | "'" | "\"" | "$" | "{" | "=")
             |
             ("x" ["0"-"9", "A"-"F", "a"-"f"])
         )

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/df4dc52f/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index cbda1a8..ac58a2a 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -2276,6 +2276,13 @@ this is a backslash: \</programlisting>
                 </tr>
 
                 <tr>
+                  <td><literal>\=</literal></td>
+
+                  <td>Equals character: <literal>=</literal> (Supported since
+                  FreeMarker 2.3.28.)</td>
+                </tr>
+
+                <tr>
                   <td><literal>\\</literal></td>
 
                   <td>Back slash (u005C)</td>
@@ -2349,15 +2356,20 @@ this is a backslash: \</programlisting>
             digit, you must use all 4 digits or else FreeMarker will
             misunderstand you.</para>
 
-            <para>Note that the character sequence <literal>${</literal> (and
-            <literal>#{</literal>) has special meaning. It's used to insert
-            the value of expressions (typically: the value of variables, as in
-            <literal>"Hello ${user}!"</literal>). This will be explained <link
+            <para>Note that the character sequence <literal>${</literal> and
+            <literal>#{</literal> (and rarely <literal>[=</literal> instead,
+            depending on <link linkend="dgui_misc_alternativesyntax">the
+            configured syntax</link>) has special meaning. They are used to
+            insert the value of expressions (typically: the value of
+            variables, as in <literal>"Hello ${user}!"</literal>). This will
+            be explained <link
             linkend="dgui_template_exp_stringop_interpolation">later</link>.
             If you want to print <literal>${</literal> or
-            <literal>#{</literal>, you should either use raw string literals
-            as explained below, or escape the <literal>{</literal> like in
-            <literal>"foo $\{bar}"</literal>.</para>
+            <literal>#{</literal> (or <literal>[=</literal>), you should
+            either use raw string literals as explained below, or escape the
+            <literal>{</literal> like in <literal>"foo $\{bar}"</literal> (or
+            the <literal>=</literal> like in <literal>"foo
+            [\=bar]"</literal>).</para>
 
             <indexterm>
               <primary>raw string literal</primary>
@@ -2839,6 +2851,14 @@ baz
             sections</link> (so it goes through the same <emphasis>locale
             sensitive</emphasis> number and date/time formatting).</para>
 
+            <note>
+              <para>It's possible to configure FreeMarker's interpolation
+              syntax to use
+              <literal>[=<replaceable>...</replaceable>]</literal> instead;
+              <link linkend="dgui_misc_alternativesyntax_interpolation">see
+              here</link>.</para>
+            </note>
+
             <para>Example (assume that user is <quote>Big Joe</quote>):</para>
 
             <programlisting role="template">&lt;#assign s = "Hello ${user}!"&gt;
@@ -4200,7 +4220,7 @@ ${("green " + "mouse")?upper_case}  &lt;#-- GREEN MOUSE --&gt;
           kind of expression (e.g. <literal>${100 + x}</literal>).</para>
 
           <note>
-            <para>Actually, FreeMarker can be configured to use
+            <para>FreeMarker can be configured to use
             <literal>[=<replaceable>expression</replaceable>]</literal> syntax
             instead. <link linkend="dgui_misc_alternativesyntax">See more
             about alternative syntaxes...</link></para>
@@ -27559,6 +27579,37 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>The template language can now be configured to use
+              <literal>[=<replaceable>expression</replaceable>]</literal>
+              instead of
+              <literal>${<replaceable>expression</replaceable>}</literal> and
+              <literal>#{<replaceable>expression</replaceable>}</literal>,
+              which is very useful if you have a lot of
+              <literal>${<replaceable>...</replaceable>}</literal> or
+              <literal>#{<replaceable>...</replaceable>}</literal> in the text
+              that you are generating, and so they should be static text. See
+              <link linkend="dgui_misc_alternativesyntax_interpolation">more
+              about the square bracket interpolation syntax here.</link> The
+              template language can also be configured to only use
+              <literal>${<replaceable>expression</replaceable>}</literal>, and
+              treat
+              <literal>#{<replaceable>expression</replaceable>}</literal> as
+              static text. (See the <literal>interpolation_syntax</literal>
+              configuration setting, or the
+              <literal>Configuration.setInterpolationSyntax(int)</literal>
+              method.)</para>
+            </listitem>
+
+            <listitem>
+              <para>In string literals, <literal>\=</literal> is now a valid
+              escape sequence, resulting in a <literal>=</literal>. This is
+              useful when you are using the new
+              <literal>[=<replaceable>exp</replaceable>]</literal>
+              interpolation syntax, which can be escaped in a string literal
+              like <literal>"Literal [\=x]"</literal>.</para>
+            </listitem>
+
+            <listitem>
               <para>Bug fixed (<link
               xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-83">FREEMARKER-83</link>);
               this fix is only active when <link

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/df4dc52f/src/test/java/freemarker/core/InterpolationSyntaxTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/InterpolationSyntaxTest.java b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
index c1721c8..adea7c9 100644
--- a/src/test/java/freemarker/core/InterpolationSyntaxTest.java
+++ b/src/test/java/freemarker/core/InterpolationSyntaxTest.java
@@ -71,6 +71,13 @@ public class InterpolationSyntaxTest extends TemplateTest {
         
         assertOutput("<@r'${1} #{1} [=1]'?interpret />", "${1} #{1} 1");
         assertOutput("[='\"${1} #{1} [=1]\"'?eval]", "${1} #{1} 1");
+        
+        assertErrorContains("[=", "end of file");
+        assertErrorContains("[=1", "unclosed \"[\"");
+        
+        assertOutput("[='[\\=1]']", "[=1]");
+        assertOutput("[='[\\=1][=2]']", "12"); // Usual legacy interpolation escaping glitch...
+        assertOutput("[=r'[=1]']", "[=1]");
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/df4dc52f/src/test/java/freemarker/template/utility/StringUtilTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/template/utility/StringUtilTest.java b/src/test/java/freemarker/template/utility/StringUtilTest.java
index 49556e2..557c5dc 100644
--- a/src/test/java/freemarker/template/utility/StringUtilTest.java
+++ b/src/test/java/freemarker/template/utility/StringUtilTest.java
@@ -19,6 +19,7 @@
 
 package freemarker.template.utility;
 
+import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
@@ -28,6 +29,8 @@ import java.util.regex.Pattern;
 import org.hamcrest.Matchers;
 import org.junit.Test;
 
+import freemarker.core.ParseException;
+
 public class StringUtilTest {
 
     @Test
@@ -200,6 +203,27 @@ public class StringUtilTest {
         assertEquals("a\\'c\"d", StringUtil.FTLStringLiteralEnc("a'c\"d", '\''));
         assertEquals("\\n\\r\\t\\f\\x0002\\\\", StringUtil.FTLStringLiteralEnc("\n\r\t\f\u0002\\"));
         assertEquals("\\l\\g\\a", StringUtil.FTLStringLiteralEnc("<>&"));
+        assertEquals("=[\\=]=", StringUtil.FTLStringLiteralEnc("=[=]="));
+        assertEquals("[\\=", StringUtil.FTLStringLiteralEnc("[="));
+    }
+
+    @Test
+    public void testFTLStringLiteralDec() throws ParseException {
+        assertEquals("", StringUtil.FTLStringLiteralDec(""));
+        assertEquals("x", StringUtil.FTLStringLiteralDec("x"));
+        assertEquals("\nq", StringUtil.FTLStringLiteralDec("\\x0Aq"));
+        assertEquals("\n\r1", StringUtil.FTLStringLiteralDec("\\x0A\\x000D1"));
+        assertEquals("\n\r\t", StringUtil.FTLStringLiteralDec("\\n\\r\\t"));
+        assertEquals("${1}#{2}[=3]", StringUtil.FTLStringLiteralDec("$\\{1}#\\{2}[\\=3]"));
+        assertEquals("{=", StringUtil.FTLStringLiteralDec("\\{\\="));
+        assertEquals("\\=", StringUtil.FTLStringLiteralDec("\\\\="));
+           
+        try {
+            StringUtil.FTLStringLiteralDec("\\[");
+            fail();
+        } catch (ParseException e) {
+            assertThat(e.getMessage(), containsString("\\["));
+        }
     }
     
     @Test