(Dr.R.K.) Web Paging - Tips and Hints on M4 quoting

Go to Top Go to Bottom blank blank Go up one level R.K.'s Home Page Keyword Index Site Map Help Linux
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

Macros 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 Substitutions

By 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 Arguments

Given 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 this

Red Blue Red

Evaluations of ifdefs and ifelses

Take for example the following m4 code:
define(`_m',    `macro :$*:')dnl
ifdef(_x,_m(some text))dnl
which unremarkably produces no output since _x is not defined. However, by looking at the GNU m4 --debug=aeqt output
m4trace: -1- define(`_m', `macro :$*:')
m4trace: -1- dnl
m4trace: -2- _m(`some text') -> `macro :some text:'
m4trace: -1- ifdef(`_x', `macro :some text:')
m4trace: -1- dnl
This demonstrates that the _m macro is being needlessly evaluated even though it will not ultimately be viewed. By quoting the conditional argument
ifdef(_x,`_m(some text)')dnl
We 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- dnl
This 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 macro

A 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-xyz
where we expect the redefined `xxx(xyz)' to show
YYY=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')dnl
which 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.


Brought to you by: R.K. Owen,Ph.D.
This page is http://rkowen.owentrek.com/howto/webpaging/m4tipsquote.html