You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@velocity.apache.org by "Kyle F. Downey" <kd...@amberarcher.com> on 2001/03/27 16:04:42 UTC

VelociMacros and varargs

The manual says that you must call a macro with the declared set of
args. How solid is this limit? At the parser level? At the interpreter
level? At a higher level? Any pointers to the corresponding place in the
code where this limit manifests? Also, how important is this limit to the
design of the language and software--i.e., if I were to write a patch that
removed it, would it be accepted (assuming it's decent code, works, etc.)?

I've written a tool (Vpp) that integrates with Ant to provide a
pre-processor for Java. Like Texen, it loads an additional template
that contains #set's and #macro definitions. Unlike Texen, it doesn't
have that template control the code generation process (so Ant can
do things like use its internal facilities to determine which source
files need (re)pre-processing).

Velocity is ideal for things like

#if $java2
  import java.util.Map;
#else
  import com.sun.java.util.collections.Map;

and cross-cutting code like logging and internationalization that
often require the name of the source class. (Vpp passes in
$callerClassName--any way to pass in line number too?)

It's working to our needs for now, but I've had to do the following:

#macro (I18N $msgid)
   // some Java code for getting int'lized text that uses $msgid to look
   // up a string resource in a Properties file
#end

#macro (I18N_VA $msgid $argList)
  // some code that merges the list argument into an
  // Object[] array to use the java.text tools to
  // replace parameters in the strings from the
  // properties code
#end

Essentially, I'm simulating varargs using the Velocity list primitive.
Ugly, but not too bad. But now I want to be able to pass an additional,
optional argument (the Locale). Using my current method, that means two
more versions of the I18N macro. That's starting to get ugly. Worse, I
have eight other macros that delegate to these macros, so their arg lists
must be replicated as well--yielding three cut-and-paste variants apiece
on nine different base macros! One begins to understand how OO started...

There seem to be several ways to approach this:

* have a smarter interpreter that matches # of arguments passed
to the macro with one of several macros with the same name (advantage of
behaving like other scripting languages; disadvantage of likely being
the most complex to implement, cutting across large parts of code)
* have #macro ALWAYS take a list type rather than having it declare
named parameters (don't like this option at all because it breaks
code and makes macros less self-documenting, but it has performance and
simplicity benefits over the first verison)
# create a #va_macro primitive that has the above properties instead
(advantage of not breaking existing code and isolating the special
case; disadvantage of probably creating duplicate code inside the
interpreter and bloating the language)
* have the interpreter pass a null value for any "missing" trailing
arguments so the macro can figure out its own defaults
* tell Kyle to go hang himself ;-)

I prefer the penultimate option myself, and am willing to give it a shot.
What do you folks think?

--kd

p.s. I can donate Vpp to the project as well if you don't feel it overlaps
too much with Texen. It's very simple thanks to Ant and Velocity. The only
problem with using it on Java code is that you hve to escape out the #
signs from Javadoc cross-references and any dollar-sign references that
break the parser.



Re: VelociMacros and varargs

Posted by Jon Stevens <jo...@latchkey.com>.
on 3/27/01 9:06 AM, "Jon Stevens" <jo...@latchkey.com> wrote:

> You are doing things badly IMHO by not allowing people to compile the code
> that they are editing without having to first run it through a
> pre-processor. Another reason PP is bad is because it totally breaks the
> ability of javac to do conditional compilation. ALL your code will get
> re-compiled every time you run javac. That is bad.
> 
> -jon

I said this incorrectly:

What I meant to say is that in order to change the I18N or Logging stuff you
need to re-compile your code. That is bad IMHO.

-jon


Re: VelociMacros and varargs

Posted by Jon Stevens <jo...@latchkey.com>.
on 3/27/01 9:48 AM, "Kyle F. Downey" <kd...@amberarcher.com> wrote:

