Next: , Previous: , Up: Answers   [Contents][Index]


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.19/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 (see 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.19/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.


Next: , Previous: , Up: Answers   [Contents][Index]