Macros, expanding to plain text, perhaps with arguments, are not quite enough. We would like to have macros expand to different things, based on decisions taken at run-time. E.g., we need some kind of conditionals. Also, we would like to have some kind of loop construct, so we could do something a number of times, or while some condition is true.
There are two different builtin conditionals in
m4. The first is
ifdef(name, string-1, opt string-2)
which makes it possible to test whether a macro is defined or not. If
name is defined as a macro,
ifdef expands to
string-1, otherwise to string-2. If string-2 is
omitted, it is taken to be the empty string (according to the normal
ifdef(`foo', ``foo' is defined', ``foo' is not defined') =>foo is not defined define(`foo', `') => ifdef(`foo', ``foo' is defined', ``foo' is not defined') =>foo is defined
ifdef is recognized only with parameters.
The other conditional,
ifelse, is much more powerful. It can be
used as a way to introduce a long comment, as an if-else construct, or
as a multibranch, depending on the number of arguments supplied:
ifelse(comment) ifelse(string-1, string-2, equal, opt not-equal) ifelse(string-1, string-2, equal, ...)
Used with only one argument, the
ifelse simply discards it and
produces no output. This is a common
m4 idiom for introducing a
block comment, as an alternative to repeatedly using
special usage is recognized by GNU
m4, so that in this case, the
warning about missing arguments is never triggered.
If called with three or four arguments,
ifelse expands into
equal, if string-1 and string-2 are equal (character
for character), otherwise it expands to not-equal.
ifelse(foo, bar, `true') => ifelse(foo, foo, `true') =>true ifelse(foo, bar, `true', `false') =>false ifelse(foo, foo, `true', `false') =>true
ifelse can take more than four arguments. If given more
than four arguments,
ifelse works like a
statement in traditional programming languages. If string-1 and
string-2 are equal,
ifelse expands into equal, otherwise
the procedure is repeated with the first three arguments discarded. This
calls for an example:
ifelse(foo, bar, `third', gnu, gnats, `sixth', `seventh') =>seventh
Naturally, the normal case will be slightly more advanced than these
examples. A common use of
ifelse is in macros implementing loops
of various kinds.
ifelse is recognized only with parameters.
There is no direct support for loops in
m4, but macros can be
recursive. There is no limit on the number of recursion levels, other
than those enforced by your hardware and operating system.
Loops can be programmed using recursion and the conditionals described previously.
There is a builtin macro,
shift, which can, among other things,
be used for iterating through the actual arguments to a macro:
It takes any number of arguments, and expands to all but the first argument, separated by commas, with each argument quoted.
shift(bar) => shift(foo, bar, baz) =>bar,baz
An example of the use of
shift is this macro, which reverses the
order of its arguments:
define(`reverse', `ifelse($#, 0, , $#, 1, ``$1'', `reverse(shift($@)), `$1'')') => reverse => reverse(foo) =>foo reverse(foo, bar, gnats, and gnus) =>and gnus, gnats, bar, foo
While not a very interesting macro, it does show how simple loops can be
ifelse and recursion.
Here is an example of a loop macro that implements a simple forloop. It can, for example, be used for simple counting:
forloop(`i', 1, 8, `i ') =>1 2 3 4 5 6 7 8
The arguments are a name for the iteration variable, the starting value,
the final value, and the text to be expanded for each iteration. With
this macro, the macro
i is defined only within the loop. After
the loop, it retains whatever value it might have had before.
For-loops can be nested, like
forloop(`i', 1, 4, `forloop(`j', 1, 8, `(i, j) ') ') =>(1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8) =>(2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8) =>(3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8) =>(4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (4, 6) (4, 7) (4, 8) =>
The implementation of the
forloop macro is fairly
forloop macro itself is simply a wrapper,
which saves the previous definition of the first argument, calls the
_forloop, and re-establishes the saved definition of
the first argument.
_forloop expands the fourth argument once, and tests
to see if it is finished. If it has not finished, it increments
the iteration variable (using the predefined macro
see section Decrement and increment operators), and recurses.
Here is the actual implementation of
define(`forloop', `pushdef(`$1', `$2')_forloop(`$1', `$2', `$3', `$4')popdef(`$1')') define(`_forloop', `$4`'ifelse($1, `$3', , `define(`$1', incr($1))_forloop(`$1', `$2', `$3', `$4')')')
Notice the careful use of quotes. Only three macro arguments are unquoted, each for its own reason. Try to find out why these three arguments are left unquoted, and see what happens if they are quoted.
Now, even though these two macros are useful, they are still not robust enough for general use. They lack even basic error handling of cases like start value less than final value, and the first argument not being a name. Correcting these errors are left as an exercise to the reader.
Go to the first, previous, next, last section, table of contents.