freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [19/21] incubator-freemarker git commit: FREEMARKER-63: Lot of refinement in the API-s and implementation. #macro now creates a `TemplateDirectiveModel`, and #function now creates `TemplateFunctionModel` (though the function/method call syntax doesn't ye
Date Mon, 07 Aug 2017 22:32:21 GMT
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;
         }



Mime
View raw message