m4: Improved forloop

 
 17.2 Solution for 'forloop'
 ===========================
 
 The 'forloop' macro (⇒Forloop) as presented earlier can go into
 an infinite loop if given an iterator that is not parsed as a macro
 name.  It does not do any sanity checking on its numeric bounds, and
 only permits decimal numbers for bounds.  Here is an improved version,
 shipped as 'm4-1.4.18/examples/forloop2.m4'; this version also optimizes
 overhead by calling four macros instead of six per iteration (excluding
 those in TEXT), by not dereferencing the ITERATOR in the helper
 '_forloop'.
 
      $ m4 -d -I examples
      undivert(`forloop2.m4')dnl
      =>divert(`-1')
      =># forloop(var, from, to, stmt) - improved version:
      =>#   works even if VAR is not a strict macro name
      =>#   performs sanity check that FROM is larger than TO
      =>#   allows complex numerical expressions in TO and FROM
      =>define(`forloop', `ifelse(eval(`($2) <= ($3)'), `1',
      =>  `pushdef(`$1')_$0(`$1', eval(`$2'),
      =>    eval(`$3'), `$4')popdef(`$1')')')
      =>define(`_forloop',
      =>  `define(`$1', `$2')$4`'ifelse(`$2', `$3', `',
      =>    `$0(`$1', incr(`$2'), `$3', `$4')')')
      =>divert`'dnl
      include(`forloop2.m4')
      =>
      forloop(`i', `2', `1', `no iteration occurs')
      =>
      forloop(`', `1', `2', ` odd iterator name')
      => odd iterator name odd iterator name
      forloop(`i', `5 + 5', `0xc', ` 0x`'eval(i, `16')')
      => 0xa 0xb 0xc
      forloop(`i', `a', `b', `non-numeric bounds')
      error->m4:stdin:6: bad expression in eval (bad input): (a) <= (b)
      =>
 
    One other change to notice is that the improved version used '_$0'
 rather than '_foreach' to invoke the helper routine.  In general, this
 is a good practice to follow, because then the set of macros can be
 uniformly transformed.  The following example shows a transformation
 that doubles the current quoting and appends a suffix '2' to each
 transformed macro.  If 'foreach' refers to the literal '_foreach', then
 'foreach2' invokes '_foreach' instead of the intended '_foreach2', and
 the mixing of quoting paradigms leads to an infinite recursion loop in
 this example.
 
      $ m4 -d -L 9 -I examples
      define(`arg1', `$1')include(`forloop2.m4')include(`quote.m4')
      =>
      define(`double', `define(`$1'`2',
        arg1(patsubst(dquote(defn(`$1')), `[`']', `\&\&')))')
      =>
      double(`forloop')double(`_forloop')defn(`forloop2')
      =>ifelse(eval(``($2) <= ($3)''), ``1'',
      =>  ``pushdef(``$1'')_$0(``$1'', eval(``$2''),
      =>    eval(``$3''), ``$4'')popdef(``$1'')'')
      forloop(i, 1, 5, `ifelse(')forloop(i, 1, 5, `)')
      =>
      changequote(`[', `]')changequote([``], [''])
      =>
      forloop2(i, 1, 5, ``ifelse('')forloop2(i, 1, 5, ``)'')
      =>
      changequote`'include(`forloop.m4')
      =>
      double(`forloop')double(`_forloop')defn(`forloop2')
      =>pushdef(``$1'', ``$2'')_forloop($@)popdef(``$1'')
      forloop(i, 1, 5, `ifelse(')forloop(i, 1, 5, `)')
      =>
      changequote(`[', `]')changequote([``], [''])
      =>
      forloop2(i, 1, 5, ``ifelse('')forloop2(i, 1, 5, ``)'')
      error->m4:stdin:12: recursion limit of 9 exceeded, use -L<N> to change it
 
    One more optimization is still possible.  Instead of repeatedly
 assigning a variable then invoking or dereferencing it, it is possible
 to pass the current iterator value as a single argument.  Coupled with
 'curry' if other arguments are needed (⇒Composition), or with
 helper macros if the argument is needed in more than one place in the
 expansion, the output can be generated with three, rather than four,
 macros of overhead per iteration.  Notice how the file
 'm4-1.4.18/examples/forloop3.m4' rearranges the arguments of the helper
 '_forloop' to take two arguments that are placed around the current
 value.  By splitting a balanced set of parantheses across multiple
 arguments, the helper macro can now be shared by 'forloop' and the new
 'forloop_arg'.
 
      $ m4 -I examples
      include(`forloop3.m4')
      =>
      undivert(`forloop3.m4')dnl
      =>divert(`-1')
      =># forloop_arg(from, to, macro) - invoke MACRO(value) for
      =>#   each value between FROM and TO, without define overhead
      =>define(`forloop_arg', `ifelse(eval(`($1) <= ($2)'), `1',
      =>  `_forloop(`$1', eval(`$2'), `$3(', `)')')')
      =># forloop(var, from, to, stmt) - refactored to share code
      =>define(`forloop', `ifelse(eval(`($2) <= ($3)'), `1',
      =>  `pushdef(`$1')_forloop(eval(`$2'), eval(`$3'),
      =>    `define(`$1',', `)$4')popdef(`$1')')')
      =>define(`_forloop',
      =>  `$3`$1'$4`'ifelse(`$1', `$2', `',
      =>    `$0(incr(`$1'), `$2', `$3', `$4')')')
      =>divert`'dnl
      forloop(`i', `1', `3', ` i')
      => 1 2 3
      define(`echo', `$@')
      =>
      forloop_arg(`1', `3', ` echo')
      => 1 2 3
      include(`curry.m4')
      =>
      forloop_arg(`1', `3', `curry(`pushdef', `a')')
      =>
      a
      =>3
      popdef(`a')a
      =>2
      popdef(`a')a
      =>1
      popdef(`a')a
      =>a
 
    Of course, it is possible to make even more improvements, such as
 adding an optional step argument, or allowing iteration through
 descending sequences.  GNU Autoconf provides some of these additional
 bells and whistles in its 'm4_for' macro.