Next: , Previous: Improved exch, Up: Answers


17.2 Solution for forloop

The 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.