File: m4.info, Node: Improved forloop, Next: Improved foreach, Prev: Improved exch, Up: Answers 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.20/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 ‘_forloop’ 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 ‘forloop’ refers to the literal ‘_forloop’, then ‘forloop2’ invokes ‘_forloop’ instead of the intended ‘_forloop2’, 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 -Lto 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.20/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 parentheses 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.