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