Take the 2-minute tour ×

I've got a couple Velocity macros like this:

  #set($p = 1)
    $p             // 1

#macro(Beta $params)
  #set($p = 2)
  $p               // 2

And I'm using them like so:

#set($p = 0)
  $p               // 3

I believe this renders like so (ignore formatting): 2, 2, 2

But I'd like to have proper closure behavior, including more locally scoped names hiding parent scope names. In particular, the usage of $p labelled '3' should refer to the value 0, '2' to the value 2, and '1' to the value 1.

Given proper closure semantics, it would print: 2, 1, 0

Is there any way to get this, or a way to implement a custom directive / modify #macro directive behavior to achieve this?

share|improve this question
add comment

1 Answer

up vote 2 down vote accepted

Velocity is a templating engine, not a proper programming language, so it's a bit harder to grasp how it works.

Macros are not functions like in Java or C, meaning that invoking a macro isn't going to create a new segment on the stack for local variables; velocity works with a context, and most of the time there's just one global context.

Still, there are two ways of dealing with local variables:

  1. There's the velocimacro.context.localscope configuration parameter that prevents changing global variables from within macros; note that this setting is deprecated and will be removed in Velocity 2.0
  2. You can use the $macro variable as a local container for private variables if you enable the macro.provide.scope.control configuration parameter

Still, there's another problem that would prevent your code from running correctly: Velocity macros work mostly as call-by-macro expansion, which means that the body passed to a macro isn't evaluated first, then passed to the macro; it will be evaluated dynamically when the nested macro is executed. The code behaves as:

  #set($macro.p = 1)  -> $macro refers to Alpha
  $p -> $p will always be 0, since we're changing a local variable
  $macro.p  -> 1 here
  $bodyContent  -> Here $p is used, and 0 is printed
    $macro.p  -> it is 1 now, but when this line will be actually evaluated, $macro will refer to Beta, so 2 will be printed
    $bodyContent  -> It's the content of the Beta macro, which triggers an infinite recursion; it's not $p as above

  #set($macro.p = 2)  -> $macro refers to Beta
  $macro.p  -> 2 here
  $bodyContent  -> the $bodyContent that was passed into the $bodyContent is evaluated now, so it causes a recursion

#set($p = 0)
  $p  -> Global variable
share|improve this answer
I had read about this magical $macro variable but couldn't get it to work, so thank you very much for that configuration property. Regarding the call by name issue, I was also aware of that and actually made a custom build which eagerly evaluates the body and passes it as a plain text node to the macro's $!bodyContent (or whatever it's configured to be) variable. –  gzak Sep 25 '12 at 17:03
I have one more follow up question: there are also other types of local variables in addition to $macro, like $foreach within a #foreach directive. Is there one for #if? There is also one called $foo in the documentation (probably as an example) which corresponds to local variables within #@foo() ... #end directives. What are the various configuration property names for these scopes (particularly one of #if and the example $foo)? –  gzak Sep 25 '12 at 17:12
All the configuration properties are listed in the documentation. There's no scope variable for #if blocks. Search for <somebodymacro>.provide.scope.control to see how to configure scope variables like $foo for a macro named #foo. –  Sergiu Dumitriu Sep 25 '12 at 19:03
Ah I see. For body macros I was hoping there was a general property which would enable it for all macros instead of on a case-by-case basis. –  gzak Sep 26 '12 at 18:16
Body macros are essentially treated as custom directives, and the pattern is for each directive to have scope controls enabled individually. Perhaps there could be a general property that enables scope objects for all directives, but that's not been implemented. Feel free to open an RFE issue for that. –  Nathan Bubna Oct 19 '12 at 16:39
add comment

Your Answer