> Actually, Vpp subclasses Copy. It re-uses most of those facilities. I
> did consider using <replace>, as you said, but I needed something a little
> more robust in the parsing department to handle the logging and I18N
> macros. You're right it's sufficient for import changing, and a
> pre-processor would be dangerous and overkill in that case. Thanks.
> 
> --kd

Java Provides excellent facilities for I18N (ResourceBundles) and Logging
(including compiling out the logging statements with the use of a static
boolean) without having to use preprocessing.

You are doing things badly IMHO by not allowing people to compile the code
that they are editing without having to first run it through a
pre-processor. Another reason PP is bad is because it totally breaks the
ability of javac to do conditional compilation. ALL your code will get
re-compiled every time you run javac. That is bad.

-jon


Re: VelociMacros and varargs

Posted by "Kyle F. Downey" <kd...@amberarcher.com>.
> on 3/27/01 8:18 AM, "Kyle F. Downey" <kd...@amberarcher.com> wrote:
>
> > There's no way to change an import package with any fancy
> > Java tricks short of pre-processing, just as there's also no way to do
> > things that require knowing the calling source file name and line (short
> > of a hack I wrote that parses a StackTrace!).
>
> BTW, I just use Ant for this...when you do the compile, first <copy> the
> source code into a different directory...then use the <replace> tag to
> search/replace for specific import strings depending on what JDK version you
> are using or target version.

Actually, Vpp subclasses Copy. It re-uses most of those facilities. I
did consider using <replace>, as you said, but I needed something a little
more robust in the parsing department to handle the logging and I18N
macros. You're right it's sufficient for import changing, and a
pre-processor would be dangerous and overkill in that case. Thanks.

--kd


Re: VelociMacros and varargs

Posted by Jon Stevens <jo...@latchkey.com>.
on 3/27/01 8:18 AM, "Kyle F. Downey" <kd...@amberarcher.com> wrote:

> There's no way to change an import package with any fancy
> Java tricks short of pre-processing, just as there's also no way to do
> things that require knowing the calling source file name and line (short
> of a hack I wrote that parses a StackTrace!).

BTW, I just use Ant for this...when you do the compile, first <copy> the
source code into a different directory...then use the <replace> tag to
search/replace for specific import strings depending on what JDK version you
are using or target version.

This way, your code still compiles on the JDK version of your choosing
without having to run it through a pre-processor first.

pre-processing code is still bad IMHO. especially since your only real
complaint has been about the issue that I just solved for you. :-)

p.s. I did that above compatibility system for WebMacro, but Justin decided
to throw all of my work into the trash because he couldn't get it to work on
his particular buggy Linux JVM at the time (yet it worked on every other
JVM). Totally pissed me off and of course the rest of the WM community
suffered as a result...absolutely stupid.

-jon


Re: VelociMacros and varargs

Posted by "Kyle F. Downey" <kd...@amberarcher.com>.
>
> Well, remember, it's not supposed to be a function or method :)
> 'Subroutine' is a verboten word. (We had some interesting and 'lively'
> debates when VMs were first proposed... )
>

Burn him! Burn him! He's a witch! ;-) I know what you mean about the
pre-processor heresy. It's kept me from pursuing this for a long time;
practically every book on intro Java sings the praises of the lack of a
pre-processor for the language, and it's become received wisdom in the
Java developer community.

However, the thing #ifdef and friends are so useful for (accounting for
platform differences) IS becoming an issue for Java because of Sun's
tendency to add cool new API classes to the java.* space rather than
javax.*, even when they can still work under Java 1.1 (I'll never
understand why they didn't do the same thing with Collections that they
did with Swing). There's no way to change an import package with any fancy
Java tricks short of pre-processing, just as there's also no way to do
things that require knowing the calling source file name and line (short
of a hack I wrote that parses a StackTrace!).

Since IBM's Java port of cpp isn't free or supported, jpp and others are
outdated and it's silly to write YAML (yet another macro language),
Velocity seems like a good choice for this.

