Previous: Stacks, Up: Conditionals [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 Defn). 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.19/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 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
Previous: Stacks, Up: Conditionals [Contents][Index]