m4: Improved forloop

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