Next: , Previous: Ifelse, Up: Conditionals


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