OK, it (see quoted mail, and
https://issues.apache.org/jira/browse/FREEMARKER-63) is partially
done, so insights are highly welcome! It's not yet merged, so see:

https://github.com/ddekany/incubator-freemarker/tree/FREEMARKER-63
https://github.com/apache/incubator-freemarker/pull/30

It works fully for directives (like macros), as per FREEMARKER-63.
(#function-s/methods is the next issue,
https://issues.apache.org/jira/browse/FREEMARKER-64)

The syntax is as it was described earlier, except "byName" and
"byPosition" was replaced with "named" and "positional":

   <#macro message text{positional}, color id=someDefault>...</#macro>
   <#function message(text, color{named}, id{named}=someDefault)>...</#function>

and then you call it like this (as it was said much earlier):

   <@message 'Hello World' color='red' id='test' />

(You can't yet call function with named arguments. You can already
define functions with named parameters though.)

More examples:
https://github.com/ddekany/incubator-freemarker/blob/FREEMARKER-63/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java

As of the main API-s, the key interfaces are TemplateCallableModel and
its two sub-interfaces:

https://github.com/ddekany/incubator-freemarker/blob/FREEMARKER-63/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java
https://github.com/ddekany/incubator-freemarker/blob/FREEMARKER-63/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java
https://github.com/ddekany/incubator-freemarker/blob/FREEMARKER-63/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java

One of the potentially controversial thing with them is that they use
a somewhat low-level way of argument passing, where all the arguments
are passed in a single TemplateModel array, and there's a
ArgumentArrayLayout that describes which parameters go to what indexes:

https://github.com/ddekany/incubator-freemarker/blob/FREEMARKER-63/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java

This is for efficiency (we avoid building Map-s on runtime, and just
create an array, and can also do some of the grinding later without
HashMap lookups and string comparisons, with simply reading array
items from know (constant) indexes. It isn't the most user friendly
API to implement for sure (but the majority users don't implement
FreeMarker directives/function in Java anyway - it's power user thing
mostly), though I have implemented a few directives and functions with
this, and it's not that bad after doing the same with FM2,
particularly because with named arguments you don't have to deal with
unknown names anymore, or write those switch-by-String statements (or
worse, Map.Entry loops before Java 7). Anyway, later I plan to add
some utility for defining directives/function with annotated static
methods, so it can be much more friendly if it has to.

Calling a TemplateCallableModel from user Java code is a much more
inconvenient thing though, as then you are responsible for setting up
the argument array as per the provided ArgumentArrayLayout. Luckily,
that's done very rarely by users (and can also be supported with some
utility methods if really needed). After all, FreeMarker
directives/function are to be called from templates, otherwise you
just use plain Java methods.

See also some more smaller changes here:
https://github.com/ddekany/incubator-freemarker/blob/FREEMARKER-63/FM3-CHANGE-LOG.txt


Wednesday, July 5, 2017, 10:00:26 PM, Daniel Dekany wrote:

> Something from the first mail of this thread that I want to emphasize
> is that which parameter needs to be passed by position and which by
> name is decided when the directive or function is defined. It's not
> decided by the caller (see reasons below).
>
> For example, the 1st parameter of #include can only be passed by
> position, as in `<#include "foo.ftl">`. You can't write
> `<#include template="foo.ftl">`. However, the `ignoreMissing`
> parameter (and in FM2 we also had `encoding` and `parse` parameters)
> can only be passed by name, as in
> `<#include "foo.ftl" ignoreMissing=true>`. You can't write
> `<#include "foo.ftl", true>`.
>
> Some may have used languages where the caller decides what is passed
> by position and what by name, but IMO it just leads to chaos in our
> case. I also realize and accept that everybody has different taste,
> but people read/edit other people's code a lot, so the taste of the
> original author often doesn't matter much. Plus if someone overuses
> positional parameters (laziness/rushing and C/Java habits may make
> people do that...), the template becomes less readable, especially for
> someone less experienced with writing templates. Consistency regarding
> how core directives are called is even more valuable, as people
> copy-paste it from StackOverflow etc. Imagine if in some cases you see
> `<#if test=foo>`, while in others <#if foo>... confusing or annoying.
>
> Now, we don't yet have a syntax in #macro and #function calls to
> declare if a parameter passed by positional or by named. How should it
> look?
>
> My idea is that for directives by-name will be the default, and for
> functions by-positional will be the default. To deviate from the
> defaults, you add the proper option after the parameter name inside
> `{}`, where `{}` is also a new thing, which will be later useful for
> specifying other parameter related options as well (like, with a
> totally fictional example, `myParam{byName, allowNull, max=100}`).
>
> So here `x` and `y` are passed by position, and `color` and `id` by
> name (and `id` defaults to `null`):
>
>   <#macro message text{byPosition}, color id=null>...</#macro>
>   <#function message(text, color{byName}, id{byName}=null)>...</#function>
>
> So you can call these as:
>
>   <@message 'Hello World' color='red' id='test' />
>
> and
>
>   message('Hello World', color='red', id='test')
>
>
>
> Wednesday, July 5, 2017, 5:19:56 PM, Woonsan Ko wrote:
>
>> On Tue, Jul 4, 2017 at 10:15 AM, Daniel Dekany <[email protected]> wrote:
>>> Tuesday, July 4, 2017, 7:57:18 AM, Woonsan Ko wrote:
>>>
>>>> On Wed, Jun 21, 2017 at 2:30 PM, Daniel Dekany <[email protected]> wrote:
>>>>> Friday, June 16, 2017, 8:41:37 PM, Daniel Dekany wrote:
>>>>>
>>>>>> A problem in FM2 is that when calling a directive (as a macro), either
>>>>>> all parameters are positional (`<@message "Hi" 2 />`), or all
>>>>>> parameters are named (`<@message content="Hi" height=2 />`); you can't
>>>>>> mix the two (`<@message "Hi" height=2 />`). Also you can't use named
>>>>>> parameters for functions/methods, only for directives. Worse, core
>>>>>> directives don't even use named parameters, but some keyword like
>>>>>> `as`, `using`... their syntax is hard coded into the parser, which is
>>>>>> not nice, and will be a problem for the custom dialects feature.
>>>>>>
>>>>>> I think that with the exception of a few core directives (see them
>>>>>> later) all directive calls should be like this, if for now (in this
>>>>>> thread) we ignore loop variables:
>>>>>>
>>>>>>   <#name posPar1 posPar2 namedPar1Name=namedPar1Value 
>>>>>> namedPar2=namedPar2Value>
>>>>>
>>>>> An adjustment to the above... I think that we should require comma
>>>>> between positional arguments (but not between positional and named
>>>>> arguments and between named arguments):
>>>>
>>>> "but not between ..." means that a comma can be *optionally* placed
>>>> between named arguments or between a positioned argument and a named
>>>> argument, right?
>>>
>>> I wouldn't allow that. If we do, then where to put comma becomes a
>>> matter of taste, and the taste of individuals in the same project
>>> differ, or for OS projects, whoever bumps into the templates might has
>>> a different taste. So overall, you just end up with chaos, and if some
>>> cares much, it's just unnecessary struggle (which of the 3
>>> combinations is the Right Way). So I believe it's better overall if we
>>> decide what's the right way for everyone... Especially as our taste is
>>> based on more knowledge. FTL tags have this HTML-ish look-and-feel, so
>>> naturally you write `<#foo n1=v1 n2=v2 m3=v3>`, and not
>>> `<#foo n1=v1, n2=v2, m3=v3>`. So now, if we add a single positional
>>> parameter, I think most will find it consistent like this:
>>> `<#foo v0 n1=v1 n2=v2 m3=v3>` (like in `<@message "Hi" height=2 />`).
>>> Still no comas anywhere. In the hopefully rare case when you have
>>> multiple positional parameters, we unfortunately ran into ambiguity
>>> issues, so then, and only then, and only between the positional
>>> parameters you add commas. I think that's also matches common
>>> conventions, where if you list something, you put commas *between* the
>>> listed items, but not after the last one. Named parameters aren't
>>> really listed (their order doesn't mater). So it's more logical to
>>> only use comma where you must, not just a matter of taste.
>>
>> I concur with you now! Indeed, listing (positional args) requires
>> comma delimiter, but non-listing (named args) doesn't.
>>
>>>
>>> BTW, I was never a fan of this HTML-ish look-and-feel, because of the
>>> confusing differences; people keep writing `foo="${x}"` instead of
>>> `foo=x`. But that's the FM tradition, so I try to remain consistent
>>> with the original idea. (When we manage to separate the expression
>>> syntax from the top-level syntax, I think I will try to introduce an
>>> officially supported alternative, something like `#foo(v0, n1=v1,
>>> n2=v2, m3=v3)`.)
>>
>> That sounds great, too!
>>
>> Thanks again for the thorough thoughts and clarifications!
>>
>> Cheers,
>>
>> Woonsan
>>
>>>
>>>> In other words, it is allowed to omit a comma between positional arg
>>>> and named arg or between named args.
>>>>
>>>> Woonsan
>>>>
>>>>>
>>>>>   <#name posPar1, posPar2 namedPar1Name=namedPar1Value 
>>>>> namedPar2=namedPar2Value>
>>>>
>>>
>>> --
>>> Thanks,
>>>  Daniel Dekany
>>>
>>
>

-- 
Thanks,
 Daniel Dekany

Reply via email to