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 2017/07/02 17:10:05 UTC

[1/2] incubator-freemarker git commit: You can't close #attempt/#recover with `` anymore, only with `<#attempt>`. This was the standard form in FM2 as well, and is consistent with how #if/#else works. (The template converter tool does this con

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 507b89bf5 -> 1f086e3f2


You can't close #attempt/#recover with `</#recover>` anymore, only with `<#attempt>`. This was the standard form in FM2 as well, and is consistent with how #if/#else works. (The template converter tool does this conversion.)


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

Branch: refs/heads/3
Commit: af17c347fa817d5ce8b826e245e64b740e2d82d0
Parents: 507b89b
Author: ddekany <dd...@apache.org>
Authored: Sun Jul 2 19:09:30 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sun Jul 2 19:09:30 2017 +0200

----------------------------------------------------------------------
 FM3-CHANGE-LOG.txt                                           | 5 ++++-
 .../freemarker/core/templatesuite/templates/recover.ftl      | 8 ++++----
 freemarker-core/src/main/javacc/FTL.jj                       | 6 +-----
 3 files changed, 9 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/af17c347/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/FM3-CHANGE-LOG.txt b/FM3-CHANGE-LOG.txt
index ba175ee..6a80de0 100644
--- a/FM3-CHANGE-LOG.txt
+++ b/FM3-CHANGE-LOG.txt
@@ -289,4 +289,7 @@ the FreeMarer 3 changelog here:
   template converter tool translates these to `${}` interpolations. For example `#{x}` is simply 
   translated to `${b}`, while `#{x; m1M3}` is translated to `${x?string('0.0##')}`).
 - #attempt now logs errors into the org.apache.freemarker.core.Runtime.Attempt log chategory (by
-  default). It doesn't create an additional debug level log entry anymore.
\ No newline at end of file
+  default). It doesn't create an additional debug level log entry anymore.
+- You can't close #attempt/#recover with `</#recover>` anymore, only with `<#attempt>`. This was the
+  standard form in FM2 as well, and is consistent with how #if/#else works. (The template converter
+  tool does this conversion.)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/af17c347/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/recover.ftl
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/recover.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/recover.ftl
index f7dc437..afcc423 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/recover.ftl
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/recover.ftl
@@ -21,7 +21,7 @@
  ${sequence[0]}
 <#recover>
   We should never get here.
-</#recover>
+</#attempt>
 <#attempt>
  Let's try to output an undefined variable: ${undefinedVariable}
 <#recover>
@@ -32,14 +32,14 @@
  <#recover>
    Oops...<@assert test=.error?contains('sequence[1]') />
    Remember, freeMarker sequences are zero-based! ${sequence[0]}
- </#recover>
+ </#attempt>
  Now we check the current error message.<@assert test=.error?contains('undefinedVariable') />
-</#recover>
+</#attempt>
 <#attempt>
   <#include "nonexistent_template">
 <#recover>
   The template is not currently available
-</#recover>
+</#attempt>
 <#attempt>
   <#include "undefined.ftl">
 <#recover>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/af17c347/freemarker-core/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/javacc/FTL.jj b/freemarker-core/src/main/javacc/FTL.jj
index 68c837e..fd07a66 100644
--- a/freemarker-core/src/main/javacc/FTL.jj
+++ b/freemarker-core/src/main/javacc/FTL.jj
@@ -2261,11 +2261,7 @@ ASTDirAttemptRecoverContainer Attempt() :
     start = <ATTEMPT>
     children = MixedContentElements()
     recoveryBlock = Recover()