> Seriously - I understand your need.  It's just that I'm wired right now
> for conservative VTL designed for  the designer, but I'm sure others
> will have different opinions.   I'll certainly think about this...
>

Totally understood. BTW, I figured out how to get what I want with the
existing VM set-up. It's still pretty clean on the client side at the
expense of the macro definition being ugly. As you suggested, just pass a
list. It works.

--kd



Re: VelociMacros and varargs

Posted by "Geir Magnusson Jr." <ge...@optonline.net>.
"Kyle F. Downey" wrote:
> 
> > > #macro (I18N $msgid)
> > >    // some Java code for getting int'lized text that uses $msgid to look
> > >    // up a string resource in a Properties file
> > > #end
> > >
> > > #macro (I18N_VA $msgid $argList)
> > >   // some code that merges the list argument into an
> > >   // Object[] array to use the java.text tools to
> > >   // replace parameters in the strings from the
> > >   // properties code
> > > #end
> >
> > Why not just declare one and check for nulls?
> >
> 
> Because I'd like to keep the macro call as simple as possible. If it's
> not going to happen, then that is an option, though.

Right. ok.

> 
> > Or how about a hash of name value pairs?
> >
> 
> Even worse.

True.  That also makes it not much better than the old #parse() 'hail
mary'....
 
> > > behaving like other scripting languages; disadvantage of likely being
> > > the most complex to implement, cutting across large parts of code)
> >
> > Yuk.  We are starting to move quickly away from VTL as simple and small.
> >
> 
> Agreed.
> 
> > > * have #macro ALWAYS take a list type rather than having it declare
> > > named parameters (don't like this option at all because it breaks
> > > code and makes macros less self-documenting, but it has performance and
> > > simplicity benefits over the first verison)
> >
> > -1
> >
> 
> Agreed.
> 
> > > # create a #va_macro primitive that has the above properties instead
> > > (advantage of not breaking existing code and isolating the special
> > > case; disadvantage of probably creating duplicate code inside the
> > > interpreter and bloating the language)
> >
> > It's not an interpreter issue.  This might be straightforward, but you
> > still have the problem of accessing the args in the VM body.
> >
> 
> That's fine. With 70,000 lines of Java code and 50 lines of macros, I'd
> much rather jump through a hoop or two in a macro than make calling the
> macro call any more verbose than necessary.
> 
> > > * have the interpreter pass a null value for any "missing" trailing
> > > arguments so the macro can figure out its own defaults
> >
> > This sounds like it could cause chaos for normal designer users.
> >
> 
> Not sure I understand this objection. Are you saying it's confusion to
> have the option of both
> 
> #LOG("foo")
> 
> and
> 
> #LOG("foo", "bar")
> 
> I agree that's more familiar to programmers, but it has the advantage of
> being there only if you need it. If you are writing macros for designers,
> they probably wouldn't need to know about this facility or use it.

Agreed - I recognize what you are saying.  I do feel very 'yecchy' about
this idea, though.  It's one of those deals where we would be pushing
Velocity pretty far out of original intent.  That doesn't mean it can't
and shouldn't change as a rule, but as one of the tenets is to keep
things simple, especially on the designer side, I am very conservative
and cautious about something like this.

> > > * tell Kyle to go hang himself ;-)
> >
> > Starting to think seriously about the last bit :)
> >
> 
> ...huntin' fer a sufficient length 'a rope...

I was just kidding, of course :) 

> > > I prefer the penultimate option myself, and am willing to give it a shot.
> > > What do you folks think?
> >
> > There is another option - what about just redefining the VMs as you need
> > to?  I.e. since this is a standalone program, why not establish the VMs
> > at runtime?
> >
> 
> It's not a standalone program. It's an ant task, as I said in the original
> message.

Right - sorry.  I guess I was thinking 'servlet based' versus
'otherwise', but that isn't clean when you consider that ant could be
running other velocity-using tasks in the same VM.

