Next: Forloop, Previous: Ifelse, Up: Conditionals [Contents][Index]
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:
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:
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 ‘$@’. Another example of
this is an implementation of a short-circuiting conditional operator.
Similar to ifelse
, where an equal comparison between the first
two strings results in the third, otherwise the first three arguments
are discarded and the process repeats. The difference is that each
test-<n> is expanded only when it is encountered. This means that
every third argument to cond
is normally given one more level of
quoting than the corresponding argument to ifelse
.
Here is the implementation of cond
, along with a demonstration of
how it can short-circuit the side effects in side
. Notice how
all the unquoted side effects happen regardless of how many comparisons
are made with ifelse
, compared with only the relevant effects
with cond
.
define(`cond', `ifelse(`$#', `1', `$1', `ifelse($1, `$2', `$3', `$0(shift(shift(shift($@))))')')')dnl define(`side', `define(`counter', incr(counter))$1')dnl define(`example1', `define(`counter', `0')dnl ifelse(side(`$1'), `yes', `one comparison: ', side(`$1'), `no', `two comparisons: ', side(`$1'), `maybe', `three comparisons: ', `side(`default answer: ')')counter')dnl define(`example2', `define(`counter', `0')dnl cond(`side(`$1')', `yes', `one comparison: ', `side(`$1')', `no', `two comparisons: ', `side(`$1')', `maybe', `three comparisons: ', `side(`default answer: ')')counter')dnl example1(`yes') ⇒one comparison: 3 example1(`no') ⇒two comparisons: 3 example1(`maybe') ⇒three comparisons: 3 example1(`feeling rather indecisive today') ⇒default answer: 4 example2(`yes') ⇒one comparison: 1 example2(`no') ⇒two comparisons: 2 example2(`maybe') ⇒three comparisons: 3 example2(`feeling rather indecisive today') ⇒default answer: 4
Another common task that requires iteration is joining a list of arguments into a single string.
Generate a single-quoted string, consisting of each arg separated
by separator. While joinall
always outputs a
separator between arguments, join
avoids the
separator for an empty arg.
Here are some examples of its usage, based on the implementation m4-1.4.19/examples/join.m4 distributed in this package:
$ m4 -I examples include(`join.m4') ⇒ join,join(`-'),join(`-', `'),join(`-', `', `') ⇒,,, joinall,joinall(`-'),joinall(`-', `'),joinall(`-', `', `') ⇒,,,- join(`-', `1') ⇒1 join(`-', `1', `2', `3') ⇒1-2-3 join(`', `1', `2', `3') ⇒123 join(`-', `', `1', `', `', `2', `') ⇒1-2 joinall(`-', `', `1', `', `', `2', `') ⇒-1---2- join(`,', `1', `2', `3') ⇒1,2,3 define(`nargs', `$#')dnl nargs(join(`,', `1', `2', `3')) ⇒1
Examining the implementation shows some interesting points about several m4 programming idioms.
$ m4 -I examples undivert(`join.m4')dnl ⇒divert(`-1') ⇒# join(sep, args) - join each non-empty ARG into a single ⇒# string, with each element separated by SEP ⇒define(`join', ⇒`ifelse(`$#', `2', ``$2'', ⇒ `ifelse(`$2', `', `', ``$2'_')$0(`$1', shift(shift($@)))')') ⇒define(`_join', ⇒`ifelse(`$#$2', `2', `', ⇒ `ifelse(`$2', `', `', ``$1$2'')$0(`$1', shift(shift($@)))')') ⇒# joinall(sep, args) - join each ARG, including empty ones, ⇒# into a single string, with each element separated by SEP ⇒define(`joinall', ``$2'_$0(`$1', shift($@))') ⇒define(`_joinall', ⇒`ifelse(`$#', `2', `', ``$1$3'$0(`$1', shift(shift($@)))')') ⇒divert`'dnl
First, notice that this implementation creates helper macros
_join
and _joinall
. This division of labor makes it
easier to output the correct number of separator instances:
join
and joinall
are responsible for the first argument,
without a separator, while _join
and _joinall
are
responsible for all remaining arguments, always outputting a separator
when outputting an argument.
Next, observe how join
decides to iterate to itself, because the
first arg was empty, or to output the argument and swap over to
_join
. If the argument is non-empty, then the nested
ifelse
results in an unquoted ‘_’, which is concatenated
with the ‘$0’ to form the next macro name to invoke. The
joinall
implementation is simpler since it does not have to
suppress empty arg; it always executes once then defers to
_joinall
.
Another important idiom is the idea that separator is reused for
each iteration. Each iteration has one less argument, but rather than
discarding ‘$1’ by iterating with $0(shift($@))
, the macro
discards ‘$2’ by using $0(`$1', shift(shift($@)))
.
Next, notice that it is possible to compare more than one condition in a
single ifelse
test. The test of ‘$#$2’ against ‘2’
allows _join
to iterate for two separate reasons—either there
are still more than two arguments, or there are exactly two arguments
but the last argument is not empty.
Finally, notice that these macros require exactly two arguments to
terminate recursion, but that they still correctly result in empty
output when given no args (i.e., zero or one macro argument). On
the first pass when there are too few arguments, the shift
results in no output, but leaves an empty string to serve as the
required second argument for the second pass. Put another way,
‘`$1', shift($@)’ is not the same as ‘$@’, since only the
former guarantees at least two arguments.
Sometimes, a recursive algorithm requires adding quotes to each element, or treating multiple arguments as a single element:
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.19/examples/quote.m4 in this package. First, let’s examine their usage:
$ m4 -I examples 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 distinguish
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.
$ m4 -I examples 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
It is worth pointing out that ‘quote(args)’ is more efficient than ‘joinall(`,', args)’ for producing the same output.
One more useful macro based on shift
allows portably selecting
an arbitrary argument (usually greater than the ninth argument), without
relying on the GNU extension of multi-digit arguments
(see Arguments).
Expands to argument n out of the remaining arguments. n must be a positive number. Usually invoked as ‘argn(`n',$@)’.
It is implemented as:
define(`argn', `ifelse(`$1', 1, ``$2'', `argn(decr(`$1'), shift(shift($@)))')') ⇒ argn(`1', `a') ⇒a define(`foo', `argn(`11', $@)') ⇒ foo(`a', `b', `c', `d', `e', `f', `g', `h', `i', `j', `k', `l') ⇒k
Next: Forloop, Previous: Ifelse, Up: Conditionals [Contents][Index]