[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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. For that, 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.
5.1 Testing macro definitions | Testing if a macro is defined | |
5.2 Comparing strings | If-else construct, or multibranch | |
5.3 Loops and recursion | Loops and recursion in m4 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
There are two different builtin conditionals in m4
. The first is
ifdef
:
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
rules).
The macro ifdef
is recognized only with parameters.
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(`no_such_macro', `yes', `no', `extra argument') error-->m4:stdin:4: Warning: excess arguments to builtin `ifdef' ignored ⇒no |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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:
[not-equal])
string-3, string-4, equal-2, …)
Used with only one argument, the ifelse
simply discards it and
produces no output.
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.
If called with six or more arguments, and string-1 and
string-2 are equal, ifelse
expands into equal,
otherwise the first three arguments are discarded and the processing
starts again.
The macro ifelse
is recognized only with parameters.
Using only one argument is a common m4
idiom for introducing a
block comment, as an alternative to repeatedly using dnl
. This
special usage is recognized by GNU m4
, so that in this
case, the warning about missing arguments is never triggered.
ifelse(`some comments') ⇒ ifelse(`foo', `bar') error-->m4:stdin:2: Warning: too few arguments to builtin `ifelse' ⇒ |
Using three or four arguments provides decision points.
ifelse(`foo', `bar', `true') ⇒ ifelse(`foo', `foo', `true') ⇒true define(`foo', `bar') ⇒ ifelse(foo, `bar', `true', `false') ⇒true ifelse(foo, `foo', `true', `false') ⇒false |
Notice how the first argument was used unquoted; it is common to compare the expansion of a macro with a string. With this macro, you can now reproduce the behavior of many of the builtins, where the macro is recognized only with arguments.
define(`foo', `ifelse(`$#', `0', ``$0'', `arguments:$#')') ⇒ foo ⇒foo foo() ⇒arguments:1 foo(`a', `b', `c') ⇒arguments:3 |
However, ifelse
can take more than four arguments. If given more
than four arguments, ifelse
works like a case
or switch
statement in traditional programming languages. If string-1 and
string-2 are equal, ifelse
expands into equal-1, 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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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:
Takes any number of arguments, and expands to all but the first argument, separated by commas, with each argument quoted.
The macro shift
is recognized only with parameters.
shift ⇒shift shift(`bar') ⇒ shift(`foo', `bar', `baz') ⇒bar,baz |
An example of the use of shift
is this macro:
Takes any number of arguments, and reverse their order.
It is implemented as:
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
made with shift
, ifelse
and recursion.
Here is an example of a loop macro that implements a simple for loop.
Takes the name in iterator, which must be a valid macro name, and
successively assign it each integer value from start to end,
inclusive. For each assignment to iterator, append text to
the expansion of the forloop
. text may refer to
iterator. Any definition of iterator prior to this
invocation is restored.
It can, for example, be used for simple counting:
include(`forloop.m4') ⇒ forloop(`i', `1', `8', `i ') ⇒1 2 3 4 5 6 7 8 |
For-loops can be nested, like:
include(`forloop.m4') ⇒ 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
straightforward. The forloop
macro itself is simply a wrapper,
which saves the previous definition of the first argument, calls the
internal macro _forloop
, and re-establishes the saved definition of
the first argument.
The macro _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 incr
,
see section Decrement and increment operators), and recurses.
Here is the actual implementation of forloop
, distributed as
`examples/forloop.m4' in this package:
undivert(`forloop.m4') ⇒divert(`-1') ⇒# forloop(var, from, to, stmt) ⇒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')')') ⇒divert`'dnl ⇒ |
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.
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by System Administrator on September, 23 2007 using texi2html 1.70.