>  Also, I'm not sure I understand what you mean about redefining as
> needed. They're all loaded up at start-up, and I don't want the Java code
> to include any #macro definitions. Trying to minimize the horror that is
> a code pre-processor. :-)

I will note that I am very used to having a pre-processor as well,
coming from a C/C++ background, but isn't the notion of a pre-processor
sort of the "Burn the witch!" kind of heresy? :)

What I meant is that you have great flexibility in adding macros at
runtime.  You can use the Velocity class to evaluate strings and
streams, and those strings and streams can be nothing more than macro
definitions. 

Now, of course I don't know if possible, but if you could know which VMs
you would need, you could simply 'register' them just before
pre-processing your code by using the Velocity class's evaluate method. 
You could even contruct the macro sets dynamically.

But I have no clue if that's even possible for your app.

> 
> Even in a single source unit a program might have a mix of different
> argument sets for calling a particular macro, so if I understand what you
> mean about redefining, it's not a solution.

Ok.
 
> > Do you know while processing which set of VMs you would need?
> >
> 
> Yes. It's just the set that are in the "profile" VM file that defines
> available macros for the pre-processing run.

So it's an 'all' not 'this set'... ?

> > > --kd
> > >
> > > p.s. I can donate Vpp to the project as well if you don't feel it overlaps
> > > too much with Texen. It's very simple thanks to Ant and Velocity. The only
> > > problem with using it on Java code is that you hve to escape out the #
> > > signs from Javadoc cross-references and any dollar-sign references that
> > > break the parser.
> >
> > Doesn't this seem  more like an Ant task?
> >
> 
> Like I said, it is an ant task. That's why I said I was able to use the
> facilities of Ant from within it. I just put this before my <javac>:
> 
>     <vpp todir="${build.gensrc}" profileDir="${profile.dir}"
>          profile="debug.vm">
>       <fileset dir="${src.dir}">
>         <include name="**/*.java"/>
>       </fileset>
>     </vpp>
> 
> and I'm able to use the FileSet scanner to find all the changed Java files
> and then pass them to be pre-processed by Velocity. By changing from
> debug.vm -> production.vm (which could be an Ant property set on the
> command line too) all the #DEBUG and #TRACE macros get commented out.
> Very convenient.

This is cool.  
 
> Thanks. I guess I'll have it pass null from the Java code for
> "missing" args, or maybe use a list/hash. That's not very clean, IMHO,
> because it doesn't look like a function/method call, but I can
> certainly see your objections to introducing this facility.

Well, remember, it's not supposed to be a function or method :) 
'Subroutine' is a verboten word. (We had some interesting and 'lively'
debates when VMs were first proposed... )

Seriously - I understand your need.  It's just that I'm wired right now
for conservative VTL designed for  the designer, but I'm sure others
will have different opinions.   I'll certainly think about this...

geir

-- 
Geir Magnusson Jr.                               geirm@optonline.net
Developing for the web?  See http://jakarta.apache.org/velocity/

Re: VelociMacros and varargs

Posted by "Kyle F. Downey" <kd...@amberarcher.com>.
> > #macro (I18N $msgid)
> >    // some Java code for getting int'lized text that uses $msgid to look
> >    // up a string resource in a Properties file
> > #end
> >
> > #macro (I18N_VA $msgid $argList)
> >   // some code that merges the list argument into an
> >   // Object[] array to use the java.text tools to
> >   // replace parameters in the strings from the
> >   // properties code
> > #end
>
> Why not just declare one and check for nulls?
>

Because I'd like to keep the macro call as simple as possible. If it's
not going to happen, then that is an option, though.

> Or how about a hash of name value pairs?
>

Even worse.

> > behaving like other scripting languages; disadvantage of likely being
> > the most complex to implement, cutting across large parts of code)
>
> Yuk.  We are starting to move quickly away from VTL as simple and small.
>

Agreed.

