The `m4' macro processor is widely available on all UNIXes. Usually, only a small percentage of users are aware of its existence. However, those who do often become commited users.
Some people found `m4' to be fairly addictive. They first use `m4' for simple problems, then take bigger and bigger challenges, learning how to write complex `m4' sets of macros along the way. Once really addicted, users pursue writing of sophisticated `m4' applications even to solve simple problems, devoting more time debugging their `m4' scripts than doing real work. Beware that `m4' may be dangerous for the health of compulsive programmers.
GNU m4 info page
The m4 macro language isn't very hard to master, but does pose some unique challenges once past the rudimentory simple macro substitution. However, it's certainly an ``addiction'' where the accomplished artist seeks out ever more complicated and dangerous tasks.
Quite simply, there are things you can do with m4 that can't easily be done any other way. On this page, I highlight some quoting techniques that lead to more efficient macro substitutions and may help avoid problems. These tips aren't obvious and only came after gaining much experience in the crafting of this m4-to-html package.
- Order of nested macro substitutions
- Order of quoted nested macro substitutions
- When not to quote macro arguments
- Evaluations of ifdefs and ifelses
- Having one macro redefine another macro
Order of Nested Macro SubstitutionsMacros with unquoted arguments are evaluated from inside out. For example:define(`_i', 0)dnl define counter define(`_xinc', `define(`$1',incr($1))')dnl x++ define(`_m1', `_xinc(`_i') macro 1 :_i: ($*)')dnl define(`_m2', `_xinc(`_i') macro 2 :_i: ($*)')dnl dnl _m1(_m2(some text :_i:))the dnl macro just leaves off any text after it, even the new-line character. It's a good way to add comments to some tricky code. The results with passing the text to m4 is shown below:macro 1 :2: ( macro 2 :1: (some text :0:))Notice that the ``increment'' variable _i which is initialized to 0 shows the inside text of macro _m2 is evaluated first and then _m2 before _m1. This is exactly the opposite of what you might expect.
Order of Nested Quoted Macro SubstitutionsBy quoting the arguments to each of the macros (show below)_m1(`_m2(`some text :_i:')')will yield the following results.macro 1 :1: ( macro 2 :2: (some text :2:))Clearly showing that _m1 is evaluated fully before continuing on with the rest of the text inside. Note that putting the quotes in the definition around the $* has no effect, but may have unwanted side effects later.
Depending on what you're trying to accomplish; generally, if you're passing a macro as an argument to another macro then quote the argument. This will force outer-to-inner evaluation of the macros.
When not to Quote Macro ArgumentsGiven the above sections, it would appear that you should always quote any arguments. There are some macros that you should probably not quote. These are the numerical ones - incr,decr, and eval. The following example illustrates some uses.define(`swp',1)dnl define(`swapcolors',`define(`swp',eval(incr(swp) % 2))dnl ifelse(swp,0,`Red',`Blue')')dnl swapcolors swapcolors swapcolors
The macro swp is our variable which contains a numerical value. The macro swapcolors is our procedure which changes the variable swp by redefining it and outputs text dependent on its value. If the eval or incr macros were to have their arguments quoted, it would delay the interpretation and instead of numerical values they would see text instead, which causes m4 to complain: Non-numeric argument to built-in `incr'.
The output looks like thisRed Blue Red
Evaluations of ifdefs and ifelsesTake for example the following m4 code:define(`_m', `macro :$*:')dnl ifdef(_x,_m(some text))dnlwhich unremarkably produces no output since _x is not defined. However, by looking at the GNU m4 --debug=aeqt outputm4trace: -1- define(`_m', `macro :$*:') m4trace: -1- dnl m4trace: -2- _m(`some text') -> `macro :some text:' m4trace: -1- ifdef(`_x', `macro :some text:') m4trace: -1- dnlThis demonstrates that the _m macro is being needlessly evaluated even though it will not ultimately be viewed. By quoting the conditional argumentifdef(_x,`_m(some text)')dnlWe see that the _m macro evaluation is avoided.m4trace: -1- define(`_m', `macro :$*:') m4trace: -1- dnl m4trace: -1- ifdef(`_x', `_m(some text)') m4trace: -1- dnlThis example is linked to the nested macro example above and shows that judicious quoting can lead to more efficient macro evaluations especially with conditional macros, ifdef and ifelse.
Having one macro redefine another macroA useful trick sometimes is to have one macro, when called, to redefine another macro. This is useful if you need to modify the actions of the other macro for special circumstances.
The following might be the naïve approach:define(`xxx', `XXX-$1')dnl xxx(abc) define(`redxxx', `--$1-- pushdef(`xxx', `YYY=$1')dnl')dnl redxxx(abc) xxx(xyz) popdef(`xxx')dnl xxx(xyz)where we define `xxx', then modify it when `redxxx' is invoked. The argument passed to `redxxx' is for some banner, and is not intended to be part of `xxx' in any fashion. The results of this code generates the following:XXX-abc --abc-- YYY=abc XXX-xyzwhere we expect the redefined `xxx(xyz)' to showYYY=xyz
The problem is that the $1 in the `pushdef' redefinition of `xxx' is interpreted as the first argument of the macro `redxxx', and is then substituted into the redefinition. This is not what we intended.
We need to delay the interpretation of $1. That can be done by inserting quote marks:define(`redxxx', `--$1-- pushdef(`xxx', `YYY=$'`1')dnl')dnlwhich will give the correct results. However, this technique can be difficult to get it right. You need to know the correct level of macro nesting.
Another, and in my opinion, easier approach is to define an ``alternative'' macro and use it later:define(`xxx', `XXX-$1')dnl xxx(abc) define(`altxxx', `YYY=$1')dnl define(`redxxx', `--$1-- pushdef(`xxx', defn(`altxxx'))dnl')dnl redxxx(abc) xxx(xyz) popdef(`xxx')dnl xxx(xyz)which calls up the definition for `altxxx' and then pushes it onto the definition for `xxx'. Now we get the expected results:XXX-abc --abc-- YYY=xyz XXX-xyz
Note the use of `pushdef' and `popdef' to ``push'' and ``pop'' the definitions onto the stack. This way you can easily recall the previous definition, when the exceptional condition is no longer in effect.
Last Modified: 2002/12/03 17:56:40
Brought to you by: R.K. Owen,Ph.D.
This page is http://owen.sj.ca.us/rkowen/howto/webpaging/m4tipsquote.html