m4: Foreach
1
1 6.5 Iteration by list contents
1 ==============================
1
1 Here is an example of a loop macro that implements list iteration.
1
1 -- Composite: foreach (ITERATOR, PAREN-LIST, TEXT)
1 -- Composite: foreachq (ITERATOR, QUOTE-LIST, TEXT)
1 Takes the name in ITERATOR, which must be a valid macro name, and
1 successively assign it each value from PAREN-LIST or QUOTE-LIST.
1 In 'foreach', PAREN-LIST is a comma-separated list of elements
1 contained in parentheses. In 'foreachq', QUOTE-LIST is a
1 comma-separated list of elements contained in a quoted string. For
1 each assignment to ITERATOR, append TEXT to the overall expansion.
1 TEXT may refer to ITERATOR. Any definition of ITERATOR prior to
1 this invocation is restored.
1
1 As an example, this displays each word in a list inside of a
1 sentence, using an implementation of 'foreach' distributed as
1 'm4-1.4.18/examples/foreach.m4', and 'foreachq' in
1 'm4-1.4.18/examples/foreachq.m4'.
1
1 $ m4 -I examples
1 include(`foreach.m4')
1 =>
1 foreach(`x', (foo, bar, foobar), `Word was: x
1 ')dnl
1 =>Word was: foo
1 =>Word was: bar
1 =>Word was: foobar
1 include(`foreachq.m4')
1 =>
1 foreachq(`x', `foo, bar, foobar', `Word was: x
1 ')dnl
1 =>Word was: foo
1 =>Word was: bar
1 =>Word was: foobar
1
1 It is possible to be more complex; each element of the PAREN-LIST or
1 QUOTE-LIST can itself be a list, to pass as further arguments to a
1 helper macro. This example generates a shell case statement:
1
1 $ m4 -I examples
1 include(`foreach.m4')
1 =>
1 define(`_case', ` $1)
1 $2=" $1";;
1 ')dnl
1 define(`_cat', `$1$2')dnl
1 case $`'1 in
1 =>case $1 in
1 foreach(`x', `(`(`a', `vara')', `(`b', `varb')', `(`c', `varc')')',
1 `_cat(`_case', x)')dnl
1 => a)
1 => vara=" a";;
1 => b)
1 => varb=" b";;
1 => c)
1 => varc=" c";;
1 esac
1 =>esac
1
1 The implementation of the 'foreach' macro is a bit more involved; it
1 is a wrapper around two helper macros. First, '_arg1' is needed to grab
1 the first element of a list. Second, '_foreach' implements the
1 recursion, successively walking through the original list. Here is a
1 simple implementation of 'foreach':
1
1 $ m4 -I examples
1 undivert(`foreach.m4')dnl
1 =>divert(`-1')
1 =># foreach(x, (item_1, item_2, ..., item_n), stmt)
1 =># parenthesized list, simple version
1 =>define(`foreach', `pushdef(`$1')_foreach($@)popdef(`$1')')
1 =>define(`_arg1', `$1')
1 =>define(`_foreach', `ifelse(`$2', `()', `',
1 => `define(`$1', _arg1$2)$3`'$0(`$1', (shift$2), `$3')')')
1 =>divert`'dnl
1
1 Unfortunately, that implementation is not robust to macro names as
1 list elements. Each iteration of '_foreach' is stripping another layer
1 of quotes, leading to erratic results if list elements are not already
1 fully expanded. The first cut at implementing 'foreachq' takes this
1 into account. Also, when using quoted elements in a PAREN-LIST, the
1 overall list must be quoted. A QUOTE-LIST has the nice property of
1 requiring fewer characters to create a list containing the same quoted
1 elements. To see the difference between the two macros, we attempt to
1 pass double-quoted macro names in a list, expecting the macro name on
1 output after one layer of quotes is removed during list iteration and
1 the final layer removed during the final rescan:
1
1 $ m4 -I examples
1 define(`a', `1')define(`b', `2')define(`c', `3')
1 =>
1 include(`foreach.m4')
1 =>
1 include(`foreachq.m4')
1 =>
1 foreach(`x', `(``a'', ``(b'', ``c)'')', `x
1 ')
1 =>1
1 =>(2)1
1 =>
1 =>, x
1 =>)
1 foreachq(`x', ```a'', ``(b'', ``c)''', `x
1 ')dnl
1 =>a
1 =>(b
1 =>c)
1
1 Obviously, 'foreachq' did a better job; here is its implementation:
1
1 $ m4 -I examples
1 undivert(`foreachq.m4')dnl
1 =>include(`quote.m4')dnl
1 =>divert(`-1')
1 =># foreachq(x, `item_1, item_2, ..., item_n', stmt)
1 =># quoted list, simple version
1 =>define(`foreachq', `pushdef(`$1')_foreachq($@)popdef(`$1')')
1 =>define(`_arg1', `$1')
1 =>define(`_foreachq', `ifelse(quote($2), `', `',
1 => `define(`$1', `_arg1($2)')$3`'$0(`$1', `shift($2)', `$3')')')
1 =>divert`'dnl
1
1 Notice that '_foreachq' had to use the helper macro 'quote' defined
1 earlier (⇒Shift), to ensure that the embedded 'ifelse' call does
1 not go haywire if a list element contains a comma. Unfortunately, this
1 implementation of 'foreachq' has its own severe flaw. Whereas the
1 'foreach' implementation was linear, this macro is quadratic in the
1 number of list elements, and is much more likely to trip up the limit
11 set by the command line option '--nesting-limit' (or '-L', ⇒
Invoking m4 Limits control.). Additionally, this implementation does
1 not expand 'defn(`ITERATOR')' very well, when compared with 'foreach'.
1
1 $ m4 -I examples
1 include(`foreach.m4')include(`foreachq.m4')
1 =>
1 foreach(`name', `(`a', `b')', ` defn(`name')')
1 => a b
1 foreachq(`name', ``a', `b'', ` defn(`name')')
1 => _arg1(`a', `b') _arg1(shift(`a', `b'))
1
1 It is possible to have robust iteration with linear behavior and sane
1 ITERATOR contents for either list style. See if you can learn from the
1 best elements of both of these implementations to create robust macros
1 (or ⇒Answers Improved foreach.).
1