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;