-    (
-        end = <END_RECOVER>
-        |
-        end = <END_ATTEMPT>
-    )
+    end = <END_ATTEMPT>
     {
         ASTDirAttemptRecoverContainer result = new ASTDirAttemptRecoverContainer(children, recoveryBlock);
         result.setLocation(template, start, end);


[2/2] incubator-freemarker git commit: Continued work on the FM2 to FM3 converter

Posted by dd...@apache.org.
Continued work on the FM2 to FM3 converter


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

Branch: refs/heads/3
Commit: 1f086e3f243e431408f613cbbc8b5f5d2831c18f
Parents: af17c34
Author: ddekany <dd...@apache.org>
Authored: Sun Jul 2 19:09:43 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sun Jul 2 19:09:43 2017 +0200

----------------------------------------------------------------------
 .../core/FM2ASTToFM3SourceConverter.java        | 233 ++++++++++++++-----
 .../converter/FM2ToFM3ConverterTest.java        |  68 ++++--
 2 files changed, 215 insertions(+), 86 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/1f086e3f/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java
----------------------------------------------------------------------
diff --git a/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java b/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java
index b88f9f2..b1f221c 100644
--- a/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java
+++ b/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java
@@ -153,7 +153,7 @@ public class FM2ASTToFM3SourceConverter {
         Expression content = getParam(node, 0, ParameterRole.CONTENT, Expression.class);
 
         int pos = getPositionAfterWSAndExpComments(getEndPositionExclusive(content));
-        assertNodeContent(pos < src.length(), node, "Unexpected EOF", null);
+        assertNodeContent(pos < src.length(), node, "Unexpected EOF");
         char c = src.charAt(pos);
         assertNodeContent(c == ';' || c == '}', node, "Expected ';' or '}', found {}", c);
         if (c == ';') { // #{exp; m1M2} -> ${exp?string('0.0#')}
@@ -216,11 +216,128 @@ public class FM2ASTToFM3SourceConverter {
             printDirCustom((UnifiedCall) node);
         } else if (node instanceof Macro) {
             printDirMacroOrFunction((Macro) node);
+        } else if (node instanceof Assignment) {
+            printDirAssignmentLonely((Assignment) node);
+        } else if (node instanceof AssignmentInstruction) {
+            printDirAssignmentMultiple((AssignmentInstruction) node);
+        } else if (node instanceof AttemptBlock) {
+            printDirAttemptRecover((AttemptBlock) node);
         } else {
             throw new ConverterException("Unhandled AST TemplateElement class: " + node.getClass().getName());
         }
     }
 
+    private void printDirAttemptRecover(AttemptBlock node) throws ConverterException {
+        print(tagBeginChar);
+        print("#attempt");
+        printStartTagSkippedTokens(node, null, true);
+        print(tagEndChar);
+
+        printNode(node.getChild(0));
+        assertNodeContent(node.getChild(1) instanceof RecoveryBlock, node, "child[1] should be #recover");
+
+        RecoveryBlock recoverDir = getOnlyParam(node, ParameterRole.ERROR_HANDLER, RecoveryBlock.class);
+        print(tagBeginChar);
+        print("#recover");
+        printStartTagSkippedTokens(recoverDir, null, true);
+        print(tagEndChar);
+
+        printChildrenElements(recoverDir);
+
+        print(tagBeginChar);
+        print("/#attempt"); // in FM2 this could be /#recover, but we normalize it
+        printEndTagSkippedTokens(node);
+        print(tagEndChar);
+    }
+
+    private void printDirAssignmentMultiple(AssignmentInstruction node) throws ConverterException {
+        assertParamCount(node, 2);
+
+        int pos = printDirAssignmentCommonTagTillAssignmentExp(node, 0);
+
+        int childCnt = node.getChildCount();
+        for (int childIdx = 0; childIdx < childCnt; childIdx++) {
+            Assignment assignment = (Assignment) node.getChild(childIdx);
+            pos = printDirAssignmentCommonExp(assignment, pos);
+            if (childIdx != childCnt - 1) {
+                pos = printWSAndExpComments(pos, ",", true);
+            }
+        }
+
+        printDirAssignmentCommonTagAfterLastAssignmentExp(node, 1, pos);
+    }
+
+    private void printDirAssignmentLonely(Assignment node) throws ConverterException {
+        assertParamCount(node, 5);
+
+        int pos = printDirAssignmentCommonTagTillAssignmentExp(node, 3);
+        pos = printDirAssignmentCommonExp(node, pos);
+        printDirAssignmentCommonTagAfterLastAssignmentExp(node, 4, pos);
+    }
+
+    private void printDirAssignmentCommonTagAfterLastAssignmentExp(TemplateElement node, int nsParamIdx, int pos) throws
+            ConverterException {
+        Expression ns = getParam(node, nsParamIdx, ParameterRole.NAMESPACE, Expression.class);
+        if (ns != null) {
+            pos = printWSAndExpComments(pos, "in", false);
+            printExp(ns);
+            pos = getEndPositionExclusive(ns);
+        }
+        pos = printWSAndExpComments(pos);
+
+        char c = src.charAt(pos);
+        assertNodeContent(c == tagEndChar, node, "End of tag was expected, but found {}", c);
+        print(tagEndChar);
+    }
+
+    private int printDirAssignmentCommonTagTillAssignmentExp(TemplateElement node, int scopeParamIdx)
+            throws ConverterException {
+        print(tagBeginChar);
+
+        int scope = getParam(node, scopeParamIdx, ParameterRole.VARIABLE_SCOPE, Integer.class);
+        String tagName;
+        if (scope == Assignment.NAMESPACE) {
+            tagName = "#assign";
+        } else if (scope == Assignment.GLOBAL) {
+            tagName = "#global";
+        } else if (scope == Assignment.LOCAL) {
+            tagName = "#local";
+        } else {
+            throw new UnexpectedNodeContentException(node, "Unhandled scope: {}", scope);
+        }
+        print(tagName);
+        int pos = getPositionAfterIdentifier(getStartPosition(node) + 2);
+
+        pos = printWSAndExpComments(pos);
+        return pos;
+    }
+
+    private int printDirAssignmentCommonExp(Assignment node, int pos) throws ConverterException {
+        {
+            String target = getParam(node, 0, ParameterRole.ASSIGNMENT_TARGET, String.class);
+            print(FTLUtil.escapeIdentifier(target));
+            pos = getPositionAfterIdentifier(pos);
+        }
+
+        pos = printWSAndExpComments(pos);
+
+        {
+            String operator = getParam(node, 1, ParameterRole.ASSIGNMENT_OPERATOR, String.class);
+            print(operator);
+            pos += operator.length();
+        }
+
+        pos = printWSAndExpComments(pos);
+
+        Expression source = getParam(node, 2, ParameterRole.ASSIGNMENT_SOURCE, Expression.class);
+        if (source != null) {
+            printExp(source);
+            pos = getEndPositionExclusive(source);
+        }
+
+        return pos;
+    }
+
     private void printDirMacroOrFunction(Macro node) throws ConverterException {
         int paramCnt = node.getParameterCount();
 
@@ -236,30 +353,19 @@ public class FM2ASTToFM3SourceConverter {
 
         print(tagBeginChar);
         print(tagName);
-        int pos = getStartPosition(node) + 1;
-        assertNodeContent(src.substring(pos, pos + tagName.length()).equals(tagName), node,
-                "Tag name doesn't match {}", tagName);
-        pos += tagName.length();
+        int pos = getPositionAfterIdentifier(getStartPosition(node) + 2);
 
-        {
-            String sep = readWSAndExpComments(pos);
-            printWithConvertedExpComments(sep);
-            pos += sep.length();
-        }
+        pos = printWSAndExpComments(pos);
 
         String assignedName = getParam(node, 0, ParameterRole.ASSIGNMENT_TARGET, String.class);
         print(FTLUtil.escapeIdentifier(assignedName));
         {
             int lastPos = pos;
             pos = getPositionAfterIdentifier(pos);
-            assertNodeContent(pos > lastPos, node, "Expected target name", null);
+            assertNodeContent(pos > lastPos, node, "Expected target name");
         }
 
-        {
-            String sep = readWSAndExpComments(pos, '(', true);
-            printWithConvertedExpComments(sep);
-            pos += sep.length();
-        }
+        pos = printWSAndExpComments(pos, "(", true);
 
         int paramIdx = 1;
         while (node.getParameterRole(paramIdx) == ParameterRole.PARAMETER_NAME) {
@@ -268,22 +374,17 @@ public class FM2ASTToFM3SourceConverter {
             {
                 int lastPos = pos;
                 pos = getPositionAfterIdentifier(pos);
-                assertNodeContent(pos > lastPos, node, "Expected parameter name", null);
+                assertNodeContent(pos > lastPos, node, "Expected parameter name");
             }
 
             Expression paramDefault = getParam(node, paramIdx++, ParameterRole.PARAMETER_DEFAULT, Expression.class);
             if (paramDefault != null) {
-                String sep = readWSAndExpComments(pos, '=', false);
-                printWithConvertedExpComments(sep);
+                printWSAndExpComments(pos, "=", false);
                 printExp(paramDefault);
                 pos = getEndPositionExclusive(paramDefault);
             }
 
-            {
-                String sep = readWSAndExpComments(pos);
-                printWithConvertedExpComments(sep);
-                pos += sep.length();
-            }
+            pos = printWSAndExpComments(pos);
             {
                 char c = src.charAt(pos);
                 assertNodeContent(
@@ -295,13 +396,11 @@ public class FM2ASTToFM3SourceConverter {
                     print(c);
                     pos++;
 
-                    String sep = readWSAndExpComments(pos);
-                    printWithConvertedExpComments(sep);
-                    pos += sep.length();
+                    pos = printWSAndExpComments(pos);
                 }
                 if (c == ')') {
                     assertNodeContent(node.getParameterRole(paramIdx) != ParameterRole.PARAMETER_NAME, node,
-                            "Expected no parameter after \"(\"", null);
+                            "Expected no parameter after \"(\"");
                 }
             }
         }
@@ -318,15 +417,11 @@ public class FM2ASTToFM3SourceConverter {
                 int lastPos = pos;
                 pos = getPositionAfterIdentifier(pos);
                 assertNodeContent(pos > lastPos, node,
-                        "Expected catch-all parameter name", null);
-            }
-            {
-                String sep = readWSAndExpComments(pos);
-                printWithConvertedExpComments(sep);
-                pos += sep.length();
+                        "Expected catch-all parameter name");
             }
+            pos = printWSAndExpComments(pos);
             assertNodeContent(src.startsWith("...", pos), node,
-                    "Expected \"...\" after catch-all parameter name", null);
+                    "Expected \"...\" after catch-all parameter name");
             print("...");
             pos += 3;
         }
@@ -334,12 +429,8 @@ public class FM2ASTToFM3SourceConverter {
         assertNodeContent(paramIdx == paramCnt - 1, node,
                 "Expected AST parameter at index {} to be the last one", paramIdx);
 
-        {
-            String sep = readWSAndExpComments(pos, ')', true);
-            printWithConvertedExpComments(sep);
-            pos += sep.length();
-        }
-        assertNodeContent(src.charAt(pos) == tagEndChar, node, "Tag end not found", null);
+        pos = printWSAndExpComments(pos, ")", true);
+        assertNodeContent(src.charAt(pos) == tagEndChar, node, "Tag end not found");
         print(tagEndChar);
 
         printChildrenElements(node);
@@ -389,9 +480,9 @@ public class FM2ASTToFM3SourceConverter {
         int pos = getEndPositionExclusive(lastPrintedExp);
         boolean beforeFirstLoopVar = true;
         while (paramIdx < paramCount) {
-            String sep = readWSAndExpComments(pos, beforeFirstLoopVar ? ';' : ',', false);
+            String sep = readWSAndExpComments(pos, beforeFirstLoopVar ? ";" : ",", false);
             assertNodeContent(sep.length() != 0, node,
-                    "Can't find loop variable separator", null);
+                    "Can't find loop variable separator");
             printWithConvertedExpComments(sep);
             pos += sep.length();
 
@@ -399,7 +490,7 @@ public class FM2ASTToFM3SourceConverter {
             print(_StringUtil.toFTLTopLevelIdentifierReference(loopVarName));
             String identifierInSrc = readIdentifier(pos);
             assertNodeContent(identifierInSrc.length() != 0, node,
-                    "Can't find loop variable identifier in source", null);
+                    "Can't find loop variable identifier in source");
             pos += identifierInSrc.length(); // skip loop var name
 
             beforeFirstLoopVar = false;
@@ -417,7 +508,7 @@ public class FM2ASTToFM3SourceConverter {
         }
         if (startTagEndPos != elementEndPos) { // We have an end-tag
             assertNodeContent(src.charAt(startTagEndPos - 1) != '/', node,
-                    "Not expected \"/\" at the end of the start tag", null);
+                    "Not expected \"/\" at the end of the start tag");
             printChildrenElements(node);
 
             print(tagBeginChar);
@@ -427,15 +518,15 @@ public class FM2ASTToFM3SourceConverter {
                 nameStartPos--;
             }
             assertNodeContent(nameStartPos >= 2, node,
-                    "Couldn't extract name from end-tag.", null);
+                    "Couldn't extract name from end-tag.");
             // Also prints ignored WS after name, for now:
             printWithConvertedExpComments(src.substring(nameStartPos, elementEndPos));
             print(tagEndChar);
         } else { // We don't have end-tag
             assertNodeContent(src.charAt(startTagEndPos - 1) == '/', node,
-                    "Expected \"/\" at the end of the start tag", null);
+                    "Expected \"/\" at the end of the start tag");
             assertNodeContent(node.getChildCount() == 0, node,
-                    "Expected no children", null);
+                    "Expected no children");
         }
     }
 
