http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/comparisons.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/comparisons.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/comparisons.ftl index a711d4d..3314765 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/comparisons.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/comparisons.ftl @@ -85,134 +85,134 @@ <#-- Signum-based optimization test, all 9 permutations: --> <#-- 1 --> -<@assert test= !(0 != 0) /> -<@assert test= (0 == 0) /> -<@assert test= !(0 > 0) /> -<@assert test= (0 >= 0) /> -<@assert test= !(0 < 0) /> -<@assert test= (0 <= 0) /> +<@assert !(0 != 0) /> +<@assert (0 == 0) /> +<@assert !(0 > 0) /> +<@assert (0 >= 0) /> +<@assert !(0 < 0) /> +<@assert (0 <= 0) /> <#-- 2 --> -<@assert test= !(3 != 3) /> -<@assert test= (3 == 3) /> -<@assert test= !(3 > 3) /> -<@assert test= (3 >= 3) /> -<@assert test= !(3 < 3) /> -<@assert test= (3 <= 3) /> +<@assert !(3 != 3) /> +<@assert (3 == 3) /> +<@assert !(3 > 3) /> +<@assert (3 >= 3) /> +<@assert !(3 < 3) /> +<@assert (3 <= 3) /> <#-- 3 --> -<@assert test= !(-3 != -3) /> -<@assert test= (-3 == -3) /> -<@assert test= !(-3 > -3) /> -<@assert test= (-3 >= -3) /> -<@assert test= !(-3 < -3) /> -<@assert test= (-3 <= -3) /> +<@assert !(-3 != -3) /> +<@assert (-3 == -3) /> +<@assert !(-3 > -3) /> +<@assert (-3 >= -3) /> +<@assert !(-3 < -3) /> +<@assert (-3 <= -3) /> <#-- 4 --> -<@assert test= (3 != 0) /> -<@assert test= !(3 == 0) /> -<@assert test= (3 > 0) /> -<@assert test= (3 >= 0) /> -<@assert test= !(3 < 0) /> -<@assert test= !(3 <= 0) /> +<@assert (3 != 0) /> +<@assert !(3 == 0) /> +<@assert (3 > 0) /> +<@assert (3 >= 0) /> +<@assert !(3 < 0) /> +<@assert !(3 <= 0) /> <#-- 5 --> -<@assert test= (0 != 3) /> -<@assert test= !(0 == 3) /> -<@assert test= !(0 > 3) /> -<@assert test= !(0 >= 3) /> -<@assert test= (0 < 3) /> -<@assert test= (0 <= 3) /> +<@assert (0 != 3) /> +<@assert !(0 == 3) /> +<@assert !(0 > 3) /> +<@assert !(0 >= 3) /> +<@assert (0 < 3) /> +<@assert (0 <= 3) /> <#-- 6 --> -<@assert test= (-3 != 0) /> -<@assert test= !(-3 == 0) /> -<@assert test= !(-3 > 0) /> -<@assert test= !(-3 >= 0) /> -<@assert test= (-3 < 0) /> -<@assert test= (-3 <= 0) /> +<@assert (-3 != 0) /> +<@assert !(-3 == 0) /> +<@assert !(-3 > 0) /> +<@assert !(-3 >= 0) /> +<@assert (-3 < 0) /> +<@assert (-3 <= 0) /> <#-- 7 --> -<@assert test= (0 != -3) /> -<@assert test= !(0 == -3) /> -<@assert test= (0 > -3) /> -<@assert test= (0 >= -3) /> -<@assert test= !(0 < -3) /> -<@assert test= !(0 <= -3) /> +<@assert (0 != -3) /> +<@assert !(0 == -3) /> +<@assert (0 > -3) /> +<@assert (0 >= -3) /> +<@assert !(0 < -3) /> +<@assert !(0 <= -3) /> <#-- 8 --> -<@assert test= (-3 != 3) /> -<@assert test= !(-3 == 3) /> -<@assert test= !(-3 > 3) /> -<@assert test= !(-3 >= 3) /> -<@assert test= (-3 < 3) /> -<@assert test= (-3 <= 3) /> +<@assert (-3 != 3) /> +<@assert !(-3 == 3) /> +<@assert !(-3 > 3) /> +<@assert !(-3 >= 3) /> +<@assert (-3 < 3) /> +<@assert (-3 <= 3) /> <#-- 9 --> -<@assert test= (3 != -3) /> -<@assert test= !(3 == -3) /> -<@assert test= (3 > -3) /> -<@assert test= (3 >= -3) /> -<@assert test= !(3 < -3) /> -<@assert test= !(3 <= -3) /> +<@assert (3 != -3) /> +<@assert !(3 == -3) /> +<@assert (3 > -3) /> +<@assert (3 >= -3) /> +<@assert !(3 < -3) /> +<@assert !(3 <= -3) /> <#-- Again, now on runtime: --> <#assign m3 = -3> <#assign p3 = 3> <#assign z = 0> <#-- 1 --> -<@assert test= !(z != z) /> -<@assert test= (z == z) /> -<@assert test= !(z > z) /> -<@assert test= (z >= z) /> -<@assert test= !(z < z) /> -<@assert test= (z <= z) /> +<@assert !(z != z) /> +<@assert (z == z) /> +<@assert !(z > z) /> +<@assert (z >= z) /> +<@assert !(z < z) /> +<@assert (z <= z) /> <#-- 2 --> -<@assert test= !(p3 != p3) /> -<@assert test= (p3 == p3) /> -<@assert test= !(p3 > p3) /> -<@assert test= (p3 >= p3) /> -<@assert test= !(p3 < p3) /> -<@assert test= (p3 <= p3) /> +<@assert !(p3 != p3) /> +<@assert (p3 == p3) /> +<@assert !(p3 > p3) /> +<@assert (p3 >= p3) /> +<@assert !(p3 < p3) /> +<@assert (p3 <= p3) /> <#-- 3 --> -<@assert test= !(m3 != m3) /> -<@assert test= (m3 == m3) /> -<@assert test= !(m3 > m3) /> -<@assert test= (m3 >= m3) /> -<@assert test= !(m3 < m3) /> -<@assert test= (m3 <= m3) /> +<@assert !(m3 != m3) /> +<@assert (m3 == m3) /> +<@assert !(m3 > m3) /> +<@assert (m3 >= m3) /> +<@assert !(m3 < m3) /> +<@assert (m3 <= m3) /> <#-- 4 --> -<@assert test= (p3 != z) /> -<@assert test= !(p3 == z) /> -<@assert test= (p3 > z) /> -<@assert test= (p3 >= z) /> -<@assert test= !(p3 < z) /> -<@assert test= !(p3 <= z) /> +<@assert (p3 != z) /> +<@assert !(p3 == z) /> +<@assert (p3 > z) /> +<@assert (p3 >= z) /> +<@assert !(p3 < z) /> +<@assert !(p3 <= z) /> <#-- 5 --> -<@assert test= (z != p3) /> -<@assert test= !(z == p3) /> -<@assert test= !(z > p3) /> -<@assert test= !(z >= p3) /> -<@assert test= (z < p3) /> -<@assert test= (z <= p3) /> +<@assert (z != p3) /> +<@assert !(z == p3) /> +<@assert !(z > p3) /> +<@assert !(z >= p3) /> +<@assert (z < p3) /> +<@assert (z <= p3) /> <#-- 6 --> -<@assert test= (m3 != z) /> -<@assert test= !(m3 == z) /> -<@assert test= !(m3 > z) /> -<@assert test= !(m3 >= z) /> -<@assert test= (m3 < z) /> -<@assert test= (m3 <= z) /> +<@assert (m3 != z) /> +<@assert !(m3 == z) /> +<@assert !(m3 > z) /> +<@assert !(m3 >= z) /> +<@assert (m3 < z) /> +<@assert (m3 <= z) /> <#-- 7 --> -<@assert test= (z != m3) /> -<@assert test= !(z == m3) /> -<@assert test= (z > m3) /> -<@assert test= (z >= m3) /> -<@assert test= !(z < m3) /> -<@assert test= !(z <= m3) /> +<@assert (z != m3) /> +<@assert !(z == m3) /> +<@assert (z > m3) /> +<@assert (z >= m3) /> +<@assert !(z < m3) /> +<@assert !(z <= m3) /> <#-- 8 --> -<@assert test= (m3 != p3) /> -<@assert test= !(m3 == p3) /> -<@assert test= !(m3 > p3) /> -<@assert test= !(m3 >= p3) /> -<@assert test= (m3 < p3) /> -<@assert test= (m3 <= p3) /> +<@assert (m3 != p3) /> +<@assert !(m3 == p3) /> +<@assert !(m3 > p3) /> +<@assert !(m3 >= p3) /> +<@assert (m3 < p3) /> +<@assert (m3 <= p3) /> <#-- 9 --> -<@assert test= (p3 != m3) /> -<@assert test= !(p3 == m3) /> -<@assert test= (p3 > m3) /> -<@assert test= (p3 >= m3) /> -<@assert test= !(p3 < m3) /> -<@assert test= !(p3 <= m3) /> +<@assert (p3 != m3) /> +<@assert !(p3 == m3) /> +<@assert (p3 > m3) /> +<@assert (p3 >= m3) /> +<@assert !(p3 < m3) /> +<@assert !(p3 <= m3) /> </body> </html>
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/date-type-builtins.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/date-type-builtins.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/date-type-builtins.ftl index 2cadc42..9e3025d 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/date-type-builtins.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/date-type-builtins.ftl @@ -18,25 +18,25 @@ --> <#setting timeZone = "UTC"> -<@assert test=unknown?isUnknownDateLike /> -<@assert test=!timeOnly?isUnknownDateLike /> -<@assert test=!dateOnly?isUnknownDateLike /> -<@assert test=!dateTime?isUnknownDateLike /> +<@assert unknown?isUnknownDateLike /> +<@assert !timeOnly?isUnknownDateLike /> +<@assert !dateOnly?isUnknownDateLike /> +<@assert !dateTime?isUnknownDateLike /> -<@assert test=!unknown?isDateOnly /> -<@assert test=!timeOnly?isDateOnly /> -<@assert test=dateOnly?isDateOnly /> -<@assert test=!dateTime?isDateOnly /> +<@assert !unknown?isDateOnly /> +<@assert !timeOnly?isDateOnly /> +<@assert dateOnly?isDateOnly /> +<@assert !dateTime?isDateOnly /> -<@assert test=!unknown?isTime /> -<@assert test=timeOnly?isTime /> -<@assert test=!dateOnly?isTime /> -<@assert test=!dateTime?isTime /> +<@assert !unknown?isTime /> +<@assert timeOnly?isTime /> +<@assert !dateOnly?isTime /> +<@assert !dateTime?isTime /> -<@assert test=!unknown?isDatetime /> -<@assert test=!timeOnly?isDatetime /> -<@assert test=!dateOnly?isDatetime /> -<@assert test=dateTime?isDatetime /> +<@assert !unknown?isDatetime /> +<@assert !timeOnly?isDatetime /> +<@assert !dateOnly?isDatetime /> +<@assert dateTime?isDatetime /> <@assertFails message="isn't known if">${unknown?string.xs}</@> <@assertEquals expected="2003-04-05T06:07:08Z" actual=unknown?dateTimeIfUnknown?string.xs /> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/dateparsing.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/dateparsing.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/dateparsing.ftl index 401371e..753dd4b 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/dateparsing.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/dateparsing.ftl @@ -42,8 +42,8 @@ <#assign gmt01Str='1998-10-30T19:30:44.512'?dateTime.xs?string /> <#setting timeZone="default"> <#assign defStr='1998-10-30T19:30:44.512'?dateTime.xs?string /> -<@assert test = gmtStr != gmt01Str /> -<@assert test = defStr != gmtStr || defStr != gmt01Str /> +<@assert gmtStr != gmt01Str /> +<@assert defStr != gmtStr || defStr != gmt01Str /> <#assign refDate = "AD 1998-10-30 +0000"?date> <#assign refTime = "15:30:44.512 +0000"?time> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/existence-operators.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/existence-operators.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/existence-operators.ftl index d8122bf..916d91e 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/existence-operators.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/existence-operators.ftl @@ -46,19 +46,19 @@ <#list ['V', 1.5] as v> <@assertEquals actual=v!'-' expected=v /> <@assertEquals actual=(v)!'-' expected=v /> - <@assert test=v?? /> - <@assert test=(v)?? /> + <@assert v?? /> + <@assert (v)?? /> <@assertEquals actual=v?default('-') expected=v /> <@assertEquals actual=(v)?default('-') expected=v /> - <@assert test=v?exists /> - <@assert test=(v)?exists /> + <@assert v?exists /> + <@assert (v)?exists /> <@assertEquals actual=v?ifExists expected=v /> <@assertEquals actual=(v)?ifExists expected=v /> - <@assert test=v?hasContent /> - <@assert test=(v)?hasContent /> + <@assert v?hasContent /> + <@assert (v)?hasContent /> </#list> -<@assert test=!v?? /> -<@assert test=!v?exists /> +<@assert !v?? /> +<@assert !v?exists /> <@isNonFastIRE>${v}</@> <#-- To check that it isn't an IRE.FAST_INSTANCE --> <@isIRE>${u.v!'-'}</@> @@ -91,16 +91,16 @@ <#assign u = { 'v': 'V' } > <@assertEquals actual=u.v!'-' expected='V' /> <@assertEquals actual=(u.v)!'-' expected='V' /> -<@assert test=u.v?? /> -<@assert test=(u.v)?? /> +<@assert u.v?? /> +<@assert (u.v)?? /> <@assertEquals actual=u.v?default('-') expected='V' /> <@assertEquals actual=(u.v)?default('-') expected='V' /> -<@assert test=u.v?exists /> -<@assert test=(u.v)?exists /> +<@assert u.v?exists /> +<@assert (u.v)?exists /> <@assertEquals actual=u.v?ifExists expected='V' /> <@assertEquals actual=(u.v)?ifExists expected='V' /> -<@assert test=u.v?hasContent /> -<@assert test=(u.v)?hasContent /> +<@assert u.v?hasContent /> +<@assert (u.v)?hasContent /> <#list 1..4 as i> <#if i == 3><#assign x = 'X'></#if> @@ -111,7 +111,7 @@ <#attempt> ${fails} <#recover> - <@assert test=isNonFastIREMessage(.error) /> + <@assert isNonFastIREMessage(.error) /> </#attempt> </#macro> <@attemptTest /> @@ -121,12 +121,12 @@ ${(callMacroFromExpression(attemptTest))!} <#attempt> <@interpretTest /> <#recover> - <@assert test=isNonFastIREMessage(.error) /> + <@assert isNonFastIREMessage(.error) /> </#attempt> <#attempt> ${(callMacroFromExpression(interpretTest))!} <#recover> - <@assert test=isNonFastIREMessage(.error) /> + <@assert isNonFastIREMessage(.error) /> </#attempt> <@assertEquals actual='fails'?eval!'-' expected='-' /> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashconcat.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashconcat.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashconcat.ftl index 127d828..362f2fa 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashconcat.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashconcat.ftl @@ -48,7 +48,7 @@ a + b + {} + b + {} + a: [@dump a + b + {} + b + {} + a /] -[#macro dump s] +[#macro dump s{positional}] [#list s?keys as k] ${k} = ${s[k]} [/#list] http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-non-ascii.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-non-ascii.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-non-ascii.ftl index 59a4b0e..3050064 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-non-ascii.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-non-ascii.ftl @@ -18,4 +18,4 @@ --> <#macro árvÃztűrÅTükörfúrógép ×ֶקס×>${×ֶקס×}</#macro> <#assign íêµì´_í¤ë³´ë="Korean Keyboard"> -<@árvÃztűrÅTükörfúrógép íêµì´_í¤ë³´ë /> \ No newline at end of file +<@árvÃztűrÅTükörfúrógép ×ֶקס×=íêµì´_í¤ë³´ë /> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list.ftl index 280491c..8ce79d4 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list.ftl @@ -38,7 +38,7 @@ <@testList listables.emptyIterator /> -<#macro testList seq> +<#macro testList seq{positional}> Size: <#attempt>${seq?size}<#recover>failed</#attempt> Items: <#list seq as i>@${i_index} ${i}<#if i_has_next>, <#else>.</#if></#list> </#macro> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list2.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list2.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list2.ftl index 6ac21d5..9893c1b 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list2.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list2.ftl @@ -34,7 +34,7 @@ <@testList listables.getEmptyIterator /> -<#macro testList xs> +<#macro testList xs{positional}> === [${resolve(xs)?join(", ")}] === <#assign resolveCallCnt = 0> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list3.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list3.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list3.ftl index d2fcf71..293a643 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list3.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/list3.ftl @@ -22,7 +22,7 @@ </#list> ] -<#macro hits xs style=""> +<#macro hits xs{positional} style=""> <#list xs> <p>${xs?size} hits: <div class="hits"> @@ -50,9 +50,9 @@ <@hits ['a', 'b'] /> -<@hits ['a', 'b'], "other" /> +<@hits ['a', 'b'] style="other" /> -<@hits ['a', 'b'], "hidden" /> +<@hits ['a', 'b'] style="hidden" /> <@hits [] /> @@ -63,7 +63,7 @@ <@testAutoClosedSep [1] /> <@testAutoClosedSep [] /> -<#macro testAutoClosedSep xs> +<#macro testAutoClosedSep xs{positional}> <#list xs as x>${x}<#sep>, <#else>Empty</#list> <#list xs as x>${x}<#sep><#if x_index == 0> /*first*/, <#else>, </#if><#else>Empty</#list> <#list xs>[<#items as x>${x}<#sep>, </#items>]<#else>Empty</#list> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/listhash.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/listhash.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/listhash.ftl index 6f22b43..f74ce65 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/listhash.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/listhash.ftl @@ -18,7 +18,7 @@ --> <#setting booleanFormat='Y,N'> -<#macro listings maps> +<#macro listings maps{positional}> <#list maps as m> Map: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl index b746321..181326c 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl @@ -45,7 +45,7 @@ <p>Function is defined, now let's call it:</p> - <@español urls.home, images.home, "Home" /><#t> + <@español url=urls.home image=images.home alt="Home" /><#t> <p>Again, but with different parameters:</p> @@ -63,7 +63,7 @@ <p>A recursive function call:</p> -<#macro recurse dummy a=3> +<#macro recurse dummy{positional}, a{positional}=3> <#if (a > 0)> <@recurse dummy, a - 1 /> </#if> @@ -83,19 +83,18 @@ foo=${foo} baz=[<#list bar?keys?sort as key>${key}=${bar[key]}<#if key_has_next> <@catchall foo="a" bar="b"/> <@catchall foo="a" bar="b" baz="c"/> -<#macro fmt pattern args...> - <#list args as arg> - <#local pattern = pattern?replace("{" + arg_index + "}", arg)> +<#macro fmt pattern{positional} args...> + <#list args as k, v> + <#local pattern = pattern?replace("{" + k + "}", v)> </#list> ${pattern}<#lt> </#macro> -<#macro m a=1 b=2> -</#macro> +<#macro m a=1 b=2></#macro> <@assertFails message='"c"'><@m c=3 /></@> -<@assertFails message='3'><@m 9, 8, 7 /></@> +<@assertFails message='position'><@m 1, 2 /></@> -<@fmt "Hello {0}! Today is {1}.", "World", "Monday" /> +<@fmt "Hello {name}! Today is {today}." name="World" today="Monday" /> </body> </html> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros2.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros2.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros2.ftl deleted file mode 100644 index 55ceefd..0000000 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros2.ftl +++ /dev/null @@ -1,35 +0,0 @@ -<#-- - 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. ---> -<#macro m1 a b=a> -${a} ${b} -</#macro> -<@m1 a="1"/> -<#macro m2 a=b b=""> -${a} ${b} -</#macro> -<@m2 b="2"/> -<#macro m3 d b=c[a] a=d c={"3":"4"}> -${b} -</#macro> -<@m3 d="3"/> -<#attempt> -<@m3 d="4"/> -<#recover> -m3 with d="4" Failed! -</#attempt> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/number-math-builtins.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/number-math-builtins.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/number-math-builtins.ftl index 19e37d5..2951f6e 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/number-math-builtins.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/number-math-builtins.ftl @@ -22,14 +22,14 @@ <@assertEquals actual=3.5?abs expected=3.5 /> <@assertEquals actual=(-3.5)?abs expected=3.5 /> -<@assert test=fNan?abs?isNan /> -<@assert test=dNan?abs?isNan /> -<@assert test=fNinf?abs?isInfinite /> -<@assert test=dPinf?abs?isInfinite /> -<@assert test=fNinf lt 0 /> -<@assert test=dPinf gt 0 /> -<@assert test=fNinf?abs gt 0 /> -<@assert test=dPinf?abs gt 0 /> +<@assert fNan?abs?isNan /> +<@assert dNan?abs?isNan /> +<@assert fNinf?abs?isInfinite /> +<@assert dPinf?abs?isInfinite /> +<@assert fNinf lt 0 /> +<@assert dPinf gt 0 /> +<@assert fNinf?abs gt 0 /> +<@assert dPinf?abs gt 0 /> <@assertEquals actual=fn?abs expected=0.05 /> <@assertEquals actual=dn?abs expected=0.05 /> @@ -49,30 +49,30 @@ <@assertEquals actual=bip?abs expected=5 /> <@assertEquals actual=bdp?abs expected=0.05 /> -<@assert test=!0?isInfinite /> -<@assert test=!fn?isInfinite /> -<@assert test=!dn?isInfinite /> -<@assert test=!ineg?isInfinite /> -<@assert test=!ln?isInfinite /> -<@assert test=!sn?isInfinite /> -<@assert test=!bn?isInfinite /> -<@assert test=!bin?isInfinite /> -<@assert test=!bdn?isInfinite /> -<@assert test=!fNan?isInfinite /> -<@assert test=!dNan?isInfinite /> -<@assert test=fNinf?isInfinite /> -<@assert test=dPinf?isInfinite /> +<@assert !0?isInfinite /> +<@assert !fn?isInfinite /> +<@assert !dn?isInfinite /> +<@assert !ineg?isInfinite /> +<@assert !ln?isInfinite /> +<@assert !sn?isInfinite /> +<@assert !bn?isInfinite /> +<@assert !bin?isInfinite /> +<@assert !bdn?isInfinite /> +<@assert !fNan?isInfinite /> +<@assert !dNan?isInfinite /> +<@assert fNinf?isInfinite /> +<@assert dPinf?isInfinite /> -<@assert test=!0?isNan /> -<@assert test=!fn?isNan /> -<@assert test=!dn?isNan /> -<@assert test=!ineg?isNan /> -<@assert test=!ln?isNan /> -<@assert test=!sn?isNan /> -<@assert test=!bn?isNan /> -<@assert test=!bin?isNan /> -<@assert test=!bdn?isNan /> -<@assert test=fNan?isNan /> -<@assert test=dNan?isNan /> -<@assert test=!fNinf?isNan /> -<@assert test=!dPinf?isNan /> \ No newline at end of file +<@assert !0?isNan /> +<@assert !fn?isNan /> +<@assert !dn?isNan /> +<@assert !ineg?isNan /> +<@assert !ln?isNan /> +<@assert !sn?isNan /> +<@assert !bn?isNan /> +<@assert !bin?isNan /> +<@assert !bdn?isNan /> +<@assert fNan?isNan /> +<@assert dNan?isNan /> +<@assert !fNinf?isNan /> +<@assert !dPinf?isNan /> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/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 afcc423..3fd916b 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 @@ -25,15 +25,15 @@ <#attempt> Let's try to output an undefined variable: ${undefinedVariable} <#recover> - Well, that did not work.<@assert test=.error?contains('undefinedVariable') /> + Well, that did not work.<@assert .error?contains('undefinedVariable') /> Now we nest another attempt/recover here: <#attempt> ${sequence[1]} <#recover> - Oops...<@assert test=.error?contains('sequence[1]') /> + Oops...<@assert .error?contains('sequence[1]') /> Remember, freeMarker sequences are zero-based! ${sequence[0]} </#attempt> - Now we check the current error message.<@assert test=.error?contains('undefinedVariable') /> + Now we check the current error message.<@assert .error?contains('undefinedVariable') /> </#attempt> <#attempt> <#include "nonexistent_template"> @@ -43,5 +43,5 @@ <#attempt> <#include "undefined.ftl"> <#recover> - The included template had a problem.<@assert test=.error?contains('undefined_variable') /> + The included template had a problem.<@assert .error?contains('undefined_variable') /> </#attempt> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/root.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/root.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/root.ftl index d4ca2c3..e6b19a0 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/root.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/root.ftl @@ -36,7 +36,7 @@ <p>Ensure that root lookups are unaffected by local variables:</p> -<#macro test message> +<#macro test message{positional}> ${.dataModel.message} ${message} </#macro> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/sequence-builtins.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/sequence-builtins.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/sequence-builtins.ftl index b86feb9..7eb3453 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/sequence-builtins.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/sequence-builtins.ftl @@ -310,17 +310,17 @@ Chunk <#assign ls = ['a', 'b', 'c', 'd', 'e', 'f', 'g']> <#list ['NULL', '-'] as fill> <#list [1, 2, 3, 4, 5, 10] as columns> - <@printTable ls, columns, fill /> + <@printTable ls columns=columns fill=fill /> </#list> </#list> -<@printTable [1, 2, 3, 4, 5, 6, 7, 8, 9], 3, 'NULL' /> -<@printTable [1, 2, 3, 4, 5, 6, 7, 8, 9], 3, '-' /> -<@printTable [1], 3, 'NULL' /> -<@printTable [1], 3, '-' /> -<@printTable [], 3, 'NULL' /> -<@printTable [], 3, '-' /> - -<#macro printTable ls columns fill> +<@printTable [1, 2, 3, 4, 5, 6, 7, 8, 9] columns=3 fill='NULL' /> +<@printTable [1, 2, 3, 4, 5, 6, 7, 8, 9] columns=3 fill='-' /> +<@printTable [1] columns=3 fill='NULL' /> +<@printTable [1] columns=3 fill='-' /> +<@printTable [] columns=3 fill='NULL' /> +<@printTable [] columns=3 fill='-' /> + +<#macro printTable ls{positional} columns fill> columns = ${columns}, fill = ${fill}:<#lt> <#if fill=='NULL'> <#local rows = ls?chunk(columns)> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtin-coercion.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtin-coercion.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtin-coercion.ftl index 475d0cb..40bb4ad 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtin-coercion.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtin-coercion.ftl @@ -18,17 +18,17 @@ --> Was broken 2.3.19: <#setting numberFormat="0.#"> -<@assert test=1232?contains('2') /> -<@assert test=1232?indexOf('2') == 1 /> -<@assert test=1232?lastIndexOf('2') == 3 /> -<@assert test=1232?leftPad(6) == ' 1232' /><@assert test=1232?leftPad(6, '0') == '001232' /> -<@assert test=1232?rightPad(6) == '1232 ' /><@assert test=1232?rightPad(6, '0') == '123200' /> -<@assert test=1232?matches('[1-3]+') /> -<@assert test=1232?replace('2', 'z') == '1z3z' /> -<@assert test=1232?replace('2', 'z', 'r') == '1z3z' /> -<@assert test=1232?split('2')[1] == '3' /><@assert test=1232?split('2')[2] == '' /> -<@assert test=1232?split('2', 'r')[1] == '3' /> +<@assert 1232?contains('2') /> +<@assert 1232?indexOf('2') == 1 /> +<@assert 1232?lastIndexOf('2') == 3 /> +<@assert 1232?leftPad(6) == ' 1232' /><@assert 1232?leftPad(6, '0') == '001232' /> +<@assert 1232?rightPad(6) == '1232 ' /><@assert 1232?rightPad(6, '0') == '123200' /> +<@assert 1232?matches('[1-3]+') /> +<@assert 1232?replace('2', 'z') == '1z3z' /> +<@assert 1232?replace('2', 'z', 'r') == '1z3z' /> +<@assert 1232?split('2')[1] == '3' /><@assert 1232?split('2')[2] == '' /> +<@assert 1232?split('2', 'r')[1] == '3' /> Was no broken in 2.3.19: -<@assert test=1232?startsWith('12') /> -<@assert test=1232?endsWith('32') /> +<@assert 1232?startsWith('12') /> +<@assert 1232?endsWith('32') /> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtins-regexps.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtins-regexps.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtins-regexps.ftl index 5b506ed..f3a4a9f 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtins-regexps.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/string-builtins-regexps.ftl @@ -42,8 +42,8 @@ ${"test\nFoo"?matches('.*\n^foo', 'mi')?string} == true ${"test\nFoo"?matches('.*^foo', 'ism')?string} == true ${"test\nFoo"?matches('.*^foo', 'smi')?string} == true <#setting booleanFormat="True,False"> -<@assert test=false?matches('[eslaF]+') /> -<@assert test='False'?matches('[eslaF]+') /> +<@assert false?matches('[eslaF]+') /> +<@assert 'False'?matches('[eslaF]+') /> <#assign s = "Code without test coverage\nis considered to be BROKEN"> @@ -124,7 +124,7 @@ ${false?replace('[abc]', 'A', 'r')} == FAlse Fails in 2.4 </#attempt> -<#macro dumpList xs>[<#list xs as x>${x}<#if x_has_next>, </#if></#list>]</#macro> +<#macro dumpList xs{positional}>[<#list xs as x>${x}<#if x_has_next>, </#if></#list>]</#macro> <@dumpList "fooXbarxbaaz"?split("X") /> == [foo, barxbaaz] <@dumpList "fooXbarxbaaz"?split("X", "") /> == [foo, barxbaaz] <@dumpList "fooXbarxbaaz"?split("X", "i") /> == [foo, bar, baaz] http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/var-layers.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/var-layers.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/var-layers.ftl index 32a6119..80e5c2a 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/var-layers.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/var-layers.ftl @@ -30,7 +30,7 @@ Invisiblity test 3.: <#global q = 1><#if .main.q?exists || .namespace.q?exists | -- <@lib.foo/> -- -<#macro foo x> +<#macro foo x{positional}> ${x} = ${.locals.x} <#local x = 2> ${x} = ${.locals.x} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml index d79ee43..7ed2215 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml @@ -117,7 +117,6 @@ Note that for the incompatibleImprovements setting you can specify a list of ver </testCase> <testCase name="loopvariable" /> <testCase name="macros"/> - <testCase name="macros2"/> <testCase name="macros-return"/> <testCase name="multimodels"/> <testCase name="nested" /> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacro.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacro.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacro.java deleted file mode 100644 index 4bed2fd..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacro.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * 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.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; - -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateModelIterator; -import org.apache.freemarker.core.util._StringUtil; - -/** - * AST directive node: {@code #macro} - */ -final class ASTDirMacro extends ASTDirective implements TemplateModel { - - static final ASTDirMacro DO_NOTHING_MACRO = new ASTDirMacro(".pass", - Collections.EMPTY_LIST, - Collections.EMPTY_MAP, - null, false, - TemplateElements.EMPTY); - - final static int TYPE_MACRO = 0; - final static int TYPE_FUNCTION = 1; - - private final String name; - private final String[] paramNames; - private final Map paramDefaults; - private final String catchAllParamName; - private final boolean function; - - ASTDirMacro(String name, List argumentNames, Map args, - String catchAllParamName, boolean function, - TemplateElements children) { - this.name = name; - paramNames = (String[]) argumentNames.toArray(new String[argumentNames.size()]); - paramDefaults = args; - - this.function = function; - this.catchAllParamName = catchAllParamName; - - setChildren(children); - } - - public String getCatchAll() { - return catchAllParamName; - } - - public String[] getArgumentNames() { - return paramNames.clone(); - } - - String[] getArgumentNamesInternal() { - return paramNames; - } - - boolean hasArgNamed(String name) { - return paramDefaults.containsKey(name); - } - - public String getName() { - return name; - } - - @Override - ASTElement[] accept(Environment env) { - env.visitMacroDef(this); - return null; - } - - @Override - protected String dump(boolean canonical) { - StringBuilder sb = new StringBuilder(); - if (canonical) sb.append('<'); - sb.append(getASTNodeDescriptor()); - sb.append(' '); - sb.append(_StringUtil.toFTLTopLevelTragetIdentifier(name)); - if (function) sb.append('('); - int argCnt = paramNames.length; - for (int i = 0; i < argCnt; i++) { - if (function) { - if (i != 0) { - sb.append(", "); - } - } else { - sb.append(' '); - } - String argName = paramNames[i]; - sb.append(_StringUtil.toFTLTopLevelIdentifierReference(argName)); - if (paramDefaults != null && paramDefaults.get(argName) != null) { - sb.append('='); - ASTExpression defaultExpr = (ASTExpression) paramDefaults.get(argName); - if (function) { - sb.append(defaultExpr.getCanonicalForm()); - } else { - MessageUtil.appendExpressionAsUntearable(sb, defaultExpr); - } - } - } - if (catchAllParamName != null) { - if (function) { - if (argCnt != 0) { - sb.append(", "); - } - } else { - sb.append(' '); - } - sb.append(catchAllParamName); - sb.append("..."); - } - if (function) sb.append(')'); - if (canonical) { - sb.append('>'); - sb.append(getChildrenCanonicalForm()); - sb.append("</").append(getASTNodeDescriptor()).append('>'); - } - return sb.toString(); - } - - @Override - String getASTNodeDescriptor() { - return function ? "#function" : "#macro"; - } - - public boolean isFunction() { - return function; - } - - class Context implements LocalContext { - final Environment.Namespace localVars; - final ASTElement[] nestedContentBuffer; - final Environment.Namespace nestedContentNamespace; - final List<String> nestedContentParameterNames; - final LocalContextStack prevLocalContextStack; - final Context prevMacroContext; - - Context(Environment env, - ASTElement[] nestedContentBuffer, - List<String> nestedContentParameterNames) { - localVars = env.new Namespace(); - this.nestedContentBuffer = nestedContentBuffer; - nestedContentNamespace = env.getCurrentNamespace(); - this.nestedContentParameterNames = nestedContentParameterNames; - prevLocalContextStack = env.getLocalContextStack(); - prevMacroContext = env.getCurrentMacroContext(); - } - - - ASTDirMacro getMacro() { - return ASTDirMacro.this; - } - - // Set default parameters, check if all the required parameters are defined. - void sanityCheck(Environment env) throws TemplateException { - boolean resolvedAnArg, hasUnresolvedArg; - ASTExpression firstUnresolvedExpression; - InvalidReferenceException firstReferenceException; - do { - firstUnresolvedExpression = null; - firstReferenceException = null; - resolvedAnArg = hasUnresolvedArg = false; - for (int i = 0; i < paramNames.length; ++i) { - String argName = paramNames[i]; - if (localVars.get(argName) == null) { - ASTExpression valueExp = (ASTExpression) paramDefaults.get(argName); - if (valueExp != null) { - try { - TemplateModel tm = valueExp.eval(env); - if (tm == null) { - if (!hasUnresolvedArg) { - firstUnresolvedExpression = valueExp; - hasUnresolvedArg = true; - } - } else { - localVars.put(argName, tm); - resolvedAnArg = true; - } - } catch (InvalidReferenceException e) { - if (!hasUnresolvedArg) { - hasUnresolvedArg = true; - firstReferenceException = e; - } - } - } else { - boolean argWasSpecified = localVars.containsKey(argName); - throw new _MiscTemplateException(env, - new _ErrorDescriptionBuilder( - "When calling macro ", new _DelayedJQuote(name), - ", required parameter ", new _DelayedJQuote(argName), - " (parameter #", Integer.valueOf(i + 1), ") was ", - (argWasSpecified - ? "specified, but had null/missing value." - : "not specified.") - ).tip(argWasSpecified - ? new Object[] { - "If the parameter value expression on the caller side is known to " - + "be legally null/missing, you may want to specify a default " - + "value for it with the \"!\" operator, like " - + "paramValue!defaultValue." } - : new Object[] { - "If the omission was deliberate, you may consider making the " - + "parameter optional in the macro by specifying a default value " - + "for it, like ", "<#macro macroName paramName=defaultExpr>", ")" } - )); - } - } - } - } while (resolvedAnArg && hasUnresolvedArg); - if (hasUnresolvedArg) { - if (firstReferenceException != null) { - throw firstReferenceException; - } else { - throw InvalidReferenceException.getInstance(firstUnresolvedExpression, env); - } - } - } - - /** - * @return the local variable of the given name - * or null if it doesn't exist. - */ - @Override - public TemplateModel getLocalVariable(String name) throws TemplateModelException { - return localVars.get(name); - } - - Environment.Namespace getLocals() { - return localVars; - } - - /** - * Set a local variable in this macro - */ - void setLocalVar(String name, TemplateModel var) { - localVars.put(name, var); - } - - @Override - public Collection<String> getLocalVariableNames() throws TemplateModelException { - HashSet<String> result = new HashSet<>(); - for (TemplateModelIterator it = localVars.keys().iterator(); it.hasNext(); ) { - result.add(it.next().toString()); - } - return result; - } - } - - @Override - int getParameterCount() { - return 1/*name*/ + paramNames.length * 2/*name=default*/ + 1/*catchAll*/ + 1/*type*/; - } - - @Override - Object getParameterValue(int idx) { - if (idx == 0) { - return name; - } else { - final int argDescsEnd = paramNames.length * 2 + 1; - if (idx < argDescsEnd) { - String paramName = paramNames[(idx - 1) / 2]; - if (idx % 2 != 0) { - return paramName; - } else { - return paramDefaults.get(paramName); - } - } else if (idx == argDescsEnd) { - return catchAllParamName; - } else if (idx == argDescsEnd + 1) { - return function ? TYPE_FUNCTION : TYPE_MACRO; - } else { - throw new IndexOutOfBoundsException(); - } - } - } - - @Override - ParameterRole getParameterRole(int idx) { - if (idx == 0) { - return ParameterRole.ASSIGNMENT_TARGET; - } else { - final int argDescsEnd = paramNames.length * 2 + 1; - if (idx < argDescsEnd) { - if (idx % 2 != 0) { - return ParameterRole.PARAMETER_NAME; - } else { - return ParameterRole.PARAMETER_DEFAULT; - } - } else if (idx == argDescsEnd) { - return ParameterRole.CATCH_ALL_PARAMETER_NAME; - } else if (idx == argDescsEnd + 1) { - return ParameterRole.AST_NODE_SUBTYPE; - } else { - throw new IndexOutOfBoundsException(); - } - } - } - - @Override - boolean isNestedBlockRepeater() { - // Because of recursive calls - return true; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java new file mode 100644 index 0000000..6aecfff --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java @@ -0,0 +1,369 @@ +/* + * 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.Collection; +import java.util.HashSet; +import java.util.List; + +import org.apache.freemarker.core.model.ArgumentArrayLayout; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.util.StringToIndexMap; +import org.apache.freemarker.core.util._StringUtil; + +/** + * AST directive node: {@code #macro} or {@code #function} + */ +final class ASTDirMacroOrFunction extends ASTDirective implements TemplateModel { + + static final ASTDirMacroOrFunction PASS_MACRO = new ASTDirMacroOrFunction( + false, ".pass", + null, null, + null, null, + TemplateElements.EMPTY); + + static final int TYPE_MACRO = 0; + static final int TYPE_FUNCTION = 1; + + static final String POSITIONAL_PARAMETER_OPTION_NAME = "positional"; + static final String NAMED_PARAMETER_OPTION_NAME = "named"; + + private final String name; + private final boolean function; + /** See {@link #getParameterDefinitionByArgumentArrayIndex()}. */ + private final ParameterDefinition[] paramDefsByArgArrayIdx; + private final ArgumentArrayLayout argsLayout; + + ASTDirMacroOrFunction( + boolean function, + String name, + List<ParameterDefinition> predefPosParamDefs, + ParameterDefinition posVarargsParamDef, + List<ParameterDefinition> predefNamedParamDefs, + ParameterDefinition namedVarargsParamDef, + TemplateElements children) { + this.function = function; + this.name = name; + + int predefPosParamsCnt = predefPosParamDefs != null ? predefPosParamDefs.size() : 0; + int predefNamedParamsCnt = predefNamedParamDefs != null ? predefNamedParamDefs.size() : 0; + + paramDefsByArgArrayIdx = new ParameterDefinition[ + predefPosParamsCnt + predefNamedParamsCnt + + (posVarargsParamDef != null ? 1 : 0) + (namedVarargsParamDef != null ? 1 : 0)]; + + int dstIdx = 0; + if (predefPosParamDefs != null) { + for (ParameterDefinition predefPosParam : predefPosParamDefs) { + paramDefsByArgArrayIdx[dstIdx++] = predefPosParam; + } + } + + StringToIndexMap.Entry[] nameToIdxEntries; + if (predefNamedParamsCnt != 0) { + nameToIdxEntries = new StringToIndexMap.Entry[predefNamedParamsCnt]; + for (int i = 0; i < predefNamedParamsCnt; i++) { + ParameterDefinition predefNamedParam = predefNamedParamDefs.get(i); + paramDefsByArgArrayIdx[dstIdx] = predefNamedParam; + nameToIdxEntries[i] = new StringToIndexMap.Entry(predefNamedParam.name, dstIdx); + dstIdx++; + } + } else { + nameToIdxEntries = null; + } + + if (posVarargsParamDef != null) { + paramDefsByArgArrayIdx[dstIdx++] = posVarargsParamDef; + } + if (namedVarargsParamDef != null) { + paramDefsByArgArrayIdx[dstIdx++] = namedVarargsParamDef; + } + + argsLayout = ArgumentArrayLayout.create( + predefPosParamsCnt, + posVarargsParamDef != null, + nameToIdxEntries != null ? StringToIndexMap.of(nameToIdxEntries) : null, + namedVarargsParamDef != null); + + setChildren(children); + } + + public String getName() { + return name; + } + + @Override + ASTElement[] accept(Environment env) { + env.visitMacroOrFunctionDefinition(this); + return null; + } + + @Override + String getASTNodeDescriptor() { + return function ? "#function" : "#macro"; + } + + boolean isFunction() { + return function; + } + + ArgumentArrayLayout getArgumentArrayLayout() { + return argsLayout; + } + + /** + * Returns the definition of the parameters by the index according the {@link ArgumentArrayLayout}. + */ + ParameterDefinition[] getParameterDefinitionByArgumentArrayIndex() { + return paramDefsByArgArrayIdx; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append(getASTNodeDescriptor()); + sb.append(' '); + sb.append(_StringUtil.toFTLTopLevelTragetIdentifier(name)); + + if (function) sb.append('('); + + boolean firstParam = true; + // Positional params: + int predefPosArgCnt = argsLayout.getPredefinedPositionalArgumentCount(); + for (int idx = 0; idx < predefPosArgCnt; idx++) { + if (!firstParam) { + sb.append(", "); + } else { + if (!function) sb.append(" "); + firstParam = false; + } + + ParameterDefinition paramDef = paramDefsByArgArrayIdx[idx]; + + sb.append(_StringUtil.toFTLTopLevelIdentifierReference(paramDef.name)); + if (!function) { + sb.append("{").append(POSITIONAL_PARAMETER_OPTION_NAME).append("}"); + } + + if (paramDef.defaultExpression != null) { + sb.append('='); + sb.append(paramDef.defaultExpression.getCanonicalForm()); + } + } + int posVarargsArgIdx = argsLayout.getPositionalVarargsArgumentIndex(); + if (posVarargsArgIdx != -1) { + if (!firstParam) { + sb.append(", "); + } else { + if (!function) sb.append(" "); + firstParam = false; + } + + sb.append(_StringUtil.toFTLTopLevelIdentifierReference(paramDefsByArgArrayIdx[posVarargsArgIdx].name)); + if (!function) { + sb.append("{").append(POSITIONAL_PARAMETER_OPTION_NAME).append("}"); + } + sb.append("..."); + } + // Named params: + int predefNamedArgCnt = argsLayout.getPredefinedNamedArgumentsMap().size(); + for (int idx = predefPosArgCnt; idx < predefPosArgCnt + predefNamedArgCnt; idx++) { + if (function) { + if (!firstParam) { + sb.append(", "); + } else { + firstParam = false; + } + } else { + sb.append(" "); + firstParam = false; + } + + ParameterDefinition paramDef = paramDefsByArgArrayIdx[idx]; + + sb.append(_StringUtil.toFTLTopLevelIdentifierReference(paramDef.name)); + if (function) { + sb.append("{").append(NAMED_PARAMETER_OPTION_NAME).append("}"); + } + + if (paramDef.defaultExpression != null) { + sb.append('='); + sb.append(paramDef.defaultExpression.getCanonicalForm()); + } + } + int namedVarargsArgIdx = argsLayout.getNamedVarargsArgumentIndex(); + if (namedVarargsArgIdx != -1) { + if (function) { + if (!firstParam) { + sb.append(", "); + } else { + firstParam = false; + } + } else { + sb.append(" "); + firstParam = false; + } + + sb.append(_StringUtil.toFTLTopLevelIdentifierReference(paramDefsByArgArrayIdx[namedVarargsArgIdx].name)); + if (function) { + sb.append("{").append(NAMED_PARAMETER_OPTION_NAME).append("}"); + } + sb.append("..."); + } + + if (function) sb.append(')'); + + if (canonical) { + sb.append('>'); + sb.append(getChildrenCanonicalForm()); + sb.append("</").append(getASTNodeDescriptor()).append('>'); + } + return sb.toString(); + } + + class Context implements LocalContext { + final Environment.Namespace localVars; + final Environment.TemplateLanguageCallable callable; + final CallPlace callPlace; + final Environment.Namespace nestedContentNamespace; + final LocalContextStack prevLocalContextStack; + final Context prevMacroContext; + + /** + * @param callPlace Not {@code null} + * @param callable Not {@code null} + * @param env Not {@code null} + */ + Context(Environment.TemplateLanguageCallable callable, CallPlace callPlace, Environment env) { + localVars = env.new Namespace(); // TODO [FM3] Pass in expected Map size + this.callable = callable; + this.callPlace = callPlace; + nestedContentNamespace = env.getCurrentNamespace(); + prevLocalContextStack = env.getLocalContextStack(); + prevMacroContext = env.getCurrentMacroContext(); + } + + ASTDirMacroOrFunction getMacro() { + return ASTDirMacroOrFunction.this; + } + + /** + * @return the local variable of the given name + * or null if it doesn't exist. + */ + @Override + public TemplateModel getLocalVariable(String name) throws TemplateModelException { + return localVars.get(name); + } + + Environment.Namespace getLocals() { + return localVars; + } + + /** + * Set a local variable in this macro + */ + void setLocalVar(String name, TemplateModel var) { + localVars.put(name, var); + } + + @Override + public Collection<String> getLocalVariableNames() throws TemplateModelException { + HashSet<String> result = new HashSet<>(); + for (TemplateModelIterator it = localVars.keys().iterator(); it.hasNext(); ) { + result.add(it.next().toString()); + } + return result; + } + } + + @Override + int getParameterCount() { + return 1/*name*/ + paramDefsByArgArrayIdx.length + 1/*type*/; + } + + @Override + Object getParameterValue(int idx) { + if (idx == 0) { + return name; + } else if (idx - 1 < paramDefsByArgArrayIdx.length) { + // TODO [FM3] This is not traversable with AST node API-s down to the default expression. Also, it's not + // extractable if the parameter is positional, named, and if it's varargs. But, as it's likely that the AST + // API will change, fixing this was postponed. + return paramDefsByArgArrayIdx[idx - 1]; + } else if (idx == getParameterCount() - 1) { + return function ? TYPE_FUNCTION : TYPE_MACRO; + } else { + throw new IndexOutOfBoundsException(); + } + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx == 0) { + return ParameterRole.ASSIGNMENT_TARGET; + } else if (idx - 1 < paramDefsByArgArrayIdx.length) { + return ParameterRole.PARAMETER_DEFINITION; + } else if (idx == getParameterCount() - 1) { + return ParameterRole.AST_NODE_SUBTYPE; + } else { + throw new IndexOutOfBoundsException(); + } + } + + @Override + boolean isNestedBlockRepeater() { + // Because of recursive calls + return true; + } + + /** + * A parameter definition from the #macro/#function start tag. + */ + static class ParameterDefinition { + private final String name; + private final ASTExpression defaultExpression; + + ParameterDefinition(String name, ASTExpression defaultExpression) { + this.name = name; + this.defaultExpression = defaultExpression; + } + + String getName() { + return name; + } + + ASTExpression getDefaultExpression() { + return defaultExpression; + } + + @Override + public String toString() { + return "ParameterDefinition(" + + "name=" + _StringUtil.jQuote(name) + + (defaultExpression != null ? ", default=" + defaultExpression.getCanonicalForm() : "") + + ')'; + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java index da427ea..a849aaf 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java @@ -20,12 +20,9 @@ package org.apache.freemarker.core; import java.io.IOException; -import java.util.Collection; -import java.util.Collections; import java.util.List; import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; /** * AST directive node: {@code #nested}. @@ -33,30 +30,40 @@ import org.apache.freemarker.core.model.TemplateModelException; final class ASTDirNested extends ASTDirective { - private List bodyParameters; + private List<ASTExpression> nestedContentParameters; - ASTDirNested(List bodyParameters) { - this.bodyParameters = bodyParameters; + ASTDirNested(List nestedContentParameters) { + this.nestedContentParameters = nestedContentParameters; } - List getBodyParameters() { - return bodyParameters; + List getNestedContentParameters() { + return nestedContentParameters; } - /** - * There is actually a subtle but essential point in the code below. - * A macro operates in the context in which it's defined. However, - * a nested block within a macro instruction is defined in the - * context in which the macro was invoked. So, we actually need to - * temporarily switch the namespace and macro context back to - * what it was before macro invocation to implement this properly. - * I (JR) realized this thanks to some incisive comments from Daniel Dekany. - */ @Override ASTElement[] accept(Environment env) throws IOException, TemplateException { - Context bodyContext = new Context(env); - env.invokeNestedContent(bodyContext); + CallPlace macroCallPlace = env.getCurrentMacroContext().callPlace; + + // When nestedContParamCnt < nestedContentParameters.size(), then we just skip calculating the extra parameters, + // and CallPlace.executeNestedContent will be successful. Note sure if this lenient behavior is a good idea, + // but for now it's inherited from FM2, so TODO [FM3]. + // When nestedContParamCnt > nestedContentParameters.size(), then later + // CallPlace.executeNestedContent will throw exception, but we let that happen so that the error message + // generation remains centralized. (In FM2 not even this was an error.) + TemplateModel[] nestedContParamValues; + if (nestedContentParameters != null) { + nestedContParamValues = new TemplateModel[ + Math.min(macroCallPlace.getNestedContentParameterCount(), nestedContentParameters.size())]; + for (int i = 0; i < nestedContParamValues.length; i++) { + nestedContParamValues[i] = nestedContentParameters.get(i).eval(env); + } + } else { + nestedContParamValues = null; + } + + env.executeNestedContentOfMacro(nestedContParamValues); + return null; } @@ -65,10 +72,10 @@ final class ASTDirNested extends ASTDirective { StringBuilder sb = new StringBuilder(); if (canonical) sb.append('<'); sb.append(getASTNodeDescriptor()); - if (bodyParameters != null) { - for (int i = 0; i < bodyParameters.size(); i++) { + if (nestedContentParameters != null) { + for (int i = 0; i < nestedContentParameters.size(); i++) { sb.append(' '); - sb.append(((ASTExpression) bodyParameters.get(i)).getCanonicalForm()); + sb.append(nestedContentParameters.get(i).getCanonicalForm()); } } if (canonical) sb.append('>'); @@ -82,13 +89,13 @@ final class ASTDirNested extends ASTDirective { @Override int getParameterCount() { - return bodyParameters != null ? bodyParameters.size() : 0; + return nestedContentParameters != null ? nestedContentParameters.size() : 0; } @Override Object getParameterValue(int idx) { checkIndex(idx); - return bodyParameters.get(idx); + return nestedContentParameters.get(idx); } @Override @@ -98,7 +105,7 @@ final class ASTDirNested extends ASTDirective { } private void checkIndex(int idx) { - if (bodyParameters == null || idx >= bodyParameters.size()) { + if (nestedContentParameters == null || idx >= nestedContentParameters.size()) { throw new IndexOutOfBoundsException(); } } @@ -118,40 +125,6 @@ final class ASTDirNested extends ASTDirective { return true; } - class Context implements LocalContext { - ASTDirMacro.Context invokingMacroContext; - Environment.Namespace bodyVars; - - Context(Environment env) throws TemplateException { - invokingMacroContext = env.getCurrentMacroContext(); - List<String> bodyParameterNames = invokingMacroContext.nestedContentParameterNames; - if (bodyParameters != null) { - for (int i = 0; i < bodyParameters.size(); i++) { - ASTExpression exp = (ASTExpression) bodyParameters.get(i); - TemplateModel tm = exp.eval(env); - if (bodyParameterNames != null && i < bodyParameterNames.size()) { - String bodyParameterName = bodyParameterNames.get(i); - if (bodyVars == null) { - bodyVars = env.new Namespace(); - } - bodyVars.put(bodyParameterName, tm); - } - } - } - } - - @Override - public TemplateModel getLocalVariable(String name) throws TemplateModelException { - return bodyVars == null ? null : bodyVars.get(name); - } - - @Override - public Collection<String> getLocalVariableNames() { - List<String> bodyParameterNames = invokingMacroContext.nestedContentParameterNames; - return bodyParameterNames == null ? Collections.<String>emptyList() : bodyParameterNames; - } - } - @Override boolean isNestedBlockRepeater() { return false; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java index b3fe6a5..8493310 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java @@ -35,7 +35,7 @@ final class ASTDirReturn extends ASTDirective { if (exp != null) { env.setLastReturnValue(exp.eval(env)); } - if (nextSibling() == null && getParent() instanceof ASTDirMacro) { + if (nextSibling() == null && getParent() instanceof ASTDirMacroOrFunction) { // Avoid unnecessary exception throwing return null; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java index 32f14ca..3fe5f74 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java @@ -22,11 +22,10 @@ package org.apache.freemarker.core; import java.io.IOException; import java.io.Writer; import java.util.Collection; -import java.util.LinkedHashMap; +import java.util.List; import org.apache.freemarker.core.ThreadInterruptionSupportTemplatePostProcessor.ASTThreadInterruptionCheck; import org.apache.freemarker.core.model.ArgumentArrayLayout; -import org.apache.freemarker.core.model.CallPlace; import org.apache.freemarker.core.model.Constants; import org.apache.freemarker.core.model.TemplateCallableModel; import org.apache.freemarker.core.model.TemplateDirectiveModel; @@ -35,8 +34,9 @@ import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateSequenceModel; import org.apache.freemarker.core.util.BugException; import org.apache.freemarker.core.util.CommonSupplier; +import org.apache.freemarker.core.util.FTLUtil; import org.apache.freemarker.core.util.StringToIndexMap; -import org.apache.freemarker.core.util._ArrayAdapterList; +import org.apache.freemarker.core.util._CollectionUtil; import org.apache.freemarker.core.util._StringUtil; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -52,7 +52,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; * is also validated (how many positional parameters are allowed, what named parameters are supported) then. Hence, the * call is "dynamic". */ -class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { +class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { static final class NamedArgument { private final String name; @@ -70,6 +70,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { private final StringToIndexMap nestedContentParamNames; private final boolean allowCallingFunctions; + // Concurrently accessed, but need not be volatile private CustomDataHolder customDataHolder; /** @@ -110,44 +111,17 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { nestedContentSupported = directive.isNestedContentSupported(); } else if (callableValueTM instanceof TemplateFunctionModel) { if (!allowCallingFunctions) { - // TODO [FM3][CF] Better exception - throw new NonUserDefinedDirectiveLikeException( - "Calling functions is not allowed on the top level in this template language", env); + throw new NonDirectiveException( + "Calling functions is not allowed. You can only call directives (like macros) here.", env); } callableValue = (TemplateCallableModel) callableValueTM; directive = null; function = (TemplateFunctionModel) callableValue; nestedContentSupported = false; - } else if (callableValueTM instanceof ASTDirMacro) { - // TODO [FM3][CF] Until macros were refactored to be TemplateDirectiveModel-s, we have this hack here. - ASTDirMacro macro = (ASTDirMacro) callableValueTM; - if (macro.isFunction()) { - throw new _MiscTemplateException(env, - "Routine ", new _DelayedJQuote(macro.getName()), " is a function, not a directive. " - + "Functions can only be called from expressions, like in ${f()}, ${x + f()} or ", - "<@someDirective someParam=f() />", "."); - } - - // We have to convert arguments to the legacy data structures... yet again, it's only a temporary hack. - LinkedHashMap<String, ASTExpression> macroNamedArgs; - if (namedArgs != null) { - macroNamedArgs = new LinkedHashMap<>(namedArgs.length * 4 / 3); - for (NamedArgument namedArg : namedArgs) { - macroNamedArgs.put(namedArg.name, namedArg.value); - } - } else { - macroNamedArgs = null; - } - env.invoke(macro, - macroNamedArgs, - _ArrayAdapterList.adapt(positionalArgs), - nestedContentParamNames != null ? nestedContentParamNames.getKeys() : null, - getChildBuffer()); - return null; } else if (callableValueTM == null) { throw InvalidReferenceException.getInstance(callableValueExp, env); } else { - throw new NonUserDefinedDirectiveLikeException(callableValueExp, callableValueTM, env); + throw new NonDirectiveException(callableValueExp, callableValueTM, env); } } @@ -170,27 +144,43 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { } if (posVarargsArgIdx != -1) { - int posVarargCnt = positionalArgs != null ? positionalArgs.length - predefPosArgCnt : 0; + int posVarargsLength = positionalArgs != null ? positionalArgs.length - predefPosArgCnt : 0; TemplateSequenceModel varargsSeq; - if (posVarargCnt <= 0) { + if (posVarargsLength <= 0) { varargsSeq = Constants.EMPTY_SEQUENCE; } else { - NativeSequence nativeSeq = new NativeSequence(posVarargCnt); + NativeSequence nativeSeq = new NativeSequence(posVarargsLength); varargsSeq = nativeSeq; - for (int posVarargIdx = 0; posVarargIdx < posVarargCnt; posVarargIdx++) { + for (int posVarargIdx = 0; posVarargIdx < posVarargsLength; posVarargIdx++) { nativeSeq.add(positionalArgs[predefPosArgCnt + posVarargIdx].eval(env)); } } execArgs[posVarargsArgIdx] = varargsSeq; } else if (positionalArgs != null && positionalArgs.length > predefPosArgCnt) { - throw new _MiscTemplateException(env, - "The target callable ", + checkSupportsAnyParameters(callableValue, env); + List<String> validPredefNames = argsLayout.getPredefinedNamedArgumentsMap().getKeys(); + _ErrorDescriptionBuilder errorDesc = new _ErrorDescriptionBuilder( + "The target ", FTLUtil.getCallableTypeName(callableValue), " ", (predefPosArgCnt != 0 - ? new Object[] { "can only have ", predefPosArgCnt } + ? new Object[]{ "can only have ", predefPosArgCnt } : "can't have" ), " arguments passed by position, but the invocation has ", - positionalArgs.length, " such arguments."); + positionalArgs.length, " such arguments. Try to pass arguments by name (as in ", + "<@example x=1 y=2 />", ").", + (!validPredefNames.isEmpty() + ? new Object[] { " The supported parameter names are:\n", + new _DelayedJQuotedListing(validPredefNames)} + : _CollectionUtil.EMPTY_OBJECT_ARRAY) + ); + if (callableValue instanceof Environment.TemplateLanguageDirective) { + errorDesc.tip("You can pass a parameter by position (i.e., without specifying its name, as you" + + " have tried now) when the macro has defined that parameter to be a positional parameter. " + + "See in the documentation how, and when that's a good practice."); + } + throw new _MiscTemplateException(env, + errorDesc + ); } int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex(); @@ -204,15 +194,19 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { } else { if (namedVarargsHash == null) { if (namedVarargsArgumentIndex == -1) { + checkSupportsAnyParameters(callableValue, env); Collection<String> validNames = predefNamedArgsMap.getKeys(); throw new _MiscTemplateException(env, validNames == null || validNames.isEmpty() ? new Object[] { - "The target callable doesn't have any by-name-passed parameters (like ", - new _DelayedJQuote(namedArg.name), ")" + "The called ", FTLUtil.getCallableTypeName(callableValue), + " can't have arguments that are passed by name (like ", + new _DelayedJQuote(namedArg.name), "). Try to pass arguments by position " + + "(i.e, without name, as in ", "<@example 1, 2, 3 />" , ")." } : new Object[] { - "The target callable has no by-name-passed parameter called ", + "The called ", FTLUtil.getCallableTypeName(callableValue), + " has no parameter that's passed by name and is called ", new _DelayedJQuote(namedArg.name), ". The supported parameter names are:\n", new _DelayedJQuotedListing(validNames) }); @@ -231,17 +225,25 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { if (directive != null) { directive.execute(execArgs, this, env.getOut(), env); } else { - TemplateModel result = function.execute(execArgs, env, this); + TemplateModel result = function.execute(execArgs, this, env); if (result == null) { throw new _MiscTemplateException(env, "Function has returned no value (or null)"); } - // TODO [FM3][CF] + // TODO [FM3] Implement it when we have a such language... it should work like `${f()}`. throw new BugException("Top-level function call not yet implemented"); } return null; } + private void checkSupportsAnyParameters(TemplateCallableModel callableValue, Environment env) + throws _MiscTemplateException { + if (callableValue.getArgumentArrayLayout().getTotalLength() == 0) { + throw new _MiscTemplateException(env, + "The called ", FTLUtil.getCallableTypeName(callableValue), " doesn't support any parameters."); + } + } + @Override boolean isNestedBlockRepeater() { return true; @@ -392,10 +394,10 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { } @Override - public void executeNestedContent(TemplateModel[] nestedContentParamValues, Writer out, Environment env) + public void executeNestedContent(TemplateModel[] nestedContentArgs, Writer out, Environment env) throws TemplateException, IOException { int nestedContentParamNamesSize = nestedContentParamNames != null ? nestedContentParamNames.size() : 0; - int nestedContentParamValuesSize = nestedContentParamValues != null ? nestedContentParamValues.length : 0; + int nestedContentParamValuesSize = nestedContentArgs != null ? nestedContentArgs.length : 0; if (nestedContentParamValuesSize != nestedContentParamNamesSize) { throw new _MiscTemplateException(env, "The invocation declares ", (nestedContentParamNamesSize != 0 ? nestedContentParamNamesSize : "no"), @@ -407,15 +409,26 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { nestedContentParamValuesSize, " parameters. You need to declare ", nestedContentParamValuesSize, " nested content parameters."); } - env.visit(getChildBuffer(), nestedContentParamNames, nestedContentParamValues, out); + env.visit(getChildBuffer(), nestedContentParamNames, nestedContentArgs, out); } @Override + public int getFirstTargetJavaParameterTypeIndex() { + // TODO [FM3] + return -1; + } + + @Override + public Class<?> getTargetJavaParameterType(int argIndex) { + // TODO [FM3] + return null; + } + + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") @SuppressFBWarnings(value={ "IS2_INCONSISTENT_SYNC", "DC_DOUBLECHECK" }, justification="Performance tricks") public Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> supplier) throws CallPlaceCustomDataInitializationException { - // We are using double-checked locking, utilizing Java memory model "final" trick. - // Note that this.customDataHolder is NOT volatile. + // We are using double-checked locking, but utilizing Java Memory Model "final" behavior, so it's correct. CustomDataHolder customDataHolder = this.customDataHolder; // Findbugs false alarm if (customDataHolder == null) { // Findbugs false alarm @@ -426,9 +439,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { this.customDataHolder = customDataHolder; } } - } - - if (customDataHolder.providerIdentity != providerIdentity) { + } else if (customDataHolder.providerIdentity != providerIdentity) { synchronized (this) { customDataHolder = this.customDataHolder; if (customDataHolder == null || customDataHolder.providerIdentity != providerIdentity) { @@ -441,7 +452,17 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { return customDataHolder.customData; } - private CustomDataHolder createNewCustomData(Object provierIdentity, CommonSupplier supplier) + @Override + public boolean isCustomDataSupported() { + return true; + } + + @Override + public boolean isNestedOutputCacheable() { + return isChildrenOutputCacheable(); + } + + private static CustomDataHolder createNewCustomData(Object providerIdentity, CommonSupplier<?> supplier) throws CallPlaceCustomDataInitializationException { CustomDataHolder customDataHolder; Object customData; @@ -450,42 +471,23 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace { } catch (Exception e) { throw new CallPlaceCustomDataInitializationException( "Failed to initialize custom data for provider identity " - + _StringUtil.tryToString(provierIdentity) + " via factory " + + _StringUtil.tryToString(providerIdentity) + " via factory " + _StringUtil.tryToString(supplier), e); } if (customData == null) { throw new NullPointerException("CommonSupplier.get() has returned null"); } - customDataHolder = new CustomDataHolder(provierIdentity, customData); + customDataHolder = new CustomDataHolder(providerIdentity, customData); return customDataHolder; } - @Override - public boolean isNestedOutputCacheable() { - return isChildrenOutputCacheable(); - } - - @Override - public int getFirstTargetJavaParameterTypeIndex() { - // TODO [FM3] - return -1; - } - - @Override - public Class<?> getTargetJavaParameterType(int argIndex) { - // TODO [FM3] - return null; - } - - /** - * Used for implementing double check locking in implementing the - * {@link #getOrCreateCustomData(Object, CommonSupplier)}. - */ - private static class CustomDataHolder { + static class CustomDataHolder { + // It's important that all fields are final (Java Memory Model behaves specially with finals)! private final Object providerIdentity; private final Object customData; - public CustomDataHolder(Object providerIdentity, Object customData) { + + private CustomDataHolder(Object providerIdentity, Object customData) { this.providerIdentity = providerIdentity; this.customData = customData; }
