This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch 3
in repository https://gitbox.apache.org/repos/asf/freemarker.git


The following commit(s) were added to refs/heads/3 by this push:
     new 44e09679 Forward ported from 2.3-gae: Added #on directive, that's now 
preferred over #case inside #switch
44e09679 is described below

commit 44e09679e3a2e6b9e63502fb71fad6aba830ec52
Author: ddekany <[email protected]>
AuthorDate: Sat Nov 30 19:29:22 2024 +0100

    Forward ported from 2.3-gae: Added #on directive, that's now preferred over 
#case inside #switch
---
 .../core/BreakAndContinuePlacementTest.java        |  12 +-
 .../org/apache/freemarker/core/SwitchTest.java     | 185 +++++++++++++++++++++
 .../core/TemplateProcessingTracerTest.java         |  17 ++
 .../resources/org/apache/freemarker/core/ast-1.ast |  28 +++-
 .../org/apache/freemarker/core/ast-1.f3ah          |  13 +-
 .../core/templatesuite/templates/switch.f3ac       |   4 +-
 .../java/org/apache/freemarker/core/ASTDirOn.java  |  93 +++++++++++
 .../org/apache/freemarker/core/ASTDirSwitch.java   |  84 +++++++---
 .../org/apache/freemarker/core/ASTDirective.java   |   5 +-
 .../main/javacc/org/apache/freemarker/core/FTL.jj  |  81 ++++++++-
 10 files changed, 470 insertions(+), 52 deletions(-)

diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/BreakAndContinuePlacementTest.java
 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/BreakAndContinuePlacementTest.java
index 323ddfb5..775192f5 100644
--- 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/BreakAndContinuePlacementTest.java
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/BreakAndContinuePlacementTest.java
@@ -19,11 +19,11 @@
 
 package org.apache.freemarker.core;
 
-import java.io.IOException;
-
 import org.apache.freemarker.test.TemplateTest;
 import org.junit.Test;
 
