(m4.info)Improved forloop


Next: Improved foreach Prev: Improved exch Up: Answers
Enter node , (file) or (file)node

17.2 Solution for 'forloop'
===========================

The 'forloop' macro (Note: 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 (Note: 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.


automatically generated by info2www version 1.2.2.9