> > * have #macro ALWAYS take a list type rather than having it declare
> > named parameters (don't like this option at all because it breaks
> > code and makes macros less self-documenting, but it has performance and
> > simplicity benefits over the first verison)
>
> -1
>

Agreed.

> > # create a #va_macro primitive that has the above properties instead
> > (advantage of not breaking existing code and isolating the special
> > case; disadvantage of probably creating duplicate code inside the
> > interpreter and bloating the language)
>
> It's not an interpreter issue.  This might be straightforward, but you
> still have the problem of accessing the args in the VM body.
>

That's fine. With 70,000 lines of Java code and 50 lines of macros, I'd
much rather jump through a hoop or two in a macro than make calling the
macro call any more verbose than necessary.

> > * have the interpreter pass a null value for any "missing" trailing
> > arguments so the macro can figure out its own defaults
>
> This sounds like it could cause chaos for normal designer users.
>

Not sure I understand this objection. Are you saying it's confusion to
have the option of both

#LOG("foo")

and

#LOG("foo", "bar")

I agree that's more familiar to programmers, but it has the advantage of
being there only if you need it. If you are writing macros for designers,
they probably wouldn't need to know about this facility or use it.

> > * tell Kyle to go hang himself ;-)
>
> Starting to think seriously about the last bit :)
>

...huntin' fer a sufficient length 'a rope...

> > I prefer the penultimate option myself, and am willing to give it a shot.
> > What do you folks think?
>
> There is another option - what about just redefining the VMs as you need
> to?  I.e. since this is a standalone program, why not establish the VMs
> at runtime?
>

It's not a standalone program. It's an ant task, as I said in the original
message. Also, I'm not sure I understand what you mean about redefining as
needed. They're all loaded up at start-up, and I don't want the Java code
to include any #macro definitions. Trying to minimize the horror that is
a code pre-processor. :-)

Even in a single source unit a program might have a mix of different
argument sets for calling a particular macro, so if I understand what you
mean about redefining, it's not a solution.

> Do you know while processing which set of VMs you would need?
>

Yes. It's just the set that are in the "profile" VM file that defines
available macros for the pre-processing run.

> > --kd
> >
> > p.s. I can donate Vpp to the project as well if you don't feel it overlaps
> > too much with Texen. It's very simple thanks to Ant and Velocity. The only
> > problem with using it on Java code is that you hve to escape out the #
> > signs from Javadoc cross-references and any dollar-sign references that
> > break the parser.
>
> Doesn't this seem  more like an Ant task?
>

Like I said, it is an ant task. That's why I said I was able to use the
facilities of Ant from within it. I just put this before my <javac>:

    <vpp todir="${build.gensrc}" profileDir="${profile.dir}"
         profile="debug.vm">
      <fileset dir="${src.dir}">
        <include name="**/*.java"/>
      </fileset>
    </vpp>

and I'm able to use the FileSet scanner to find all the changed Java files
and then pass them to be pre-processed by Velocity. By changing from
debug.vm -> production.vm (which could be an Ant property set on the
command line too) all the #DEBUG and #TRACE macros get commented out.
Very convenient.

Thanks. I guess I'll have it pass null from the Java code for
"missing" args, or maybe use a list/hash. That's not very clean, IMHO,
because it doesn't look like a function/method call, but I can
certainly see your objections to introducing this facility.

--kd


Re: VelociMacros and varargs

