[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Macros, expanding to plain text, perhaps with arguments, are not quite enough. We would like to have macros expand to different things, based on decisions taken at run-time. For that, we need some kind of conditionals. Also, we would like to have some kind of loop construct, so we could do something a number of times, or while some condition is true.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
There are two different builtin conditionals in m4
. The first is
ifdef
:
If name is defined as a macro, ifdef
expands to
string-1, otherwise to string-2. If string-2 is
omitted, it is taken to be the empty string (according to the normal
rules).
The macro ifdef
is recognized only with parameters.
ifdef(`foo', ``foo' is defined', ``foo' is not defined') ⇒foo is not defined define(`foo', `') ⇒ ifdef(`foo', ``foo' is defined', ``foo' is not defined') ⇒foo is defined ifdef(`no_such_macro', `yes', `no', `extra argument') error-->m4:stdin:4: Warning: excess arguments to builtin `ifdef' ignored ⇒no |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The other conditional, ifelse
, is much more powerful. It can be
used as a way to introduce a long comment, as an if-else construct, or
as a multibranch, depending on the number of arguments supplied:
Used with only one argument, the ifelse
simply discards it and
produces no output.
If called with three or four arguments, ifelse
expands into
equal, if string-1 and string-2 are equal (character
for character), otherwise it expands to not-equal. A final fifth
argument is ignored, after triggering a warning.
If called with six or more arguments, and string-1 and
string-2 are equal, ifelse
expands into equal-1,
otherwise the first three arguments are discarded and the processing
starts again.
The macro ifelse
is recognized only with parameters.
Using only one argument is a common m4
idiom for introducing a
block comment, as an alternative to repeatedly using dnl
. This
special usage is recognized by GNU m4
, so that in this
case, the warning about missing arguments is never triggered.
ifelse(`some comments') ⇒ ifelse(`foo', `bar') error-->m4:stdin:2: Warning: too few arguments to builtin `ifelse' ⇒ |
Using three or four arguments provides decision points.
ifelse(`foo', `bar', `true') ⇒ ifelse(`foo', `foo', `true') ⇒true define(`foo', `bar') ⇒ ifelse(foo, `bar', `true', `false') ⇒true ifelse(foo, `foo', `true', `false') ⇒false |
Notice how the first argument was used unquoted; it is common to compare the expansion of a macro with a string. With this macro, you can now reproduce the behavior of blind builtins, where the macro is recognized only with arguments.
define(`foo', `ifelse(`$#', `0', ``$0'', `arguments:$#')') ⇒ foo ⇒foo foo() ⇒arguments:1 foo(`a', `b', `c') ⇒arguments:3 |
For an example of a way to make defining blind macros easier, see Building macros with macros.
The macro ifelse
can take more than four arguments. If given more
than four arguments, ifelse
works like a case
or switch
statement in traditional programming languages. If string-1 and
string-2 are equal, ifelse
expands into equal-1, otherwise
the procedure is repeated with the first three arguments discarded. This
calls for an example:
ifelse(`foo', `bar', `third', `gnu', `gnats') error-->m4:stdin:1: Warning: excess arguments to builtin `ifelse' ignored ⇒gnu ifelse(`foo', `bar', `third', `gnu', `gnats', `sixth') ⇒ ifelse(`foo', `bar', `third', `gnu', `gnats', `sixth', `seventh') ⇒seventh ifelse(`foo', `bar', `3', `gnu', `gnats', `6', `7', `8') error-->m4:stdin:4: Warning: excess arguments to builtin `ifelse' ignored ⇒7 |
Naturally, the normal case will be slightly more advanced than these
examples. A common use of ifelse
is in macros implementing loops
of various kinds.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
m4
There is no direct support for loops in m4
, but macros can be
recursive. There is no limit on the number of recursion levels, other
than those enforced by your hardware and operating system.
Loops can be programmed using recursion and the conditionals described previously.
There is a builtin macro, shift
, which can, among other things,
be used for iterating through the actual arguments to a macro:
Takes any number of arguments, and expands to all its arguments except arg1, separated by commas, with each argument quoted.
The macro shift
is recognized only with parameters.
shift ⇒shift shift(`bar') ⇒ shift(`foo', `bar', `baz') ⇒bar,baz |
An example of the use of shift
is this macro:
Takes any number of arguments, and reverses their order.
It is implemented as:
define(`reverse', `ifelse(`$#', `0', , `$#', `1', ``$1'', `reverse(shift($@)), `$1'')') ⇒ reverse ⇒ reverse(`foo') ⇒foo reverse(`foo', `bar', `gnats', `and gnus') ⇒and gnus, gnats, bar, foo |
While not a very interesting macro, it does show how simple loops can be
made with shift
, ifelse
and recursion. It also shows
that shift
is usually used with ‘$@’. Another example of
this is an implementation of a short-circuiting conditional operator.
Similar to ifelse
, where an equal comparison between the first
two strings results in the third, otherwise the first three arguments
are discarded and the process repeats. The difference is that each
test-<n> is expanded only when it is encountered. This means that
every third argument to cond
is normally given one more level of
quoting than the corresponding argument to ifelse
.
Here is the implementation of cond
, along with a demonstration of
how it can short-circuit the side effects in side
. Notice how
all the unquoted side effects happen regardless of how many comparisons
are made with ifelse
, compared with only the relevant effects
with cond
.
define(`cond', `ifelse(`$#', `1', `$1', `ifelse($1, `$2', `$3', `$0(shift(shift(shift($@))))')')')dnl define(`side', `define(`counter', incr(counter))$1')dnl define(`example1', `define(`counter', `0')dnl ifelse(side(`$1'), `yes', `one comparison: ', side(`$1'), `no', `two comparisons: ', side(`$1'), `maybe', `three comparisons: ', `side(`default answer: ')')counter')dnl define(`example2', `define(`counter', `0')dnl cond(`side(`$1')', `yes', `one comparison: ', `side(`$1')', `no', `two comparisons: ', `side(`$1')', `maybe', `three comparisons: ', `side(`default answer: ')')counter')dnl example1(`yes') ⇒one comparison: 3 example1(`no') ⇒two comparisons: 3 example1(`maybe') ⇒three comparisons: 3 example1(`feeling rather indecisive today') ⇒default answer: 4 example2(`yes') ⇒one comparison: 1 example2(`no') ⇒two comparisons: 2 example2(`maybe') ⇒three comparisons: 3 example2(`feeling rather indecisive today') ⇒default answer: 4 |
Another common task that requires iteration is joining a list of arguments into a single string.
Generate a single-quoted string, consisting of each arg separated
by separator. While joinall
always outputs a
separator between arguments, join
avoids the
separator for an empty arg.
Here are some examples of its usage, based on the implementation ‘m4-1.4.16/examples/join.m4’ distributed in this package:
$ m4 -I examples include(`join.m4') ⇒ join,join(`-'),join(`-', `'),join(`-', `', `') ⇒,,, joinall,joinall(`-'),joinall(`-', `'),joinall(`-', `', `') ⇒,,,- join(`-', `1') ⇒1 join(`-', `1', `2', `3') ⇒1-2-3 join(`', `1', `2', `3') ⇒123 join(`-', `', `1', `', `', `2', `') ⇒1-2 joinall(`-', `', `1', `', `', `2', `') ⇒-1---2- join(`,', `1', `2', `3') ⇒1,2,3 define(`nargs', `$#')dnl nargs(join(`,', `1', `2', `3')) ⇒1 |
Examining the implementation shows some interesting points about several m4 programming idioms.
$ m4 -I examples undivert(`join.m4')dnl ⇒divert(`-1') ⇒# join(sep, args) - join each non-empty ARG into a single ⇒# string, with each element separated by SEP ⇒define(`join', ⇒`ifelse(`$#', `2', ``$2'', ⇒ `ifelse(`$2', `', `', ``$2'_')$0(`$1', shift(shift($@)))')') ⇒define(`_join', ⇒`ifelse(`$#$2', `2', `', ⇒ `ifelse(`$2', `', `', ``$1$2'')$0(`$1', shift(shift($@)))')') ⇒# joinall(sep, args) - join each ARG, including empty ones, ⇒# into a single string, with each element separated by SEP ⇒define(`joinall', ``$2'_$0(`$1', shift($@))') ⇒define(`_joinall', ⇒`ifelse(`$#', `2', `', ``$1$3'$0(`$1', shift(shift($@)))')') ⇒divert`'dnl |
First, notice that this implementation creates helper macros
_join
and _joinall
. This division of labor makes it
easier to output the correct number of separator instances:
join
and joinall
are responsible for the first argument,
without a separator, while _join
and _joinall
are
responsible for all remaining arguments, always outputting a separator
when outputting an argument.
Next, observe how join
decides to iterate to itself, because the
first arg was empty, or to output the argument and swap over to
_join
. If the argument is non-empty, then the nested
ifelse
results in an unquoted ‘_’, which is concatenated
with the ‘$0’ to form the next macro name to invoke. The
joinall
implementation is simpler since it does not have to
suppress empty arg; it always executes once then defers to
_joinall
.
Another important idiom is the idea that separator is reused for
each iteration. Each iteration has one less argument, but rather than
discarding ‘$1’ by iterating with $0(shift($@))
, the macro
discards ‘$2’ by using $0(`$1', shift(shift($@)))
.
Next, notice that it is possible to compare more than one condition in a
single ifelse
test. The test of ‘$#$2’ against ‘2’
allows _join
to iterate for two separate reasons—either there
are still more than two arguments, or there are exactly two arguments
but the last argument is not empty.
Finally, notice that these macros require exactly two arguments to
terminate recursion, but that they still correctly result in empty
output when given no args (i.e., zero or one macro argument). On
the first pass when there are too few arguments, the shift
results in no output, but leaves an empty string to serve as the
required second argument for the second pass. Put another way,
‘`$1', shift($@)’ is not the same as ‘$@’, since only the
former guarantees at least two arguments.
Sometimes, a recursive algorithm requires adding quotes to each element, or treating multiple arguments as a single element:
Takes any number of arguments, and adds quoting. With quote
,
only one level of quoting is added, effectively removing whitespace
after commas and turning multiple arguments into a single string. With
dquote
, two levels of quoting are added, one around each element,
and one around the list. And with dquote_elt
, two levels of
quoting are added around each element.
An actual implementation of these three macros is distributed as ‘m4-1.4.16/examples/quote.m4’ in this package. First, let’s examine their usage:
$ m4 -I examples include(`quote.m4') ⇒ -quote-dquote-dquote_elt- ⇒---- -quote()-dquote()-dquote_elt()- ⇒--`'-`'- -quote(`1')-dquote(`1')-dquote_elt(`1')- ⇒-1-`1'-`1'- -quote(`1', `2')-dquote(`1', `2')-dquote_elt(`1', `2')- ⇒-1,2-`1',`2'-`1',`2'- define(`n', `$#')dnl -n(quote(`1', `2'))-n(dquote(`1', `2'))-n(dquote_elt(`1', `2'))- ⇒-1-1-2- dquote(dquote_elt(`1', `2')) ⇒``1'',``2'' dquote_elt(dquote(`1', `2')) ⇒``1',`2'' |
The last two lines show that when given two arguments, dquote
results in one string, while dquote_elt
results in two. Now,
examine the implementation. Note that quote
and
dquote_elt
make decisions based on their number of arguments, so
that when called without arguments, they result in nothing instead of a
quoted empty string; this is so that it is possible to distinguish
between no arguments and an empty first argument. dquote
, on the
other hand, results in a string no matter what, since it is still
possible to tell whether it was invoked without arguments based on the
resulting string.
$ m4 -I examples undivert(`quote.m4')dnl ⇒divert(`-1') ⇒# quote(args) - convert args to single-quoted string ⇒define(`quote', `ifelse(`$#', `0', `', ``$*'')') ⇒# dquote(args) - convert args to quoted list of quoted strings ⇒define(`dquote', ``$@'') ⇒# dquote_elt(args) - convert args to list of double-quoted strings ⇒define(`dquote_elt', `ifelse(`$#', `0', `', `$#', `1', ```$1''', ⇒ ```$1'',$0(shift($@))')') ⇒divert`'dnl |
It is worth pointing out that ‘quote(args)’ is more efficient than ‘joinall(`,', args)’ for producing the same output.
One more useful macro based on shift
allows portably selecting
an arbitrary argument (usually greater than the ninth argument), without
relying on the GNU extension of multi-digit arguments
(see section Arguments to macros).
Expands to argument n out of the remaining arguments. n must be a positive number. Usually invoked as ‘argn(`n',$@)’.
It is implemented as:
define(`argn', `ifelse(`$1', 1, ``$2'', `argn(decr(`$1'), shift(shift($@)))')') ⇒ argn(`1', `a') ⇒a define(`foo', `argn(`11', $@)') ⇒ foo(`a', `b', `c', `d', `e', `f', `g', `h', `i', `j', `k', `l') ⇒k |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Here is an example of a loop macro that implements a simple for loop.
Takes the name in iterator, which must be a valid macro name, and
successively assign it each integer value from start to end,
inclusive. For each assignment to iterator, append text to
the expansion of the forloop
. text may refer to
iterator. Any definition of iterator prior to this
invocation is restored.
It can, for example, be used for simple counting:
$ m4 -I examples include(`forloop.m4') ⇒ forloop(`i', `1', `8', `i ') ⇒1 2 3 4 5 6 7 8 |
For-loops can be nested, like:
$ m4 -I examples include(`forloop.m4') ⇒ forloop(`i', `1', `4', `forloop(`j', `1', `8', ` (i, j)') ') ⇒ (1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8) ⇒ (2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8) ⇒ (3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8) ⇒ (4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (4, 6) (4, 7) (4, 8) ⇒ |
The implementation of the forloop
macro is fairly
straightforward. The forloop
macro itself is simply a wrapper,
which saves the previous definition of the first argument, calls the
internal macro _forloop
, and re-establishes the saved
definition of the first argument.
The macro _forloop
expands the fourth argument once, and
tests to see if the iterator has reached the final value. If it has
not finished, it increments the iterator (using the predefined macro
incr
, see section Decrement and increment operators), and recurses.
Here is an actual implementation of forloop
, distributed as
‘m4-1.4.16/examples/forloop.m4’ in this package:
$ m4 -I examples undivert(`forloop.m4')dnl ⇒divert(`-1') ⇒# forloop(var, from, to, stmt) - simple version ⇒define(`forloop', `pushdef(`$1', `$2')_forloop($@)popdef(`$1')') ⇒define(`_forloop', ⇒ `$4`'ifelse($1, `$3', `', `define(`$1', incr($1))$0($@)')') ⇒divert`'dnl |
Notice the careful use of quotes. Certain macro arguments are left unquoted, each for its own reason. Try to find out why these arguments are left unquoted, and see what happens if they are quoted. (As presented, these two macros are useful but not very robust for general use. They lack even basic error handling for cases like start less than end, end not numeric, or iterator not being a macro name. See if you can improve these macros; or see section Answers).
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Here is an example of a loop macro that implements list iteration.
Takes the name in iterator, which must be a valid macro name, and
successively assign it each value from paren-list or
quote-list. In foreach
, paren-list is a
comma-separated list of elements contained in parentheses. In
foreachq
, quote-list is a comma-separated list of elements
contained in a quoted string. For each assignment to iterator,
append text to the overall expansion. text may refer to
iterator. Any definition of iterator prior to this
invocation is restored.
As an example, this displays each word in a list inside of a sentence,
using an implementation of foreach
distributed as
‘m4-1.4.16/examples/foreach.m4’, and foreachq
in ‘m4-1.4.16/examples/foreachq.m4’.
$ m4 -I examples include(`foreach.m4') ⇒ foreach(`x', (foo, bar, foobar), `Word was: x ')dnl ⇒Word was: foo ⇒Word was: bar ⇒Word was: foobar include(`foreachq.m4') ⇒ foreachq(`x', `foo, bar, foobar', `Word was: x ')dnl ⇒Word was: foo ⇒Word was: bar ⇒Word was: foobar |
It is possible to be more complex; each element of the paren-list or quote-list can itself be a list, to pass as further arguments to a helper macro. This example generates a shell case statement:
$ m4 -I examples include(`foreach.m4') ⇒ define(`_case', ` $1) $2=" $1";; ')dnl define(`_cat', `$1$2')dnl case $`'1 in ⇒case $1 in foreach(`x', `(`(`a', `vara')', `(`b', `varb')', `(`c', `varc')')', `_cat(`_case', x)')dnl ⇒ a) ⇒ vara=" a";; ⇒ b) ⇒ varb=" b";; ⇒ c) ⇒ varc=" c";; esac ⇒esac |
The implementation of the foreach
macro is a bit more involved;
it is a wrapper around two helper macros. First, _arg1
is
needed to grab the first element of a list. Second,
_foreach
implements the recursion, successively walking
through the original list. Here is a simple implementation of
foreach
:
$ m4 -I examples undivert(`foreach.m4')dnl ⇒divert(`-1') ⇒# foreach(x, (item_1, item_2, ..., item_n), stmt) ⇒# parenthesized list, simple version ⇒define(`foreach', `pushdef(`$1')_foreach($@)popdef(`$1')') ⇒define(`_arg1', `$1') ⇒define(`_foreach', `ifelse(`$2', `()', `', ⇒ `define(`$1', _arg1$2)$3`'$0(`$1', (shift$2), `$3')')') ⇒divert`'dnl |
Unfortunately, that implementation is not robust to macro names as list
elements. Each iteration of _foreach
is stripping another
layer of quotes, leading to erratic results if list elements are not
already fully expanded. The first cut at implementing foreachq
takes this into account. Also, when using quoted elements in a
paren-list, the overall list must be quoted. A quote-list
has the nice property of requiring fewer characters to create a list
containing the same quoted elements. To see the difference between the
two macros, we attempt to pass double-quoted macro names in a list,
expecting the macro name on output after one layer of quotes is removed
during list iteration and the final layer removed during the final
rescan:
$ m4 -I examples define(`a', `1')define(`b', `2')define(`c', `3') ⇒ include(`foreach.m4') ⇒ include(`foreachq.m4') ⇒ foreach(`x', `(``a'', ``(b'', ``c)'')', `x ') ⇒1 ⇒(2)1 ⇒ ⇒, x ⇒) foreachq(`x', ```a'', ``(b'', ``c)''', `x ')dnl ⇒a ⇒(b ⇒c) |
Obviously, foreachq
did a better job; here is its implementation:
$ m4 -I examples undivert(`foreachq.m4')dnl ⇒include(`quote.m4')dnl ⇒divert(`-1') ⇒# foreachq(x, `item_1, item_2, ..., item_n', stmt) ⇒# quoted list, simple version ⇒define(`foreachq', `pushdef(`$1')_foreachq($@)popdef(`$1')') ⇒define(`_arg1', `$1') ⇒define(`_foreachq', `ifelse(quote($2), `', `', ⇒ `define(`$1', `_arg1($2)')$3`'$0(`$1', `shift($2)', `$3')')') ⇒divert`'dnl |
Notice that _foreachq
had to use the helper macro
quote
defined earlier (see section Recursion in m4
), to ensure that the
embedded ifelse
call does not go haywire if a list element
contains a comma. Unfortunately, this implementation of foreachq
has its own severe flaw. Whereas the foreach
implementation was
linear, this macro is quadratic in the number of list elements, and is
much more likely to trip up the limit set by the command line option
‘--nesting-limit’ (or ‘-L’, see section Invoking m4). Additionally, this implementation does not expand
‘defn(`iterator')’ very well, when compared with
foreach
.
$ m4 -I examples include(`foreach.m4')include(`foreachq.m4') ⇒ foreach(`name', `(`a', `b')', ` defn(`name')') ⇒ a b foreachq(`name', ``a', `b'', ` defn(`name')') ⇒ _arg1(`a', `b') _arg1(shift(`a', `b')) |
It is possible to have robust iteration with linear behavior and sane iterator contents for either list style. See if you can learn from the best elements of both of these implementations to create robust macros (or see section Answers).
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Thanks to pushdef
, manipulation of a stack is an intrinsic
operation in m4
. Normally, only the topmost definition in a
stack is important, but sometimes, it is desirable to manipulate the
entire definition stack.
For each of the pushdef
definitions associated with macro,
invoke the macro action with a single argument of that definition.
stack_foreach
visits the oldest definition first, while
stack_foreach_lifo
visits the current definition first.
action should not modify or dereference macro. There are a
few special macros, such as defn
, which cannot be used as the
macro parameter.
A sample implementation of these macros is distributed in the file ‘m4-1.4.16/examples/stack.m4’.
$ m4 -I examples include(`stack.m4') ⇒ pushdef(`a', `1')pushdef(`a', `2')pushdef(`a', `3') ⇒ define(`show', ``$1' ') ⇒ stack_foreach(`a', `show')dnl ⇒1 ⇒2 ⇒3 stack_foreach_lifo(`a', `show')dnl ⇒3 ⇒2 ⇒1 |
Now for the implementation. Note the definition of a helper macro,
_stack_reverse
, which destructively swaps the contents of one
stack of definitions into the reverse order in the temporary macro
‘tmp-$1’. By calling the helper twice, the original order is
restored back into the macro ‘$1’; since the operation is
destructive, this explains why ‘$1’ must not be modified or
dereferenced during the traversal. The caller can then inject
additional code to pass the definition currently being visited to
‘$2’. The choice of helper names is intentional; since ‘-’ is
not valid as part of a macro name, there is no risk of conflict with a
valid macro name, and the code is guaranteed to use defn
where
necessary. Finally, note that any macro used in the traversal of a
pushdef
stack, such as pushdef
or defn
, cannot be
handled by stack_foreach
, since the macro would temporarily be
undefined during the algorithm.
$ m4 -I examples undivert(`stack.m4')dnl ⇒divert(`-1') ⇒# stack_foreach(macro, action) ⇒# Invoke ACTION with a single argument of each definition ⇒# from the definition stack of MACRO, starting with the oldest. ⇒define(`stack_foreach', ⇒`_stack_reverse(`$1', `tmp-$1')'dnl ⇒`_stack_reverse(`tmp-$1', `$1', `$2(defn(`$1'))')') ⇒# stack_foreach_lifo(macro, action) ⇒# Invoke ACTION with a single argument of each definition ⇒# from the definition stack of MACRO, starting with the newest. ⇒define(`stack_foreach_lifo', ⇒`_stack_reverse(`$1', `tmp-$1', `$2(defn(`$1'))')'dnl ⇒`_stack_reverse(`tmp-$1', `$1')') ⇒define(`_stack_reverse', ⇒`ifdef(`$1', `pushdef(`$2', defn(`$1'))$3`'popdef(`$1')$0($@)')') ⇒divert`'dnl |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Since m4 is a macro language, it is possible to write macros that can build other macros. First on the list is a way to automate the creation of blind macros.
Defines name as a blind macro, such that name will expand to
value only when given explicit arguments. value should not
be the result of defn
(see section Renaming macros). This macro is only
recognized with parameters, and results in an empty string.
Defining a macro to define another macro can be a bit tricky. We want
to use a literal ‘$#’ in the argument to the nested define
.
However, if ‘$’ and ‘#’ are adjacent in the definition of
define_blind
, then it would be expanded as the number of
arguments to define_blind
rather than the intended number of
arguments to name. The solution is to pass the difficult
characters through extra arguments to a helper macro
_define_blind
. When composing macros, it is a common idiom to
need a helper macro to concatenate text that forms parameters in the
composed macro, rather than interpreting the text as a parameter of the
composing macro.
As for the limitation against using defn
, there are two reasons.
If a macro was previously defined with define_blind
, then it can
safely be renamed to a new blind macro using plain define
; using
define_blind
to rename it just adds another layer of
ifelse
, occupying memory and slowing down execution. And if a
macro is a builtin, then it would result in an attempt to define a macro
consisting of both text and a builtin token; this is not supported, and
the builtin token is flattened to an empty string.
With that explanation, here’s the definition, and some sample usage.
Notice that define_blind
is itself a blind macro.
$ m4 -d define(`define_blind', `ifelse(`$#', `0', ``$0'', `_$0(`$1', `$2', `$'`#', `$'`0')')') ⇒ define(`_define_blind', `define(`$1', `ifelse(`$3', `0', ``$4'', `$2')')') ⇒ define_blind ⇒define_blind define_blind(`foo', `arguments were $*') ⇒ foo ⇒foo foo(`bar') ⇒arguments were bar define(`blah', defn(`foo')) ⇒ blah ⇒blah blah(`a', `b') ⇒arguments were a,b defn(`blah') ⇒ifelse(`$#', `0', ``$0'', `arguments were $*') |
Another interesting composition tactic is argument currying, or factoring a macro that takes multiple arguments for use in a context that provides exactly one argument.
Expand to a macro call that takes exactly one argument, then appends that argument to the original arguments and invokes macro with the resulting list of arguments.
A demonstration of currying makes the intent of this macro a little more
obvious. The macro stack_foreach
mentioned earlier is an example
of a context that provides exactly one argument to a macro name. But
coupled with currying, we can invoke reverse
with two arguments
for each definition of a macro stack. This example uses the file
‘m4-1.4.16/examples/curry.m4’ included in the
distribution.
$ m4 -I examples include(`curry.m4')include(`stack.m4') ⇒ define(`reverse', `ifelse(`$#', `0', , `$#', `1', ``$1'', `reverse(shift($@)), `$1'')') ⇒ pushdef(`a', `1')pushdef(`a', `2')pushdef(`a', `3') ⇒ stack_foreach(`a', `:curry(`reverse', `4')') ⇒:1, 4:2, 4:3, 4 curry(`curry', `reverse', `1')(`2')(`3') ⇒3, 2, 1 |
Now for the implementation. Notice how curry
leaves off with a
macro name but no open parenthesis, while still in the middle of
collecting arguments for ‘$1’. The macro _curry
is the
helper macro that takes one argument, then adds it to the list and
finally supplies the closing parenthesis. The use of a comma inside the
shift
call allows currying to also work for a macro that takes
one argument, although it often makes more sense to invoke that macro
directly rather than going through curry
.
$ m4 -I examples undivert(`curry.m4')dnl ⇒divert(`-1') ⇒# curry(macro, args) ⇒# Expand to a macro call that takes one argument, then invoke ⇒# macro(args, extra). ⇒define(`curry', `$1(shift($@,)_$0') ⇒define(`_curry', ``$1')') ⇒divert`'dnl |
Unfortunately, with M4 1.4.x, curry
is unable to handle builtin
tokens, which are silently flattened to the empty string when passed
through another text macro. This limitation will be lifted in a future
release of M4.
Putting the last few concepts together, it is possible to copy or rename an entire stack of macro definitions.
Ensure that dest is undefined, then define it to the same stack of
definitions currently in source. copy
leaves source
unchanged, while rename
undefines source. There are only a
few macros, such as copy
or defn
, which cannot be copied
via this macro.
The implementation is relatively straightforward (although since it uses
curry
, it is unable to copy builtin macros, such as the second
definition of a
as a synonym for divnum
. See if you can
design a version that works around this limitation, or see section Answers).
$ m4 -I examples include(`curry.m4')include(`stack.m4') ⇒ define(`rename', `copy($@)undefine(`$1')')dnl define(`copy', `ifdef(`$2', `errprint(`$2 already defined ')m4exit(`1')', `stack_foreach(`$1', `curry(`pushdef', `$2')')')')dnl pushdef(`a', `1')pushdef(`a', defn(`divnum'))pushdef(`a', `2') ⇒ copy(`a', `b') ⇒ rename(`b', `c') ⇒ a b c ⇒2 b 2 popdef(`a', `c')c a ⇒ 0 popdef(`a', `c')a c ⇒1 1 |
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by root on March 13, 2013 using texi2html 1.82.