+import java.io.IOException;
+
 public class BreakAndContinuePlacementTest extends TemplateTest {
     
     private static final String BREAK_NESTING_ERROR_MESSAGE_PART = "<#break> 
must be nested";
@@ -43,7 +43,10 @@ public class BreakAndContinuePlacementTest extends 
TemplateTest {
                 + "<#list xs>[<#items as 
x>${x}</#items>]<#else><#break></#list>"
                 + "</#list>.",
                 "[12][34].");
-        assertOutput("<#switch 1><#case 1>1<#break></#switch>", "1");
+        assertOutput("<#list 1..2 as x><#switch x><#on 
1>one<#break></#switch>;</#list>", "one");
+        assertOutput("<#list 1..2 as x><#switch x><#on 
1>one<#continue></#switch>;</#list>", "one;");
+        assertOutput("<#switch 1><#case 1>1<#break>unreachable</#switch>.", 
"1.");
+        assertOutput("<#switch 1><#default>1<#break>unreachable</#switch>.", 
"1.");
     }
 
     @Test
@@ -51,6 +54,9 @@ public class BreakAndContinuePlacementTest extends 
TemplateTest {
         assertErrorContains("<#break>", BREAK_NESTING_ERROR_MESSAGE_PART);
         assertErrorContains("<#continue>", 
CONTINUE_NESTING_ERROR_MESSAGE_PART);
         assertErrorContains("<#switch 1><#case 1>1<#continue></#switch>", 
CONTINUE_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#switch 1><#on 1>1<#continue></#switch>", 
CONTINUE_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#switch 1><#on 1>1<#break></#switch>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#switch 1><#on 1>1<#default><#break></#switch>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
         assertErrorContains("<#list 1..2 as x>${x}</#list><#break>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
         assertErrorContains("<#if false><#break></#if>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
         assertErrorContains("<#list xs><#break></#list>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/SwitchTest.java 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SwitchTest.java
new file mode 100644
index 00000000..19a5797f
--- /dev/null
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SwitchTest.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * There are more (older) test in "switch-builtin.ftl", but we prefer creating 
new ones in Java.
+ */
+public class SwitchTest extends TemplateTest {
+
+    @Test
+    public void testCaseBasics() throws TemplateException, IOException {
+        testCaseBasics(true);
+        testCaseBasics(false);
+    }
+
+    private void testCaseBasics(boolean hasDefault) throws TemplateException, 
IOException {
+        for (int i = 1 ; i <= 6; i++) {
+            assertOutput(
+                    "[<#switch " + i + ">\n"
+                            + "<#case 3>Case 3<#break>"
+                            + "<#case 1>Case 1<#break>"
+                            + "<#case 4><#case 5>Case 4 or 5<#break>"
+                            + "<#case 2>Case 2<#break>"
+                            + (hasDefault ? "<#default>D" : "")
+                            + "</#switch>]",
+                    i < 6
+                            ? "[Case " + (i < 4 ? i : "4 or 5") + "]"
+                            : (hasDefault ? "[D]" : "[]"));
+        }
+    }
+
+    @Test
+    public void testDefaultOnly() throws TemplateException, IOException {
+        assertOutput("<#switch 1><#default>D</#switch>", "D");
+        assertOutput("<#list 1..2 as i><#switch 
1><#default>D<#break>unreachable</#switch></#list>", "DD");
+    }
+
+    @Test
+    public void testCaseWhitespace() throws TemplateException, IOException {
+        assertOutput(
+                ""
+                        + "<#list 1..3 as i>\n"
+                        + "[\n"  // <#----> is to avoid unrelated old 
white-space stripping bug
+                        + "  <#switch i>\n"
+                        + "    <#case 1>C1\n"
+                        + "    <#case 2>C2<#break>\n"
+                        + "    <#default>D\n"
+                        + "  </#switch>\n"
+                        + "]\n"
+                        + "</#list>",
+                "[\nC1\n    C2]\n[\nC2]\n[\nD\n]\n");
+    }
+
+    @Test
+    public void testOn() throws TemplateException, IOException {
+        testOnBasics(true);
+        testOnBasics(false);
+    }
+
+    private void testOnBasics(boolean hasDefault) throws TemplateException, 
IOException {
+        for (int i = 1 ; i <= 6; i++) {
+            assertOutput(
+                    "[<#switch " + i + ">\n"
+                            + "<#on 3>On 3"
+                            + "<#on 1>On 1"
+                            + "<#on 4, 5>On 4 or 5"
+                            + "<#on 2>On 2"
+                            + (hasDefault ? "<#default>D" : "")
+                            + "</#switch>]",
+                    i < 6
+                            ? "[On " + (i < 4 ? i : "4 or 5") + "]"
+                            : (hasDefault ? "[D]" : "[]"));
+        }
+    }
+
+    @Test
+    public void testParsingErrors() {
+        assertErrorContains(
+                "<#list 1..3 as i><#switch i><#case 1>1<#default>D<#case 
3>3</#switch>;</#list>",
+                ParseException.class,
+                "#case directive after the #default");
+    }
+
+    @Test
+    public void testOnParsingErrors() {
+        assertErrorContains(
+                "<#switch x><#on 1>On 1<#default>D<#on 2>On 2</#switch>",
+                ParseException.class,
+                "#on after #default");
+        assertErrorContains(
+                "<#switch x><#on 1>On 1<#case 2>On 2</#switch>",
+                ParseException.class,
+                "can't use both #on, and #case", "already had an #on");
+        assertErrorContains(
+                "<#switch x><#case 1>On 1<#on 2>On 2</#switch>",
+                ParseException.class,
+                "can't use both #on, and #case", "already had a #case");
+        assertErrorContains(
+                "<#switch x><#on 1>On 1<#default>D<#case 2>On 2</#switch>",
+                ParseException.class,
+                "can't use both #on, and #case", "already had an #on");
+        assertErrorContains(
+                "<#switch x><#on 1>On 1<#default>D1<#default>D2</#switch>",
+                ParseException.class,
+                "already had a #default");
+        assertErrorContains(
+                "<#switch x><#case 1>On 1<#default>D1<#default>D2</#switch>",
+                ParseException.class,
+                "already had a #default");
+        assertErrorContains(
+                "<#switch x><#on 1>On 1<#default>D<#on 2>On 2</#switch>",
+                ParseException.class,
+                "#on after #default");
+        assertErrorContains(
+                "<#switch x><#default>D<#on 2>On 2</#switch>",
+                ParseException.class,
+                "#on after #default");
+    }
+
+    @Test
+    public void testOnWhitespace() throws TemplateException, IOException {
+        assertOutput(
+                ""
+                        + "<#list 1..3 as i>\n"
+                        + "[\n"  // <#----> is to avoid unrelated old 
white-space stripping bug
+                        + "  <#switch i>\n"
+                        + "    <#on 1>C1\n"
+                        + "    <#on 2>C2\n"
+                        + "    <#default>D\n"
+                        + "  </#switch>\n"
+                        + "]\n"
+                        + "</#list>",
+                "[\nC1\n    ]\n[\nC2\n    ]\n[\nD\n]\n");
+        assertOutput(
+                ""
+                        + "<#list 1..3 as i>\n"
+                        + "[\n"  // <#----> is to avoid unrelated old 
white-space stripping bug
+                        + "  <#switch i>\n"
+                        + "    <#on 1>C1<#t>\n"
+                        + "    <#on 2>C2<#t>\n"
+                        + "    <#default>D<#t>\n"
+                        + "  </#switch>\n"
+                        + "]\n"
+                        + "</#list>",
+                "[\nC1]\n[\nC2]\n[\nD]\n");
+        assertOutput(
+                ""
+                        + "<#list 1..3 as i>\n"
+                        + "[\n"  // <#----> is to avoid unrelated old 
white-space stripping bug
+                        + "  <#switch i>\n"
+                        + "    <#on 1>\n"
+                        + "      C1\n"
+                        + "    <#on 2>\n"
+                        + "      C2\n"
+                        + "    <#default>\n"
+                        + "      D\n"
+                        + "  </#switch>\n"
+                        + "]\n"
+                        + "</#list>",
+                "[\n      C1\n]\n[\n      C2\n]\n[\n      D\n]\n");
+    }
+
+}
diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateProcessingTracerTest.java
 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateProcessingTracerTest.java
index 36beec6f..fc3c6f5d 100644
--- 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateProcessingTracerTest.java
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateProcessingTracerTest.java
@@ -62,6 +62,15 @@ public class TemplateProcessingTracerTest {
             "<#case 2>C3<#break>" +
             "<#default>D" +
             "</#switch>" +
+            "<#switch 4>" +
+            "<#on 1>O1" +
+            "<#on 4>O4" +
+            "<#default>D" +
+            "</#switch>" +
+            "<#switch 5>" +
+            "<#on 1>O1" +
+            "<#default>OD" +
+            "</#switch>" +
             "<#macro m>Hello from m!</#macro>" +
             "Calling macro: <@m />" +
             "<#assign t>captured</#assign>" +
@@ -114,6 +123,8 @@ public class TemplateProcessingTracerTest {
                         "C2",
                         "<#break>",
                         "D",
+                        "O4",
+                        "OD",
                         "Calling macro: ",
                         "<@m />",
                         "Hello from m!",
@@ -179,6 +190,12 @@ public class TemplateProcessingTracerTest {
                         " #switch 3",
                         "  #default",
                         "   text \"D\"",
+                        " #switch 4",
+                        "  #on 4",
+                        "   text \"O4\"",
+                        " #switch 5",
+                        "  #default",
+                        "   text \"OD\"",
                         " #macro m",
                         " text \"Calling macro: \"",
                         " @m",
diff --git 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast
index e5f70358..65c289b5 100644
--- 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast
+++ 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast
@@ -112,6 +112,24 @@
                 - content: "more"  // String
     #text  // o.a.f.c.ASTStaticText
         - content: "\n6 "  // String
+    #switch  // o.a.f.c.ASTDirSwitch
+        - value: x  // o.a.f.c.ASTExpVariable
+        #on  // o.a.f.c.ASTDirOn
+            - condition: 1  // o.a.f.c.ASTExpNumberLiteral
+            - condition: 2  // o.a.f.c.ASTExpNumberLiteral
+            #text  // o.a.f.c.ASTStaticText
+                - content: "one or two"  // String
+        #on  // o.a.f.c.ASTDirOn
+            - condition: 3  // o.a.f.c.ASTExpNumberLiteral
+            #text  // o.a.f.c.ASTStaticText
+                - content: "three"  // String
+        #default  // o.a.f.c.ASTDirCase
+            - condition: null  // Null
+            - AST-node subtype: "1"  // Integer
+            #text  // o.a.f.c.ASTStaticText
+                - content: "more"  // String
+    #text  // o.a.f.c.ASTStaticText
+        - content: "\n7 "  // String
     #macro  // o.a.f.c.ASTDirMacroOrFunction
         - assignment target: "foo"  // String
         - parameter definition: "ParameterDefinition(name=\"x\")"  // 
o.a.f.c.ASTDirMacroOrFunction$ParameterDefinition
@@ -123,7 +141,7 @@
             - passed value: x  // o.a.f.c.ASTExpVariable
             - passed value: y  // o.a.f.c.ASTExpVariable
     #text  // o.a.f.c.ASTStaticText
-        - content: "\n7 "  // String
+        - content: "\n8 "  // String
     #function  // o.a.f.c.ASTDirMacroOrFunction
         - assignment target: "foo"  // String
         - parameter definition: "ParameterDefinition(name=\"x\")"  // 
o.a.f.c.ASTDirMacroOrFunction$ParameterDefinition
@@ -138,12 +156,12 @@
         #return  // o.a.f.c.ASTDirReturn
             - value: 1  // o.a.f.c.ASTExpNumberLiteral
     #text  // o.a.f.c.ASTStaticText
-        - content: "\n8 "  // String
+        - content: "\n9 "  // String
     #list  // o.a.f.c.ASTDirList
         - list source: xs  // o.a.f.c.ASTExpVariable
         - nested content parameter: "x"  // String
     #text  // o.a.f.c.ASTStaticText
-        - content: "\n9 "  // String
+        - content: "\n10 "  // String
     #list-#else-container  // o.a.f.c.ASTDirListElseContainer
         #list  // o.a.f.c.ASTDirList
             - list source: xs  // o.a.f.c.ASTExpVariable
@@ -162,11 +180,11 @@
             #text  // o.a.f.c.ASTStaticText
                 - content: "None"  // String
     #text  // o.a.f.c.ASTStaticText
-        - content: "\n10 "  // String
+        - content: "\n11 "  // String
     #--...--  // o.a.f.c.ASTComment
         - content: " A comment "  // String
     #text  // o.a.f.c.ASTStaticText
-        - content: "\n11 "  // String
+        - content: "\n12 "  // String
     #outputFormat  // o.a.f.c.ASTDirOutputFormat
         - value: "XML"  // o.a.f.c.ASTExpStringLiteral
         #noAutoEsc  // o.a.f.c.ASTDirNoAutoEsc
diff --git 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.f3ah 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.f3ah
index f9af6567..ff1ba093 100644
--- 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.f3ah
+++ 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.f3ah
@@ -21,9 +21,10 @@
 3 <#assign x = 123><#assign x = 123 in ns><#global x = 123>
 4 <#if x + 1 == 0>foo${y}bar<#else>${"static"}${'x${baaz * 10}y'}</#if>
 5 <#switch x><#case 1>one<#case 2>two<#default>more</#switch>
-6 <#macro foo x y=2 z=y+1 q...><#nested x, y></#macro>
-7 <#function foo(x, y)><#local x = 123><#return 1></#function>
-8 <#list xs as x></#list>
-9 <#list xs>[<#items as x>${x}<#sep>, </#items>]<#else>None</#list>
-10 <#-- A comment -->
-11 <#outputFormat 
"XML"><#noAutoEsc>${a}<#autoEsc>${b}</#autoEsc>${c}</#noAutoEsc></#outputFormat>
\ No newline at end of file
+6 <#switch x><#on 1, 2>one or two<#on 3>three<#default>more</#switch>
+7 <#macro foo x y=2 z=y+1 q...><#nested x, y></#macro>
+8 <#function foo(x, y)><#local x = 123><#return 1></#function>
+9 <#list xs as x></#list>
+10 <#list xs>[<#items as x>${x}<#sep>, </#items>]<#else>None</#list>
+11 <#-- A comment -->
+12 <#outputFormat 
"XML"><#noAutoEsc>${a}<#autoEsc>${b}</#autoEsc>${c}</#noAutoEsc></#outputFormat>
\ No newline at end of file
diff --git 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/switch.f3ac
 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/switch.f3ac
index 6c147b9c..3e505751 100644
--- 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/switch.f3ac
+++ 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/switch.f3ac
@@ -131,8 +131,8 @@
 </#list>
 
 <#-- Parsing errors -->
-<@assertFails message="can only have one default"><@"<#switch 1><#case 
1><#default><#default></#switch>"?interpret /></@>
-<@assertFails message="after the \"default\""><@"<#switch 1><#default><#case 
1></#switch>"?interpret /></@>
+<@assertFails message="already had a #default"><@"<#switch 1><#case 
1><#default><#default></#switch>"?interpret /></@>
+<@assertFails message="after the #default"><@"<#switch 1><#default><#case 
1></#switch>"?interpret /></@>
 
 </body>
 </html>
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirOn.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirOn.java
new file mode 100644
index 00000000..3ea010df
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirOn.java
@@ -0,0 +1,93 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.util.List;
+
+/**
+ * AST directive node: {@code #on}.
+ */
+final class ASTDirOn extends ASTDirective {
+
+    List<ASTExpression> conditions;
+
+    ASTDirOn(List<ASTExpression> matchingValues, TemplateElements children) {
+        this.conditions = matchingValues;
+        setChildren(children);
+    }
+
+    @Override
+    ASTElement[] execute(Environment env) {
+        return getChildBuffer();
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder sb = new StringBuilder();
+        if (canonical) sb.append('<');
+        sb.append(getLabelWithoutParameters());
+        for (int i = 0; i < conditions.size(); i++) {
+            if (i != 0) {
+                sb.append(',');
+            }
+            sb.append(' ');
+            sb.append((conditions.get(i)).getCanonicalForm());
+        }
+        if (canonical) {
+            sb.append('>');
+            sb.append(getChildrenCanonicalForm());
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String getLabelWithoutParameters() {
+        return "#on";
+    }
+
+    @Override
+    int getParameterCount() {
+        return conditions.size();
+    }
+
+    @Override
+    ASTExpression getParameterValue(int idx) {
+        checkIndex(idx);
+        return conditions.get(idx);
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        checkIndex(idx);
+        return ParameterRole.CONDITION;
+    }
+
+    private void checkIndex(int idx) {
+        if (conditions == null || idx >= conditions.size()) {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+
+}
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java
index 909e88d7..6407b355 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java
@@ -27,8 +27,9 @@ import java.io.IOException;
 final class ASTDirSwitch extends ASTDirective {
 
     private ASTDirCase defaultCase;
+    private boolean usesOnDirective;
     private final ASTExpression searched;
-    private int firstCaseIndex;
+    private int firstCaseOrOnIndex;
 
     /**
      * @param searched the expression to be tested.
@@ -41,7 +42,7 @@ final class ASTDirSwitch extends ASTDirective {
         for (int i = 0; i < ignoredCnt; i++) {
             addChild(ignoredSectionBeforeFirstCase.fastGetChild(i));
         }
-        firstCaseIndex = ignoredCnt; // Note that normally postParseCleanup 
will overwrite this
+        firstCaseOrOnIndex = ignoredCnt; // Note that normally 
postParseCleanup will overwrite this
     }
 
     void addCase(ASTDirCase cas) {
@@ -51,39 +52,70 @@ final class ASTDirSwitch extends ASTDirective {
         addChild(cas);
     }
 
+    void addOn(ASTDirOn on) {
+        addChild(on);
+        usesOnDirective = true;
+    }
+
     @Override
     ASTElement[] execute(Environment env)
         throws TemplateException, IOException {
         boolean processedCase = false;
         int ln = getChildCount();
-        try {
-            for (int i = firstCaseIndex; i < ln; i++) {
-                ASTDirCase cas = (ASTDirCase) fastGetChild(i);
-                boolean processCase = false;
-
-                // Fall through if a previous case tested true.
-                if (processedCase) {
-                    processCase = true;
-                } else if (cas.condition != null) {
-                    // Otherwise, if this case isn't the default, test it.
-                    processCase = _EvalUtils.compare(
-                            searched,
-                            _EvalUtils.CMP_OP_EQUALS, "case==", cas.condition, 
cas.condition, env);
+        if (usesOnDirective) {
+            processOnDirectives: for (int i = firstCaseOrOnIndex; i < ln; i++) 
{
+                ASTElement tel = getChild(i);
+
+                // "default" is always the last; the parser ensures this
+                if (tel == defaultCase) {
+                    env.executeElement(defaultCase);
+                    break;
                 }
-                if (processCase) {
-                    env.executeElement(cas);
-                    processedCase = true;
+
+                for (ASTExpression condition : ((ASTDirOn) tel).conditions) {
+                    boolean processOn = _EvalUtils.compare(
+                            searched,
+                            _EvalUtils.CMP_OP_EQUALS, "on==", condition, 
condition, env);
+                    if (processOn) {
+                        env.executeElement(tel);
+                        break processOnDirectives;
+                    }
                 }
             }
+        } else { // case-s
+            try {
+                for (int i = firstCaseOrOnIndex; i < ln; i++) {
+                    ASTDirCase cas = (ASTDirCase) fastGetChild(i);
+                    boolean processCase = false;
+
+                    // Fall through if a previous case tested true.
+                    if (processedCase) {
+                        processCase = true;
+                    } else if (cas.condition != null) {
+                        // Otherwise, if this case isn't the default, test it.
+                        processCase = _EvalUtils.compare(
+                                searched,
+                                _EvalUtils.CMP_OP_EQUALS, "case==", 
cas.condition, cas.condition, env);
+                    }
+                    if (processCase) {
+                        env.executeElement(cas);
+                        processedCase = true;
+                    }
+                }
 
-            // If we didn't process any nestedElements, and we have a default,
-            // process it.
-            if (!processedCase && defaultCase != null) {
-                env.executeElement(defaultCase);
+                // If we didn't process any nestedElements, and we have a 
default,
+                // process it.
+                if (!processedCase && defaultCase != null) {
+                    env.executeElement(defaultCase);
+                }
+            } catch (BreakOrContinueException br) {
+                // Catch #break, but not #continue
+                if (br == BreakOrContinueException.CONTINUE_INSTANCE) {
+                    throw br;
+                }
             }
-        } catch (BreakOrContinueException br) {
-            // #break was called
         }
+
         return null;
     }
 
@@ -139,10 +171,10 @@ final class ASTDirSwitch extends ASTDirective {
         // The first #case might have shifted in the child array, so we have 
to find it again:
         int ln = getChildCount();
         int i = 0;
-        while (i < ln && !(fastGetChild(i) instanceof ASTDirCase)) {
+        while (i < ln && !(fastGetChild(i) instanceof ASTDirCase || 
fastGetChild(i) instanceof ASTDirOn)) {
             i++;
         }
-        firstCaseIndex = i;
+        firstCaseOrOnIndex = i;
 
         return result;
     }
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java
index 40cdc90e..c7595845 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java
@@ -18,12 +18,12 @@
  */
 package org.apache.freemarker.core;
 
+import org.apache.freemarker.core.util.StringToIndexMap;
+
 import java.util.Collections;
 import java.util.Set;
 import java.util.TreeSet;
 
-import org.apache.freemarker.core.util.StringToIndexMap;
-
 /**
  * AST directive node superclass.
  * Concrete instances are normally created using {@link 
StaticallyLinkedNamespaceEntry#getDirectiveCallNodeFactory()}/
@@ -64,6 +64,7 @@ abstract class ASTDirective extends ASTElement {
         names.add("noEscape");
         names.add("noParse");
         names.add("nt");
+        names.add("on");
         names.add("outputFormat");
         names.add("recover");
         names.add("recurse");
diff --git a/freemarker-core/src/main/javacc/org/apache/freemarker/core/FTL.jj 
b/freemarker-core/src/main/javacc/org/apache/freemarker/core/FTL.jj
index 404ca5c1..6bfc789d 100644
--- a/freemarker-core/src/main/javacc/org/apache/freemarker/core/FTL.jj
+++ b/freemarker-core/src/main/javacc/org/apache/freemarker/core/FTL.jj
@@ -710,6 +710,8 @@ TOKEN:
     |
     <CASE : <START_TAG> "case" <BLANK>> { 
handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
     |
+    <ON : <START_TAG> "on" <BLANK>> { handleTagSyntaxAndSwitch(matchedToken, 
FM_EXPRESSION); }
+    |
     <ASSIGN : <START_TAG> "assign" <BLANK>> { 
handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
     |
     <GLOBALASSIGN : <START_TAG> "global" <BLANK>> { 
handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
@@ -3567,9 +3569,12 @@ ASTDirSwitch Switch() :
     ASTDirSwitch switchBlock;
     ASTImplicitParent ignoredSectionBeforeFirstCase = null;
     ASTDirCase caseOrDefault;
+    ASTDirOn on;
+    boolean hadCase = false;
+    boolean hadDefault = false;
+    boolean hadOn = false;
     ASTExpression switchExp;
     Token start, end;
-    boolean defaultFound = false;
 }
 {
     (
@@ -3579,32 +3584,73 @@ ASTDirSwitch Switch() :
         [ ignoredSectionBeforeFirstCase = WhitespaceAndComments() ]
     )
     {
-        breakableDirectiveNesting++;
+        breakableDirectiveNesting++; // Note that this will be undone when we 
find an "on" directive call.
         switchBlock = new ASTDirSwitch(switchExp, 
ignoredSectionBeforeFirstCase);
     }
+
     [
         (
             caseOrDefault = DirCase()
             {
                 if (caseOrDefault.condition == null) {
-                    if (defaultFound) {
+                    if (hadDefault) {
                         throw new ParseException(
-                                "You can only have one default case in a 
switch statement", template, start);
+                               "You already had a #default in the #switch 
block",
+                                caseOrDefault);
                     }
-                    defaultFound = true;
-                } else if (defaultFound) {
+                    hadDefault = true;
+                } else {
+                    if (hadOn) {
                         throw new ParseException(
-                                "You can't have a \"case\" directive after the 
\"default\" directive",
+                                "You can't use both #on, and #case in a 
#switch block, and you already had an #on.",
                                 caseOrDefault);
+                    }
+                    if (hadDefault) {
+                        throw new ParseException(
+                                "You can't have a #case directive after the 
#default directive",
+                                caseOrDefault);
+                    }
+                    hadCase = true;
                 }
                 switchBlock.addCase(caseOrDefault);
             }
+            |
+            (
+                {
+                    if (!hadOn) {
+                        // A #switch with #case handles #break specially, but 
not #switch with #on.
+                        // Also, this must be done before calling On(), as 
that consumes the nested #break already.
+                        breakableDirectiveNesting--;
+                    }
+                }
+
+                on = DirOn()
+                {
+                    if (hadCase) {
+                        throw new ParseException(
+                                "You can't use both #on, and #case in a 
#switch block, and you already had a #case.",
+                                on);
+                    }
+                    if (hadDefault) {
+                        throw new ParseException(
+                                "You can't use #on after #default in a #switch 
block; #default must come last.",
+                                on);
+                    }
+                    hadOn = true;
+
+                    switchBlock.addOn(on);
+                }
+            )
         )+
         [<STATIC_TEXT_WS>]
     ]
     end = <END_SWITCH>
     {
-        breakableDirectiveNesting--;
+        // If we had #on, then this was already decreased there
+        if (!hadOn) {
+            breakableDirectiveNesting--;
+        }
+
         switchBlock.setLocation(template, start, end);
         return switchBlock;
     }
@@ -3630,6 +3676,25 @@ ASTDirCase DirCase() :
     }
 }
 
+
+ASTDirOn DirOn() :
+{
+    ArrayList exps;
+    TemplateElements children;
+    Token start;
+}
+{
+    (
+        start = <ON> exps = PositionalArgs() <DIRECTIVE_END>
+    )
+    children = MixedContentElements()
+    {
+        ASTDirOn result = new ASTDirOn(exps, children);
+        result.setLocation(template, start, start, children);
+        return result;
+    }
+}
+
 ASTDirEscape Escape() :
 {
     Token variable, start, end;

Reply via email to