Posted by "Geir Magnusson Jr." <ge...@optonline.net>.
"Kyle F. Downey" wrote:
> 
> The manual says that you must call a macro with the declared set of
> args. How solid is this limit? At the parser level? At the interpreter
> level? At a higher level? Any pointers to the corresponding place in the
> code where this limit manifests? Also, how important is this limit to the
> design of the language and software--i.e., if I were to write a patch that
> removed it, would it be accepted (assuming it's decent code, works, etc.)?

It's a code-level limit, and would't be that hard to work around,  I
think it would take me about 10 minutes...

but this seems like trouble in a simple template language.  I am not
sure we'd really want to go there.

> I've written a tool (Vpp) that integrates with Ant to provide a
> pre-processor for Java. Like Texen, it loads an additional template
> that contains #set's and #macro definitions. Unlike Texen, it doesn't
> have that template control the code generation process (so Ant can
> do things like use its internal facilities to determine which source
> files need (re)pre-processing).
> 
> Velocity is ideal for things like
> 
> #if $java2
>   import java.util.Map;
> #else
>   import com.sun.java.util.collections.Map;
> 
> and cross-cutting code like logging and internationalization that
> often require the name of the source class. (Vpp passes in
> $callerClassName--any way to pass in line number too?)
> 
> It's working to our needs for now, but I've had to do the following:
> 
> #macro (I18N $msgid)
>    // some Java code for getting int'lized text that uses $msgid to look
>    // up a string resource in a Properties file
> #end
> 
> #macro (I18N_VA $msgid $argList)
>   // some code that merges the list argument into an
>   // Object[] array to use the java.text tools to
>   // replace parameters in the strings from the
>   // properties code
> #end

Why not just declare one and check for nulls?

Or how about a hash of name value pairs?

> Essentially, I'm simulating varargs using the Velocity list primitive.
> Ugly, but not too bad. But now I want to be able to pass an additional,
> optional argument (the Locale). Using my current method, that means two
> more versions of the I18N macro. That's starting to get ugly. Worse, I
> have eight other macros that delegate to these macros, so their arg lists
> must be replicated as well--yielding three cut-and-paste variants apiece
> on nine different base macros! One begins to understand how OO started...
> 
> There seem to be several ways to approach this:
> 
> * have a smarter interpreter that matches # of arguments passed
> to the macro with one of several macros with the same name (advantage of
> behaving like other scripting languages; disadvantage of likely being
> the most complex to implement, cutting across large parts of code)

Yuk.  We are starting to move quickly away from VTL as simple and small.

> * have #macro ALWAYS take a list type rather than having it declare
> named parameters (don't like this option at all because it breaks
> code and makes macros less self-documenting, but it has performance and
> simplicity benefits over the first verison)

-1

> # create a #va_macro primitive that has the above properties instead
> (advantage of not breaking existing code and isolating the special
> case; disadvantage of probably creating duplicate code inside the
> interpreter and bloating the language)

It's not an interpreter issue.  This might be straightforward, but you
still have the problem of accessing the args in the VM body.

Any reason a hash won't work?

> * have the interpreter pass a null value for any "missing" trailing
> arguments so the macro can figure out its own defaults

This sounds like it could cause chaos for normal designer users.

> * tell Kyle to go hang himself ;-)

Starting to think seriously about the last bit :)
 
> I prefer the penultimate option myself, and am willing to give it a shot.
> What do you folks think?

There is another option - what about just redefining the VMs as you need
to?  I.e. since this is a standalone program, why not establish the VMs
at runtime? 

Do you know while processing which set of VMs you would need?  I

> --kd
> 
> p.s. I can donate Vpp to the project as well if you don't feel it overlaps
> too much with Texen. It's very simple thanks to Ant and Velocity. The only
> problem with using it on Java code is that you hve to escape out the #
> signs from Javadoc cross-references and any dollar-sign references that
> break the parser.

Doesn't this seem  more like an Ant task?

geir

-- 
Geir Magnusson Jr.                               geirm@optonline.net
Developing for the web?  See http://jakarta.apache.org/velocity/

Re: VelociMacros and varargs

Posted by "Kyle F. Downey" <kd...@amberarcher.com>.
>
> Velocity is ideal for things like
>
> #if $java2
>   import java.util.Map;
> #else
>   import com.sun.java.util.collections.Map;
>

*sheepish*

Whoops! That wouldn't be ideal because it would break the parser. ;-)
What I thought, not what I typed:

#if ($java2)
  import java.util.Map;
#else
  import com.sun.java.util.collections.Map
#end

--kd