[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

# 6. Conditionals, loops, and recursion

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.

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## 6.1 Testing if a macro is defined

There are two different builtin conditionals in `m4`. The first is `ifdef`:

Builtin: ifdef (name, string-1, [string-2])

  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] | [ ? ]

## 6.2 If-else construct, or multibranch

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:

Builtin: ifelse (comment)
Builtin: ifelse (string-1, string-2, equal, [not-equal])
Builtin: ifelse (string-1, string-2, equal-1, 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. A final fifth argument is ignored, after triggering a warning.

  If called with six or more arguments, and string-1 and string-2 are equal, `ifelse` expands into equal-1, 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')
    error-->m4:stdin:1: Warning: excess arguments to builtin `ifelse' ignored
    ⇒gnu
    ifelse(`foo', `bar', `third', `gnu', `gnats', `sixth')
    ⇒
    ifelse(`foo', `bar', `third', `gnu', `gnats', `sixth', `seventh')
    ⇒seventh
    ifelse(`foo', `bar', `3', `gnu', `gnats', `6', `7', `8')
    error-->m4:stdin:4: Warning: excess arguments to builtin `ifelse' ignored
    ⇒7

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] | [ ? ]

## 6.3 Recursion in `m4`

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:

Builtin: shift (arg1, …)

  Takes any number of arguments, and expands to all its arguments except arg1, 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:

Composite: reverse (…)

  Takes any number of arguments, and reverses 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. It also shows that `shift` is usually used with ``$@'`. Sometimes, a recursive algorithm requires adding quotes to each element:

Composite: quote (…)
Composite: dquote (…)
Composite: dquote_elt (…)

  Takes any number of arguments, and adds quoting. With `quote`, only one level of quoting is added, effectively removing whitespace after commas and turning multiple arguments into a single string. With `dquote`, two levels of quoting are added, one around each element, and one around the list. And with `dquote_elt`, two levels of quoting are added around each element.

An actual implementation of these three macros is distributed as ``m4-1.4.8/examples/quote.m4'` in this package. First, let's examine their usage:

    include(`quote.m4')
    ⇒
    -quote-dquote-dquote_elt-
    ⇒----
    -quote()-dquote()-dquote_elt()-
    ⇒--`'-`'-
    -quote(`1')-dquote(`1')-dquote_elt(`1')-
    ⇒-1-`1'-`1'-
    -quote(`1', `2')-dquote(`1', `2')-dquote_elt(`1', `2')-
    ⇒-1,2-`1',`2'-`1',`2'-
    define(`n', `$#')dnl
    -n(quote(`1', `2'))-n(dquote(`1', `2'))-n(dquote_elt(`1', `2'))-
    ⇒-1-1-2-
    dquote(dquote_elt(`1', `2'))
    ⇒``1'',``2''
    dquote_elt(dquote(`1', `2'))
    ⇒``1',`2''

The last two lines show that when given two arguments, `dquote` results in one string, while `dquote_elt` results in two. Now, examine the implementation. Note that `quote` and `dquote_elt` make decisions based on their number of arguments, so that when called without arguments, they result in nothing instead of a quoted empty string; this is so that it is possible to distinquish between no arguments and an empty first argument. `dquote`, on the other hand, results in a string no matter what, since it is still possible to tell whether it was invoked without arguments based on the resulting string.

    undivert(`quote.m4')dnl
    ⇒divert(`-1')
    ⇒# quote(args) - convert args to single-quoted string
    ⇒define(`quote', `ifelse(`$#', `0', `', ``$*'')')
    ⇒# dquote(args) - convert args to quoted list of quoted strings
    ⇒define(`dquote', ``$@'')
    ⇒# dquote_elt(args) - convert args to list of double-quoted strings
    ⇒define(`dquote_elt', `ifelse(`$#', `0', `', `$#', `1', ```$1''',
    ⇒                             ```$1'',$0(shift($@))')')
    ⇒divert`'dnl

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## 6.4 Iteration by counting

Here is an example of a loop macro that implements a simple for loop.

Composite: forloop (iterator, start, end, text)

  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 the iterator has reached the final value. If it has not finished, it increments the iterator (using the predefined macro `incr`, see section Decrement and increment operators), and recurses.

Here is an actual implementation of `forloop`, distributed as ``m4-1.4.8/examples/forloop.m4'` in this package:

    undivert(`forloop.m4')dnl
    ⇒divert(`-1')
    ⇒# forloop(var, from, to, stmt) - simple version
    ⇒define(`forloop', `pushdef(`$1', `$2')_forloop($@)popdef(`$1')')
    ⇒define(`_forloop',
    ⇒       `$4`'ifelse($1, `$3', `', `define(`$1', incr($1))$0($@)')')
    ⇒divert`'dnl

Notice the careful use of quotes. Certain macro arguments are left unquoted, each for its own reason. Try to find out why these arguments are left unquoted, and see what happens if they are quoted. (As presented, these two macros are useful but not very robust for general use. They lack even basic error handling for cases like start less than end, end not numeric, or iterator not being a macro name. See if you can improve these macros; or see section Answers).

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## 6.5 Iteration by list contents

Here is an example of a loop macro that implements list iteration.

Composite: foreach (iterator, paren-list, text)
Composite: foreachq (iterator, quote-list, text)

  Takes the name in iterator, which must be a valid macro name, and successively assign it each value from paren-list or quote-list. In `foreach`, paren-list is a comma-separated list of elements contained in parentheses. In `foreachq`, quote-list is a comma-separated list of elements contained in a quoted string. For each assignment to iterator, append text to the overall expansion. text may refer to iterator. Any definition of iterator prior to this invocation is restored.

As an example, this displays each word in a list inside of a sentence, using an implementation of `foreach` distributed as ``m4-1.4.8/examples/foreach.m4'`, and `foreachq` in ``m4-1.4.8/examples/foreachq.m4'`.

    include(`foreach.m4')
    ⇒
    foreach(`x', (foo, bar, foobar), `Word was: x
    ')dnl
    ⇒Word was: foo
    ⇒Word was: bar
    ⇒Word was: foobar
    include(`foreachq.m4')
    ⇒
    foreachq(`x', `foo, bar, foobar', `Word was: x
    ')dnl
    ⇒Word was: foo
    ⇒Word was: bar
    ⇒Word was: foobar

It is possible to be more complex; each element of the paren-list or quote-list can itself be a list, to pass as further arguments to a helper macro. This example generates a shell case statement:

    include(`foreach.m4')
    ⇒
    define(`_case', `  $1)
        $2=" $1";;
    ')dnl
    define(`_cat', `$1$2')dnl
    case $`'1 in
    ⇒case $1 in
    foreach(`x', `(`(`a', `vara')', `(`b', `varb')', `(`c', `varc')')',
            `_cat(`_case', x)')dnl
    ⇒  a)
    ⇒    vara=" a";;
    ⇒  b)
    ⇒    varb=" b";;
    ⇒  c)
    ⇒    varc=" c";;
    esac
    ⇒esac

The implementation of the `foreach` macro is a bit more involved; it is a wrapper around two helper macros. First, `_arg1` is needed to grab the first element of a list. Second, `_foreach` implements the recursion, successively walking through the original list. Here is a simple implementation of `foreach`:

    undivert(`foreach.m4')dnl
    ⇒divert(`-1')
    ⇒# foreach(x, (item_1, item_2, ..., item_n), stmt)
    ⇒#   parenthesized list, simple version
    ⇒define(`foreach', `pushdef(`$1')_foreach($@)popdef(`$1')')
    ⇒define(`_arg1', `$1')
    ⇒define(`_foreach', `ifelse(`$2', `()', `',
    ⇒  `define(`$1', _arg1$2)$3`'$0(`$1', (shift$2), `$3')')')
    ⇒divert`'dnl

Unfortunately, that implementation is not robust to macro names as list elements. Each iteration of `_foreach` is stripping another layer of quotes, leading to erratic results if list elements are not already fully expanded. The first cut at implementing `foreachq` takes this into account. Also, when using quoted elements in a paren-list, the overall list must be quoted. A quote-list has the nice property of requiring fewer characters to create a list containing the same quoted elements. To see the difference between the two macros, we attempt to pass double-quoted macro names in a list, expecting the macro name on output after one layer of quotes is removed during list iteration and the final layer removed during the final rescan:

    define(`a', `1')define(`b', `2')define(`c', `3')
    ⇒
    include(`foreach.m4')
    ⇒
    include(`foreachq.m4')
    ⇒
    foreach(`x', `(``a'', ``(b'', ``c)'')', `x
    ')
    ⇒1
    ⇒(2)1
    ⇒
    ⇒, x
    ⇒)
    foreachq(`x', ```a'', ``(b'', ``c)''', `x
    ')dnl
    ⇒a
    ⇒(b
    ⇒c)

Obviously, `foreachq` did a better job; here is its implementation:

    undivert(`foreachq.m4')dnl
    ⇒include(`quote.m4')dnl
    ⇒divert(`-1')
    ⇒# foreachq(x, `item_1, item_2, ..., item_n', stmt)
    ⇒#   quoted list, simple version
    ⇒define(`foreachq', `pushdef(`$1')_foreachq($@)popdef(`$1')')
    ⇒define(`_arg1', `$1')
    ⇒define(`_foreachq', `ifelse(quote($2), `', `',
    ⇒  `define(`$1', `_arg1($2)')$3`'$0(`$1', `shift($2)', `$3')')')
    ⇒divert`'dnl

Notice that `_foreachq` had to use the helper macro `quote` defined earlier (see section Recursion in `m4`), to ensure that the embedded `ifelse` call does not go haywire if a list element contains a comma. Unfortunately, this implementation of `foreachq` has its own severe flaw. Whereas the `foreach` implementation was linear, this macro is quadratic in the number of list elements, and is much more likely to trip up the limit set by the command line option ``--nesting-limit'` (or ``-L'`, see section Invoking m4). (It is possible to have robust iteration with linear behavior for either list style. See if you can learn from the best elements of both of these implementations to create robust macros; or see section Answers).

[ << ] | [ >> ] |  |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

This document was generated by root on April, 16 2008 using texi2html 1.76.