| [ < ] | [ > ] | [ << ] | [ 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.8/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.
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:
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.8/examples/foreachq2.m4':
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.8/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:
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.
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 January, 5 2007 using texi2html 1.76.