For reasons I won't disclose, I've come to need a macro processor to generate a bunch of .java files. Somebody at work is using m4 for a similar task, so I decided to try it. Unfortunately, using macro processors with Java brings a huge problem - you typically cannot use an IDE like IntelliJ to edit the raw files. For example, this trivial macro program cannot be parsed by IntelliJ:
public class HelloNTimes {
public static void main(String[] args) {
forloop(`i', 1, HELLO_COUNT, `System.out.println("Hello i");
')
}
}`</pre>
After running it through m4 (first adding the standard [m4 forloop](http://www.gnu.org/software/m4/manual/html_node/Loops.html)), the output is compilable:
<pre>`public class HelloNTimes {
public static void main(String[] args) {
System.out.println("Hello 1");
System.out.println("Hello 2");
System.out.println("Hello 3");
System.out.println("Hello 4");
}
}`</pre>
But I'd like to have my cake and eat it too. I'd like to be able to edit a .java file that is both well-formed and contains macros! To make this possible, I need alternate code for every piece of macro code that I write. This alternate code serves many purposes:
<li>it allows me to reference symbols defined only by macros
</li><li>it gives me something to verify the generated code against
Including the alternate code, here's what the HelloNTimes app looks like:
<pre>`public class HelloNTimes {
public static void main(String[] args) {
/* BEGIN_M4_MACRO forloop(`i', 1, HELLO_COUNT, `System.out.println("Hello i"); ') END_M4_MACRO */ // BEGIN_M4_ALTERNATE
System.out.println("Hello 1");
System.out.println("Hello 2");
// END_M4_ALTERNATE
}
}`</pre>
As you can see, the 'alternate' section tells you what the macro could generate. Of course, if the parameters passed to the macro change (in this case _HELLO_COUNT_), the alternate will not equal what's generated. Due to the careful use of commenting, `/*` and `*/`, this code will compile fine! That means I can edit this file just fine in my IDE.
Then I add m4 definitions, to remove my markers when the file is processed:
<pre>`define(`BEGIN_M4_MACRO', ` BEGIN M4 MACRO GENERATED CODE *'`/')
define(`END_M4_MACRO', `/'`* END M4 MACRO GENERATED CODE ')
define(`BEGIN_M4_ALTERNATE', `BEGIN M4 ALTERNATE CODE
/'`* ')
define(`END_M4_ALTERNATE', `END ALTERNATE CODE *'`/')`</pre>
Finally I can see what the whole works generates. Note that it keeps the alternate code inside a comment, so I can verify that it's generating what I expect:
<pre>`public class HelloNTimes {
public static void main(String[] args) {
/* BEGIN M4 MACRO GENERATED CODE */
System.out.println("Hello 1");
System.out.println("Hello 2");
System.out.println("Hello 3");
System.out.println("Hello 4");
/* END M4 MACRO GENERATED CODE */ // BEGIN M4 ALTERNATE CODE/* System.out.println("Hello 1"); System.out.println("Hello 2"); // END ALTERNATE CODE */
}
}
Note that the generated code has 4 hellos, wheras the alternate has only 2. This is because I'm running m4 with a parameter, -DHELLO_COUNT=4
.
Preprocessor code is inconvenient, but when I can edit in my IDE, it loses a lot of its pain! If you're interested, please look at the example HelloNTimes.jh source file, or my real-world usage in NodeN.