@@ -649,9 +740,10 @@ public class FM2ASTToFM3SourceConverter {
 
     private void printExpBuiltinVariable(BuiltinVariable node) throws ConverterException {
         int startPos = getStartPosition(node);
-        String sep = readWSAndExpComments(startPos, '.', false);
-        printWithConvertedExpComments(sep);
-        String name = src.substring(startPos + sep.length(), getEndPositionExclusive(node));
+
+        int varNameStart = printWSAndExpComments(startPos, ".", false);
+
+        String name = src.substring(varNameStart, getEndPositionExclusive(node));
         print(convertBuiltInVariableName(name));
     }
 
@@ -668,8 +760,7 @@ public class FM2ASTToFM3SourceConverter {
         Expression lho = getParam(node, 0, ParameterRole.LEFT_HAND_OPERAND, Expression.class);
         String rho = getParam(node, 1, ParameterRole.RIGHT_HAND_OPERAND, String.class);
         printNode(lho);
-        printWithConvertedExpComments(
-                readWSAndExpComments(getEndPositionExclusive(lho), '.', false));
+        printWSAndExpComments(getEndPositionExclusive(lho), ".", false);
         print(FTLUtil.escapeIdentifier(rho));
     }
 
@@ -707,9 +798,9 @@ public class FM2ASTToFM3SourceConverter {
         int openCharPos = getStartPosition(node);
         int closeCharPos = getEndPositionInclusive(node);
         assertNodeContent(src.charAt(openCharPos) == '{', node,
-                "Expected '{'", null);
+                "Expected '{'");
         assertNodeContent(src.charAt(closeCharPos) == '}', node,
-                "Expected '}'", null);
+                "Expected '}'");
 
         int paramCnt = node.getParameterCount();
         if (paramCnt == 0) {
@@ -740,9 +831,9 @@ public class FM2ASTToFM3SourceConverter {
         int openCharPos = getStartPosition(node);
         int closeCharPos = getEndPositionInclusive(node);
         assertNodeContent(src.charAt(openCharPos) == '[', node,
-                "Expected '['", null);
+                "Expected '['");
         assertNodeContent(src.charAt(closeCharPos) == ']', node,
-                "Expected ']'", null);
+                "Expected ']'");
 
         int paramCnt = node.getParameterCount();
         if (paramCnt == 0) {
@@ -1064,7 +1155,7 @@ public class FM2ASTToFM3SourceConverter {
         }
 
         assertNodeContent(pos > 0 && isCoreNameChar(src.charAt(pos)), node,
-                "Can't find end tag name", null);
+                "Can't find end tag name");
 
         printWithConvertedExpComments(src.substring(pos + 1, tagEndPos));
     }
