Next: Improved foreach, Previous: Improved exch, Up: Answers
forloopThe forloop macro (see 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.11/examples/forloop2.m4; this
version also optimizes based on the fact that the starting bound does
not need to be passed to the helper _forloop.
$ m4 -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(`($3) >= ($2)'), `1',
=> `pushdef(`$1', eval(`$2'))_$0(`$1',
=> eval(`$3'), `$4')popdef(`$1')')')
=>define(`_forloop',
=> `$3`'ifelse(indir(`$1'), `$2', `',
=> `define(`$1', incr(indir(`$1')))$0($@)')')
=>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): (b) >= (a)
=>
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(``($3) >= ($2)''), ``1'',
=> ``pushdef(``$1'', eval(``$2''))_$0(``$1'',
=> 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
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.