[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Some of the examples in this manuals are buggy or not very robust, for demonstration purposes. Improved versions of these composite macros are presented here.
17.1 Solution for exch | ||
17.2 Solution for forloop | ||
17.3 Solution for foreach | ||
17.4 Solution for cleardivert | ||
17.5 Solution for fatal_error |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
exch
The exch
macro (see section Arguments to macros) as presented requires clients
to double quote their arguments. A nicer definition, which lets
clients follow the rule of thumb of one level of quoting per level of
parentheses, involves adding quotes in the definition of exch
, as
follows:
define(`exch', ``$2', `$1'') ⇒ define(exch(`expansion text', `macro')) ⇒ macro ⇒expansion text |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
forloop
The forloop
macro (see section Iteration by counting) 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.10/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'))_forloop(`$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) ⇒ |
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
foreach
The foreach
and foreachq
macros (see section Iteration by list contents) as
presented earlier each have flaws. First, we will examine and fix the
quadratic behavior of foreachq
:
$ m4 -I examples include(`foreachq.m4') ⇒ traceon(`shift')debugmode(`aq') ⇒ foreachq(`x', ``1', `2', `3', `4'', `x ')dnl ⇒1 error-->m4trace: -3- shift(`1', `2', `3', `4') error-->m4trace: -2- shift(`1', `2', `3', `4') ⇒2 error-->m4trace: -4- shift(`1', `2', `3', `4') error-->m4trace: -3- shift(`2', `3', `4') error-->m4trace: -3- shift(`1', `2', `3', `4') error-->m4trace: -2- shift(`2', `3', `4') ⇒3 error-->m4trace: -5- shift(`1', `2', `3', `4') error-->m4trace: -4- shift(`2', `3', `4') error-->m4trace: -3- shift(`3', `4') error-->m4trace: -4- shift(`1', `2', `3', `4') error-->m4trace: -3- shift(`2', `3', `4') error-->m4trace: -2- shift(`3', `4') ⇒4 error-->m4trace: -6- shift(`1', `2', `3', `4') error-->m4trace: -5- shift(`2', `3', `4') error-->m4trace: -4- shift(`3', `4') error-->m4trace: -3- shift(`4') |
Each successive iteration was adding more quoted shift
invocations, and the entire list contents were passing through every
iteration. In general, when recursing, it is a good idea to make the
recursion use fewer arguments, rather than adding additional quoted
uses of shift
. By doing so, m4
uses less memory, invokes
fewer macros, is less likely to run into machine limits, and most
importantly, performs faster. The fixed version of foreachq
can
be found in `m4-1.4.10/examples/foreachq2.m4':
$ m4 -I examples include(`foreachq2.m4') ⇒ undivert(`foreachq2.m4')dnl ⇒include(`quote.m4')dnl ⇒divert(`-1') ⇒# foreachq(x, `item_1, item_2, ..., item_n', stmt) ⇒# quoted list, improved version ⇒define(`foreachq', `pushdef(`$1')_foreachq($@)popdef(`$1')') ⇒define(`_arg1q', ``$1'') ⇒define(`_rest', `ifelse(`$#', `1', `', `dquote(shift($@))')') ⇒define(`_foreachq', `ifelse(`$2', `', `', ⇒ `define(`$1', _arg1q($2))$3`'$0(`$1', _rest($2), `$3')')') ⇒divert`'dnl traceon(`shift')debugmode(`aq') ⇒ foreachq(`x', ``1', `2', `3', `4'', `x ')dnl ⇒1 error-->m4trace: -3- shift(`1', `2', `3', `4') ⇒2 error-->m4trace: -3- shift(`2', `3', `4') ⇒3 error-->m4trace: -3- shift(`3', `4') ⇒4 |
Note that the fixed version calls unquoted helper macros in
_foreachq
to trim elements immediately; those helper macros
in turn must re-supply the layer of quotes lost in the macro invocation.
Contrast the use of _arg1q
, which quotes the first list
element, with _arg1
of the earlier implementation that
returned the first list element directly.
For a different approach, the improved version of foreach
,
available in `m4-1.4.10/examples/foreach2.m4', simply
overquotes the arguments to _foreach
to begin with, using
dquote_elt
. Then _foreach
can just use
_arg1
to remove the extra layer of quoting that was added up
front:
$ m4 -I examples include(`foreach2.m4') ⇒ undivert(`foreach2.m4')dnl ⇒include(`quote.m4')dnl ⇒divert(`-1') ⇒# foreach(x, (item_1, item_2, ..., item_n), stmt) ⇒# parenthesized list, improved version ⇒define(`foreach', `pushdef(`$1')_foreach(`$1', ⇒ (dquote(dquote_elt$2)), `$3')popdef(`$1')') ⇒define(`_arg1', `$1') ⇒define(`_foreach', `ifelse(`$2', `(`')', `', ⇒ `define(`$1', _arg1$2)$3`'$0(`$1', (dquote(shift$2)), `$3')')') ⇒divert`'dnl traceon(`shift')debugmode(`aq') ⇒ foreach(`x', `(`1', `2', `3', `4')', `x ')dnl error-->m4trace: -4- shift(`1', `2', `3', `4') error-->m4trace: -4- shift(`2', `3', `4') error-->m4trace: -4- shift(`3', `4') ⇒1 error-->m4trace: -3- shift(``1'', ``2'', ``3'', ``4'') ⇒2 error-->m4trace: -3- shift(``2'', ``3'', ``4'') ⇒3 error-->m4trace: -3- shift(``3'', ``4'') ⇒4 error-->m4trace: -3- shift(``4'') |
In summary, recursion over list elements is trickier than it appeared at
first glance, but provides a powerful idiom within m4
processing.
As a final demonstration, both list styles are now able to handle
several scenarios that would wreak havoc on the original
implementations. This points out one other difference between the two
list styles. foreach
evaluates unquoted list elements only once,
in preparation for calling _foreach
. But foreachq
evaluates unquoted list elements twice while visiting the first list
element, once in _arg1q
and once in _rest
. When
deciding which list style to use, one must take into account whether
repeating the side effects of unquoted list elements will have any
detrimental effects.
$ m4 -I examples include(`foreach2.m4') ⇒ include(`foreachq2.m4') ⇒ dnl 0-element list: foreach(`x', `', `<x>') / foreachq(`x', `', `<x>') ⇒ / dnl 1-element list of empty element foreach(`x', `()', `<x>') / foreachq(`x', ``'', `<x>') ⇒<> / <> dnl 2-element list of empty elements foreach(`x', `(`',`')', `<x>') / foreachq(`x', ``',`'', `<x>') ⇒<><> / <><> dnl 1-element list of a comma foreach(`x', `(`,')', `<x>') / foreachq(`x', ``,'', `<x>') ⇒<,> / <,> dnl 2-element list of unbalanced parentheses foreach(`x', `(`(', `)')', `<x>') / foreachq(`x', ``(', `)'', `<x>') ⇒<(><)> / <(><)> define(`active', `ACT, IVE') ⇒ traceon(`active') ⇒ dnl list of unquoted macros; expansion occurs before recursion foreach(`x', `(active, active)', `<x> ')dnl error-->m4trace: -4- active -> `ACT, IVE' error-->m4trace: -4- active -> `ACT, IVE' ⇒<ACT> ⇒<IVE> ⇒<ACT> ⇒<IVE> foreachq(`x', `active, active', `<x> ')dnl error-->m4trace: -3- active -> `ACT, IVE' error-->m4trace: -3- active -> `ACT, IVE' ⇒<ACT> error-->m4trace: -3- active -> `ACT, IVE' error-->m4trace: -3- active -> `ACT, IVE' ⇒<IVE> ⇒<ACT> ⇒<IVE> dnl list of quoted macros; expansion occurs during recursion foreach(`x', `(`active', `active')', `<x> ')dnl error-->m4trace: -1- active -> `ACT, IVE' ⇒<ACT, IVE> error-->m4trace: -1- active -> `ACT, IVE' ⇒<ACT, IVE> foreachq(`x', ``active', `active'', `<x> ')dnl error-->m4trace: -1- active -> `ACT, IVE' ⇒<ACT, IVE> error-->m4trace: -1- active -> `ACT, IVE' ⇒<ACT, IVE> dnl list of double-quoted macro names; no expansion foreach(`x', `(``active'', ``active'')', `<x> ')dnl ⇒<active> ⇒<active> foreachq(`x', ```active'', ``active''', `<x> ')dnl ⇒<active> ⇒<active> |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
cleardivert
The cleardivert
macro (see section Discarding diverted text) cannot, as it stands, be
called without arguments to clear all pending diversions. That is
because using undivert with an empty string for an argument is different
than using it with no arguments at all. Compare the earlier definition
with one that takes the number of arguments into account:
define(`cleardivert', `pushdef(`_n', divnum)divert(`-1')undivert($@)divert(_n)popdef(`_n')') ⇒ divert(`1')one divert ⇒ cleardivert ⇒ undivert ⇒one ⇒ define(`cleardivert', `pushdef(`_num', divnum)divert(`-1')ifelse(`$#', `0', `undivert`'', `undivert($@)')divert(_num)popdef(`_num')') ⇒ divert(`2')two divert ⇒ cleardivert ⇒ undivert ⇒ |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
fatal_error
The fatal_error
macro (see section Exiting from m4
) is not robust to versions
of GNU M4 earlier than 1.4.8, where invoking
__file__
(see section Printing current location) inside m4wrap
would result
in an empty string, and __line__
resulted in `0' even
though all files start at line 1. Furthermore, versions earlier than
1.4.6 did not support the __program__
macro. If you want
fatal_error
to work across the entire 1.4.x release series, a
better implementation would be:
define(`fatal_error', `errprint(ifdef(`__program__', `__program__', ``m4'')'dnl `:ifelse(__line__, `0', `', `__file__:__line__:')` fatal error: $* ')m4exit(`1')') ⇒ m4wrap(`divnum(`demo of internal message') fatal_error(`inside wrapped text')') ⇒ ^D error-->m4:stdin:6: Warning: excess arguments to builtin `divnum' ignored ⇒0 error-->m4:stdin:6: fatal error: inside wrapped text |
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by root on October, 4 2007 using texi2html 1.76.