@@ -1134,6 +1225,11 @@ public class FM2ASTToFM3SourceConverter {
     }
 
     private void assertNodeContent(boolean good, TemplateObject node, String
+        errorMessage) throws UnexpectedNodeContentException {
+        assertNodeContent(good, node, errorMessage, null);
+    }
+
+    private void assertNodeContent(boolean good, TemplateObject node, String
             errorMessage, Object msgParam) throws UnexpectedNodeContentException {
         if (!good) {
             throw new UnexpectedNodeContentException(node, errorMessage, msgParam);
@@ -1220,21 +1316,36 @@ public class FM2ASTToFM3SourceConverter {
         return src.substring(startPos, getPositionAfterWSAndExpComments(startPos));
     }
 
-    private String readWSAndExpComments(int startPos, char separator, boolean separatorOptional)
+    private String readWSAndExpComments(int startPos, String separator, boolean separatorOptional)
             throws ConverterException {
         int pos = getPositionAfterWSAndExpComments(startPos);
 
-        if (pos == src.length() || src.charAt(pos) != separator) {
+        if (pos == src.length() || !src.startsWith(separator, pos)) {
             // No separator
             return separatorOptional ? src.substring(startPos, pos) : "";
         }
-        pos++; // Skip separator
+        pos += separator.length();
 
         pos = getPositionAfterWSAndExpComments(pos);
 
         return src.substring(startPos, pos);
     }
 
+    private int printWSAndExpComments(int pos) throws ConverterException {
+        String sep = readWSAndExpComments(pos);
+        printWithConvertedExpComments(sep);
+        pos += sep.length();
+        return pos;
+    }
+
+    private int printWSAndExpComments(int pos, String separator, boolean sepOptional) throws
+            ConverterException {
+        String sep = readWSAndExpComments(pos, separator, sepOptional);
+        printWithConvertedExpComments(sep);
+        pos += sep.length();
+        return pos;
+    }
+
     private int getPositionAfterIdentifier(int startPos) throws ConverterException {
         int pos = startPos;
         scanUntilIdentifierEnd: while (pos < src.length()) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/1f086e3f/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java
----------------------------------------------------------------------
diff --git a/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java b/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java
index 9166e35..0f74125 100644
--- a/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java
+++ b/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java
@@ -70,19 +70,19 @@ public class FM2ToFM3ConverterTest extends ConverterTest {
         assertConvertedSame("${f([ <#-- C --> ])}");
         assertConvertedSame("${f([1])}");
         assertConvertedSame("${f([1, [x,y], 3])}");
-        assertConvertedSame("${f([<#-- C1 --> 1, <#-- C2 --> 2, <#-- C3 --> 3 <#-- C4 -->])}");
+        assertConvertedSame("${f([<#--1--> 1, <#--2--> 2, <#--3--> 3 <#--4-->])}");
 
         assertConvertedSame("${f({})}");
         assertConvertedSame("${f({k: v})}");
         assertConvertedSame("${f({k1: v1, k2: v2, 'k3': 33})}");
-        assertConvertedSame("${f({ <#-- C1 --> k1 <#-- C1 --> : <#-- C1 --> v1 <#-- C1 -->,k2:v2 <#-- C1 -->})}");
+        assertConvertedSame("${f({ <#--1--> k1 <#--2--> : <#--3--> v1 <#--4-->,k2:v2 <#--5-->})}");
 
         assertConvertedSame("${f(1 .. 9)}");
         assertConvertedSame("${f(1 ..* 9)}");
         assertConvertedSame("${f(1 ..! 9)}");
         assertConvertedSame("${f(1 ..< 9)}");
         assertConvertedSame("${f(1 ..)}");
-        assertConvertedSame("${f(1<#-- C1 -->..\t<#-- C2 -->9)}");
+        assertConvertedSame("${f(1<#--1-->..\t<#--2-->9)}");
     }
 
     @Test
@@ -93,24 +93,24 @@ public class FM2ToFM3ConverterTest extends ConverterTest {
         assertConvertedSame("${f()}");
         assertConvertedSame("${f(1)}");
         assertConvertedSame("${f(1, 2)}");
-        assertConvertedSame("${f<#-- C1 -->(<#-- C2 --> 1, 2 ,<#-- C3 --> 3,<#-- C4 -->4 <#-- C5 -->)}");
+        assertConvertedSame("${f<#--1-->(<#--2--> 1, 2 ,<#--3--> 3,<#--4-->4 <#--5-->)}");
 
         assertConvertedSame("${m[key]}");
         assertConvertedSame("${m['key']}");
-        assertConvertedSame("${m <#-- C1 --> [ <#-- C2 --> key <#-- C3 --> ]}");
+        assertConvertedSame("${m <#--1--> [ <#--2--> key <#--3--> ]}");
         assertConvertedSame("${seq\\-foo[1]}");
 
         assertConvertedSame("${m.key}");
-        assertConvertedSame("${m <#-- C1 --> . <#-- C3 --> key}");
+        assertConvertedSame("${m <#--1--> . <#--3--> key}");
 
         assertConvertedSame("${.outputFormat}");
         assertConvertedSame("${. <#-- C --> outputFormat}");
         assertConverted("${.outputFormat}","${.output_format}");
 
         assertConvertedSame("${a < b}${a <= b}${(a > b)}${(a >= b)}${a == b}${a != b}");
-        assertConvertedSame("${a<#-- C1 --><<#-- C2 -->b}${a<#-- C3 --><=<#-- C4 -->b}"
-                + "${(a<#-- C7 -->><#-- C8 -->b)}${(a<#-- C9 -->>=<#-- CA -->b)}"
-                + "${a<#-- CB -->==<#-- CC -->b}${a<#-- CD -->!=<#-- CE -->b}");
+        assertConvertedSame("${a<#--1--><<#--2-->b}${a<#--3--><=<#--4-->b}"
+                + "${(a<#--7-->><#--8-->b)}${(a<#--9-->>=<#--A-->b)}"
+                + "${a<#--B-->==<#--C-->b}${a<#--D-->!=<#--E-->b}");
         // "Same" for now, will be different later.
         assertConvertedSame("${a = b}${a == b}");
         assertConvertedSame("${a &lt; b}${a lt b}${a \\lt b}");
@@ -120,19 +120,19 @@ public class FM2ToFM3ConverterTest extends ConverterTest {
 
         // [FM3] Add \and and &amp;&amp; tests when 2.3.27 is released
         assertConvertedSame("${a && b}${a & b}${a || b}${a | b}");
-        assertConvertedSame("${a<#-- C1 -->&&<#-- C2 -->b}${a<#-- C3 -->&<#-- C4 -->b}"
-                + "${a<#-- C5 -->||<#-- C6 -->b}${a<#-- C7 -->|<#-- C8 -->b}");
+        assertConvertedSame("${a<#--1-->&&<#--2-->b}${a<#--3-->&<#--4-->b}"
+                + "${a<#--5-->||<#--6-->b}${a<#--7-->|<#--8-->b}");
 
-        assertConvertedSame("${!a}${! foo}${! <#-- C1 --> bar}${!!c}");
+        assertConvertedSame("${!a}${! foo}${! <#--1--> bar}${!!c}");
 
         assertConvertedSame("${a!} ${a!0}");
-        assertConvertedSame("${a <#-- C1 --> !} ${a <#-- C2 --> ! <#-- C3 --> 0}");
+        assertConvertedSame("${a <#--1--> !} ${a <#--2--> ! <#--3--> 0}");
         assertConvertedSame("${a!b.c(x!0, y!0)}");
         assertConvertedSame("${(a.b)!x}");
         // [FM3] Will be: a!(x+1)
         assertConvertedSame("${a!x+1}");
 
-        assertConvertedSame("${a??} ${a <#-- C1 --> ??}");
+        assertConvertedSame("${a??} ${a <#--1--> ??}");
     }
 
     @Test
@@ -166,25 +166,43 @@ public class FM2ToFM3ConverterTest extends ConverterTest {
         assertConvertedSame("<#if  foo >1<#elseIf  bar >2<#else >3</#if >");
 
         assertConvertedSame("<#macro m>body</#macro>");
-        assertConvertedSame("<#macro <#-- C1 --> m <#-- C2 -->></#macro >");
+        assertConvertedSame("<#macro <#--1--> m <#--2-->></#macro >");
         assertConvertedSame("<#macro m()></#macro>");
-        assertConvertedSame("<#macro m <#-- C1 --> ( <#-- C2 --> ) <#-- C3 --> ></#macro>");
+        assertConvertedSame("<#macro m <#--1--> ( <#--2--> ) <#--3--> ></#macro>");
         assertConvertedSame("<#macro m p1></#macro>");
         assertConvertedSame("<#macro m(p1)></#macro>");
         assertConvertedSame("<#macro m p1 p2 p3></#macro>");
-        assertConvertedSame("<#macro m p1 <#-- C1 --> p2 <#-- C2 --> p3 <#-- C3 -->></#macro>");
-        assertConvertedSame("<#macro m(p1<#-- C1 -->,<#-- C2 --> p2<#-- C3 -->,<#-- C4 -->"
-                + " p5<#-- C5 -->)<#-- C6 -->></#macro>");
+        assertConvertedSame("<#macro m p1 <#--1--> p2 <#--2--> p3 <#--3-->></#macro>");
+        assertConvertedSame("<#macro m(p1<#--1-->,<#--2--> p2<#--3-->,<#--4-->"
+                + " p5<#--5-->)<#--6-->></#macro>");
         assertConvertedSame("<#macro m p1=11 p2=foo p3=a+b></#macro>");
         assertConvertedSame("<#macro m(p1=11, p2=foo, p3=a+b)></#macro>");
-        assertConvertedSame("<#macro m p1<#-- C1 -->=<#-- C2 -->11<#-- C3 -->,<#-- C4 -->p2=22></#macro>");
+        assertConvertedSame("<#macro m p1<#--1-->=<#--2-->11<#--3-->,<#--4-->p2=22></#macro>");
         assertConvertedSame("<#macro m others...></#macro>");
         assertConvertedSame("<#macro m p1 others...></#macro>");
         assertConvertedSame("<#macro m p1 p2=22 others...></#macro>");
         assertConvertedSame("<#macro m(others...)></#macro>");
-        assertConvertedSame("<#macro m(others <#-- C1 --> ... <#-- C2 --> )></#macro>");
+        assertConvertedSame("<#macro m(others <#--1--> ... <#--2--> )></#macro>");
         assertConvertedSame("<#function m x y>foo</#function>");
         assertConvertedSame("<#macro m\\-1 p\\-1></#macro>");
+
+        assertConvertedSame("<#assign x = 1>");
+        assertConvertedSame("<#global x = 1>");
+        assertConvertedSame("<#macro m><#local x = 1></#macro>");
+        assertConvertedSame("<#assign x = 1 in someNs>");
+        assertConvertedSame("<#assign <#--1--> x <#--2--> = <#--3--> 1 <#--4--> in <#--5--> someNs <#--6-->>");
+        assertConvertedSame("<#assign x=1 y=2,z=3>");
+        assertConvertedSame("<#assign x += 1>");
+        assertConvertedSame("<#assign x *= 2>");
+        assertConvertedSame("<#assign x-->");
+        assertConvertedSame("<#global x = 1, y++, z /= 2>");
+        assertConvertedSame("<#assign x = 1 y++ z /= 2>");
+        assertConvertedSame("<#assign <#--0-->x = 1<#--1-->,<#--2-->y++<#--3-->z/=2<#--4-->>");
+
+        assertConvertedSame("<#attempt>a<#recover>r</#attempt>");
+        assertConvertedSame("<#attempt >a<#recover  >r</#attempt   >");
+        assertConverted("<#attempt>a<#recover>r</#attempt>", "<#attempt>a<#recover>r</#recover>");
+        assertConverted("<#attempt >a<#recover  >r</#attempt   >", "<#attempt >a<#recover  >r</#recover   >");
     }
 
     @Test
@@ -199,14 +217,14 @@ public class FM2ToFM3ConverterTest extends ConverterTest {
         assertConvertedSame("<@foo x\\-y=1 />");
         assertConvertedSame("<@foo\n\tx = 1\n\ty = 2\n/>");
         assertConvertedSame("<@foo 1 2 />");
-        assertConvertedSame("<@foo <#-- C1 --> 1 <#-- C2 --> 2 <#-- C3 --> />");
+        assertConvertedSame("<@foo <#--1--> 1 <#--2--> 2 <#--3--> />");
         assertConvertedSame("<@foo 1, 2 />");
-        assertConvertedSame("<@foo <#-- C1 --> 1 <#-- C2 -->, <#-- C3 --> 2 <#-- C4 --> />");
+        assertConvertedSame("<@foo <#--1--> 1 <#--2-->, <#--3--> 2 <#--4--> />");
         assertConvertedSame("<@foo x=1; i, j></@>");
         assertConvertedSame("<@foo 1; i, j></@>");
         assertConvertedSame("<@foo 1 2; i\\-2, j></@>");
         assertConvertedSame("<@foo x=1 y=2; i></@>");
-        assertConvertedSame("<@foo x=1 ;\n    i <#-- C0 --> , <#-- C1 -->\n\t<!-- C2 --> j <#-- C3 -->\n></@>");
+        assertConvertedSame("<@foo x=1 ;\n    i <#-- C0 --> , <#--1-->\n\t<!-- C2 --> j <#--3-->\n></@>");
     }
 
     @Test
@@ -219,7 +237,7 @@ public class FM2ToFM3ConverterTest extends ConverterTest {
     @Test
     public void testComments() throws IOException, ConverterException {
         assertConvertedSame("\n<#--\n  c\n\t-->\n");
-        assertConvertedSame("${1 + <#-- C1 -->\r\n2 +[#-- C2 --]3 +<!--\tC3\t-->4 +[!-- C4 --] 5 + -<!-- -->1}");
+        assertConvertedSame("${1 + <#--1-->\r\n2 +[#-- C2 --]3 +<!--\tC3\t-->4 +[!-- C4 --] 5 + -<!-- -->1}");
     }
 
     @Test