[ < ] | [ > ] | [ << ] | [ 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.
[ < ] | [ > ] | [ << ] | [ 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.16/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 section Building macros with macros), 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.16/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.
[ < ] | [ > ] | [ << ] | [ 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.16/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')_$0($@)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. Additionally, by calling the
helper method immediately, the ‘defn(`iterator')’ no longer
contains unexpanded macros.
The astute m4 programmer might notice that the solution above still uses
more memory and macro invocations, and thus more time, than strictly
necessary. Note that ‘$2’, which contains an arbitrarily long
quoted list, is expanded and rescanned three times per iteration of
_foreachq
. Furthermore, every iteration of the algorithm
effectively unboxes then reboxes the list, which costs a couple of macro
invocations. It is possible to rewrite the algorithm for a bit more
speed by swapping the order of the arguments to _foreachq
in
order to operate on an unboxed list in the first place, and by using the
fixed-length ‘$#’ instead of an arbitrary length list as the key to
end recursion. The result is an overhead of six macro invocations per
loop (excluding any macros in text), instead of eight. This
alternative approach is available as
‘m4-1.4.16/examples/foreach3.m4’:
$ m4 -I examples include(`foreachq3.m4') ⇒ undivert(`foreachq3.m4')dnl ⇒divert(`-1') ⇒# foreachq(x, `item_1, item_2, ..., item_n', stmt) ⇒# quoted list, alternate improved version ⇒define(`foreachq', `ifelse(`$2', `', `', ⇒ `pushdef(`$1')_$0(`$1', `$3', `', $2)popdef(`$1')')') ⇒define(`_foreachq', `ifelse(`$#', `3', `', ⇒ `define(`$1', `$4')$2`'$0(`$1', `$2', ⇒ shift(shift(shift($@))))')') ⇒divert`'dnl traceon(`shift')debugmode(`aq') ⇒ foreachq(`x', ``1', `2', `3', `4'', `x ')dnl ⇒1 error-->m4trace: -4- shift(`x', `x error-->', `', `1', `2', `3', `4') error-->m4trace: -3- shift(`x error-->', `', `1', `2', `3', `4') error-->m4trace: -2- shift(`', `1', `2', `3', `4') ⇒2 error-->m4trace: -4- shift(`x', `x error-->', `1', `2', `3', `4') error-->m4trace: -3- shift(`x error-->', `1', `2', `3', `4') error-->m4trace: -2- shift(`1', `2', `3', `4') ⇒3 error-->m4trace: -4- shift(`x', `x error-->', `2', `3', `4') error-->m4trace: -3- shift(`x error-->', `2', `3', `4') error-->m4trace: -2- shift(`2', `3', `4') ⇒4 error-->m4trace: -4- shift(`x', `x error-->', `3', `4') error-->m4trace: -3- shift(`x error-->', `3', `4') error-->m4trace: -2- shift(`3', `4') |
In the current version of M4, every instance of ‘$@’ is rescanned
as it is encountered. Thus, the ‘foreachq3.m4’ alternative uses
much less memory than ‘foreachq2.m4’, and executes as much as 10%
faster, since each iteration encounters fewer ‘$@’. However, the
implementation of rescanning every byte in ‘$@’ is quadratic in
the number of bytes scanned (for example, making the broken version in
‘foreachq.m4’ cubic, rather than quadratic, in behavior). A future
release of M4 will improve the underlying implementation by reusing
results of previous scans, so that both styles of foreachq
can
become linear in the number of bytes scanned. Notice how the
implementation injects an empty argument prior to expanding ‘$2’
within foreachq
; the helper macro _foreachq
then ignores
the third argument altogether, and ends recursion when there are three
arguments left because there was nothing left to pass through
shift
. Thus, each iteration only needs one ifelse
, rather
than the two conditionals used in the version from ‘foreachq2.m4’.
So far, all of the implementations of foreachq
presented have
been quadratic with M4 1.4.x. But forloop
is linear, because
each iteration parses a constant amount of arguments. So, it is
possible to design a variant that uses forloop
to do the
iteration, then uses ‘$@’ only once at the end, giving a linear
result even with older M4 implementations. This implementation relies
on the GNU extension that ‘$10’ expands to the tenth
argument rather than the first argument concatenated with ‘0’. The
trick is to define an intermediate macro that repeats the text
m4_define(`$1', `$n')$2`'
, with ‘n’ set to successive
integers corresponding to each argument. The helper macro
_foreachq_
is needed in order to generate the literal sequences
such as ‘$1’ into the intermediate macro, rather than expanding
them as the arguments of _foreachq
. With this approach, no
shift
calls are even needed! Even though there are seven macros
of overhead per iteration instead of six in ‘foreachq3.m4’, the
linear scaling is apparent at relatively small list sizes. However,
this approach will need adjustment when a future version of M4 follows
POSIX by no longer treating ‘$10’ as the tenth argument;
the anticipation is that ‘${10}’ can be used instead, although
that alternative syntax is not yet supported.
$ m4 -I examples include(`foreachq4.m4') ⇒ undivert(`foreachq4.m4')dnl ⇒include(`forloop2.m4')dnl ⇒divert(`-1') ⇒# foreachq(x, `item_1, item_2, ..., item_n', stmt) ⇒# quoted list, version based on forloop ⇒define(`foreachq', ⇒`ifelse(`$2', `', `', `_$0(`$1', `$3', $2)')') ⇒define(`_foreachq', ⇒`pushdef(`$1', forloop(`$1', `3', `$#', ⇒ `$0_(`1', `2', indir(`$1'))')`popdef( ⇒ `$1')')indir(`$1', $@)') ⇒define(`_foreachq_', ⇒``define(`$$1', `$$3')$$2`''') ⇒divert`'dnl traceon(`shift')debugmode(`aq') ⇒ foreachq(`x', ``1', `2', `3', `4'', `x ')dnl ⇒1 ⇒2 ⇒3 ⇒4 |
For yet another approach, the improved version of foreach
,
available in ‘m4-1.4.16/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')_$0(`$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'') |
It is likewise possible to write a variant of foreach
that
performs in linear time on M4 1.4.x; the easiest method is probably
writing a version of foreach
that unboxes its list, then invokes
_foreachq
as previously defined in ‘foreachq4.m4’.
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 one or both of the original
implementations. This points out one other difference between the
list styles. foreach
evaluates unquoted list elements only once,
in preparation for calling _foreach
, similary for
foreachq
as provided by ‘foreachq3.m4’ or
‘foreachq4.m4’. But
foreachq
, as provided by ‘foreachq2.m4’,
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(`ab', `oops')dnl using defn(`iterator') foreach(`x', `(`a', `b')', `defn(`x')') /dnl foreachq(`x', ``a', `b'', `defn(`x')') ⇒ab / ab 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] | [ ? ] |
copy
The macro copy
presented above
is unable to handle builtin tokens with M4 1.4.x, because it tries to
pass the builtin token through the macro curry
, where it is
silently flattened to an empty string (see section Building macros with macros). Rather
than using the problematic curry
to work around the limitation
that stack_foreach
expects to invoke a macro that takes exactly
one argument, we can write a new macro that lets us form the exact
two-argument pushdef
call sequence needed, so that we are no
longer passing a builtin token through a text macro.
For each of the pushdef
definitions associated with macro,
expand the sequence ‘pre`'definition`'post’.
Additionally, expand sep between definitions.
stack_foreach_sep
visits the oldest definition first, while
stack_foreach_sep_lifo
visits the current definition first. The
expansion may dereference macro, but should not modify it. There
are a few special macros, such as defn
, which cannot be used as
the macro parameter.
Note that stack_foreach(`macro', `action')
is
equivalent to stack_foreach_sep(`macro', `action(',
`)')
. By supplying explicit parentheses, split among the pre and
post arguments to stack_foreach_sep
, it is now possible to
construct macro calls with more than one argument, without passing
builtin tokens through a macro call. It is likewise possible to
directly reference the stack definitions without a macro call, by
leaving pre and post empty. Thus, in addition to fixing
copy
on builtin tokens, it also executes with fewer macro
invocations.
The new macro also adds a separator that is only output after the first
iteration of the helper _stack_reverse_sep
, implemented by
prepending the original sep to pre and omitting a sep
argument in subsequent iterations. Note that the empty string that
separates sep from pre is provided as part of the fourth
argument when originally calling _stack_reverse_sep
, and not by
writing $4`'$3
as the third argument in the recursive call; while
the other approach would give the same output, it does so at the expense
of increasing the argument size on each iteration of
_stack_reverse_sep
, which results in quadratic instead of linear
execution time. The improved stack walking macros are available in
‘m4-1.4.16/examples/stack_sep.m4’:
$ m4 -I examples include(`stack_sep.m4') ⇒ define(`copy', `ifdef(`$2', `errprint(`$2 already defined ')m4exit(`1')', `stack_foreach_sep(`$1', `pushdef(`$2',', `)')')')dnl pushdef(`a', `1')pushdef(`a', defn(`divnum')) ⇒ copy(`a', `b') ⇒ b ⇒0 popdef(`b') ⇒ b ⇒1 pushdef(`c', `1')pushdef(`c', `2') ⇒ stack_foreach_sep_lifo(`c', `', `', `, ') ⇒2, 1 undivert(`stack_sep.m4')dnl ⇒divert(`-1') ⇒# stack_foreach_sep(macro, pre, post, sep) ⇒# Invoke PRE`'defn`'POST with a single argument of each definition ⇒# from the definition stack of MACRO, starting with the oldest, and ⇒# separated by SEP between definitions. ⇒define(`stack_foreach_sep', ⇒`_stack_reverse_sep(`$1', `tmp-$1')'dnl ⇒`_stack_reverse_sep(`tmp-$1', `$1', `$2`'defn(`$1')$3', `$4`'')') ⇒# stack_foreach_sep_lifo(macro, pre, post, sep) ⇒# Like stack_foreach_sep, but starting with the newest definition. ⇒define(`stack_foreach_sep_lifo', ⇒`_stack_reverse_sep(`$1', `tmp-$1', `$2`'defn(`$1')$3', `$4`'')'dnl ⇒`_stack_reverse_sep(`tmp-$1', `$1')') ⇒define(`_stack_reverse_sep', ⇒`ifdef(`$1', `pushdef(`$2', defn(`$1'))$3`'popdef(`$1')$0( ⇒ `$1', `$2', `$4$3')')') ⇒divert`'dnl |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
m4wrap
The replacement m4wrap
versions presented above, designed to
guarantee FIFO or LIFO order regardless of the underlying M4
implementation, share a bug when dealing with wrapped text that looks
like parameter expansion. Note how the invocation of
m4wrapn
interprets these parameters, while using the
builtin preserves them for their intended use.
$ m4 -I examples include(`wraplifo.m4') ⇒ m4wrap(`define(`foo', ``$0:'-$1-$*-$#-')foo(`a', `b') ') ⇒ builtin(`m4wrap', ``'define(`bar', ``$0:'-$1-$*-$#-')bar(`a', `b') ') ⇒ ^D ⇒bar:-a-a,b-2- ⇒m4wrap0:---0- |
Additionally, the computation of _m4wrap_level
and creation of
multiple m4wrapn
placeholders in the original examples is
more expensive in time and memory than strictly necessary. Notice how
the improved version grabs the wrapped text via defn
to avoid
parameter expansion, then undefines _m4wrap_text
, before
stripping a level of quotes with _arg1
to expand the text. That
way, each level of wrapping reuses the single placeholder, which starts
each nesting level in an undefined state.
Finally, it is worth emulating the GNU M4 extension of saving
all arguments to m4wrap
, separated by a space, rather than saving
just the first argument. This is done with the join
macro
documented previously (see section Recursion in m4
). The improved LIFO example is
shipped as ‘m4-1.4.16/examples/wraplifo2.m4’, and can
easily be converted to a FIFO solution by swapping the adjacent
invocations of joinall
and defn
.
$ m4 -I examples include(`wraplifo2.m4') ⇒ undivert(`wraplifo2.m4')dnl ⇒dnl Redefine m4wrap to have LIFO semantics, improved example. ⇒include(`join.m4')dnl ⇒define(`_m4wrap', defn(`m4wrap'))dnl ⇒define(`_arg1', `$1')dnl ⇒define(`m4wrap', ⇒`ifdef(`_$0_text', ⇒ `define(`_$0_text', joinall(` ', $@)defn(`_$0_text'))', ⇒ `_$0(`_arg1(defn(`_$0_text')undefine(`_$0_text'))')dnl ⇒define(`_$0_text', joinall(` ', $@))')')dnl m4wrap(`define(`foo', ``$0:'-$1-$*-$#-')foo(`a', `b') ') ⇒ m4wrap(`lifo text m4wrap(`nested', `', `$@ ')') ⇒ ^D ⇒lifo text ⇒foo:-a-a,b-2- ⇒nested $@ |
[ < ] | [ > ] | [ << ] | [ 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] | [ ? ] |
capitalize
The capitalize
macro (see section Substituting text by regular expression) as presented earlier does
not allow clients to follow the quoting rule of thumb. Consider the
three macros active
, Active
, and ACTIVE
, and the
difference between calling capitalize
with the expansion of a
macro, expanding the result of a case change, and changing the case of a
double-quoted string:
$ m4 -I examples include(`capitalize.m4')dnl define(`active', `act1, ive')dnl define(`Active', `Act2, Ive')dnl define(`ACTIVE', `ACT3, IVE')dnl upcase(active) ⇒ACT1,IVE upcase(`active') ⇒ACT3, IVE upcase(``active'') ⇒ACTIVE downcase(ACTIVE) ⇒act3,ive downcase(`ACTIVE') ⇒act1, ive downcase(``ACTIVE'') ⇒active capitalize(active) ⇒Act1 capitalize(`active') ⇒Active capitalize(``active'') ⇒_capitalize(`active') define(`A', `OOPS') ⇒ capitalize(active) ⇒OOPSct1 capitalize(`active') ⇒OOPSctive |
First, when capitalize
is called with more than one argument, it
was throwing away later arguments, whereas upcase
and
downcase
used ‘$*’ to collect them all. The fix is simple:
use ‘$*’ consistently.
Next, with single-quoting, capitalize
outputs a single character,
a set of quotes, then the rest of the characters, making it impossible
to invoke Active
after the fact, and allowing the alternate macro
A
to interfere. Here, the solution is to use additional quoting
in the helper macros, then pass the final over-quoted output string
through _arg1
to remove the extra quoting and finally invoke the
concatenated portions as a single string.
Finally, when passed a double-quoted string, the nested macro
_capitalize
is never invoked because it ended up nested inside
quotes. This one is the toughest to fix. In short, we have no idea how
many levels of quotes are in effect on the substring being altered by
patsubst
. If the replacement string cannot be expressed entirely
in terms of literal text and backslash substitutions, then we need a
mechanism to guarantee that the helper macros are invoked outside of
quotes. In other words, this sounds like a job for changequote
(see section Changing the quote characters). By changing the active quoting characters, we
can guarantee that replacement text injected by patsubst
always
occurs in the middle of a string that has exactly one level of
over-quoting using alternate quotes; so the replacement text closes the
quoted string, invokes the helper macros, then reopens the quoted
string. In turn, that means the replacement text has unbalanced quotes,
necessitating another round of changequote
.
In the fixed version below, (also shipped as
‘m4-1.4.16/examples/capitalize2.m4’), capitalize
uses the alternate quotes of ‘<<[’ and ‘]>>’ (the longer
strings are chosen so as to be less likely to appear in the text being
converted). The helpers _to_alt
and _from_alt
merely
reduce the number of characters required to perform a
changequote
, since the definition changes twice. The outermost
pair means that patsubst
and _capitalize_alt
are invoked
with alternate quoting; the innermost pair is used so that the third
argument to patsubst
can contain an unbalanced
‘]>>’/‘<<[’ pair. Note that upcase
and downcase
must be redefined as _upcase_alt
and _downcase_alt
, since
they contain nested quotes but are invoked with the alternate quoting
scheme in effect.
$ m4 -I examples include(`capitalize2.m4')dnl define(`active', `act1, ive')dnl define(`Active', `Act2, Ive')dnl define(`ACTIVE', `ACT3, IVE')dnl define(`A', `OOPS')dnl capitalize(active; `active'; ``active''; ```actIVE''') ⇒Act1,Ive; Act2, Ive; Active; `Active' undivert(`capitalize2.m4')dnl ⇒divert(`-1') ⇒# upcase(text) ⇒# downcase(text) ⇒# capitalize(text) ⇒# change case of text, improved version ⇒define(`upcase', `translit(`$*', `a-z', `A-Z')') ⇒define(`downcase', `translit(`$*', `A-Z', `a-z')') ⇒define(`_arg1', `$1') ⇒define(`_to_alt', `changequote(`<<[', `]>>')') ⇒define(`_from_alt', `changequote(<<[`]>>, <<[']>>)') ⇒define(`_upcase_alt', `translit(<<[$*]>>, <<[a-z]>>, <<[A-Z]>>)') ⇒define(`_downcase_alt', `translit(<<[$*]>>, <<[A-Z]>>, <<[a-z]>>)') ⇒define(`_capitalize_alt', ⇒ `regexp(<<[$1]>>, <<[^\(\w\)\(\w*\)]>>, ⇒ <<[_upcase_alt(<<[<<[\1]>>]>>)_downcase_alt(<<[<<[\2]>>]>>)]>>)') ⇒define(`capitalize', ⇒ `_arg1(_to_alt()patsubst(<<[<<[$*]>>]>>, <<[\w+]>>, ⇒ _from_alt()`]>>_$0_alt(<<[\&]>>)<<['_to_alt())_from_alt())') ⇒divert`'dnl |
[ < ] | [ > ] | [ << ] | [ 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 March 13, 2013 using texi2html 1.82.