Next: Stacks, Previous: Forloop, Up: Conditionals [Contents][Index]
Here is an example of a loop macro that implements list iteration.
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.19/examples/foreach.m4, and foreachq
in m4-1.4.19/examples/foreachq.m4.
$ m4 -I examples 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:
$ m4 -I examples 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
:
$ m4 -I examples 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:
$ m4 -I examples 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:
$ m4 -I examples 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 Shift), 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 Invoking m4). Additionally, this implementation does not expand
‘defn(`iterator')’ very well, when compared with
foreach
.
$ m4 -I examples include(`foreach.m4')include(`foreachq.m4') ⇒ foreach(`name', `(`a', `b')', ` defn(`name')') ⇒ a b foreachq(`name', ``a', `b'', ` defn(`name')') ⇒ _arg1(`a', `b') _arg1(shift(`a', `b'))
It is possible to have robust iteration with linear behavior and sane iterator contents for either list style. See if you can learn from the best elements of both of these implementations to create robust macros (or see Answers).
Next: Stacks, Previous: Forloop, Up: Conditionals [Contents][Index]