m4: Composition
1
1 6.7 Building macros with macros
1 ===============================
1
1 Since m4 is a macro language, it is possible to write macros that can
1 build other macros. First on the list is a way to automate the creation
1 of blind macros.
1
1 -- Composite: define_blind (NAME, [VALUE])
1 Defines NAME as a blind macro, such that NAME will expand to VALUE
1 only when given explicit arguments. VALUE should not be the result
1 of 'defn' (⇒Defn). This macro is only recognized with
1 parameters, and results in an empty string.
1
1 Defining a macro to define another macro can be a bit tricky. We
1 want to use a literal '$#' in the argument to the nested 'define'.
1 However, if '$' and '#' are adjacent in the definition of
1 'define_blind', then it would be expanded as the number of arguments to
1 'define_blind' rather than the intended number of arguments to NAME.
1 The solution is to pass the difficult characters through extra arguments
1 to a helper macro '_define_blind'. When composing macros, it is a
1 common idiom to need a helper macro to concatenate text that forms
1 parameters in the composed macro, rather than interpreting the text as a
1 parameter of the composing macro.
1
1 As for the limitation against using 'defn', there are two reasons.
1 If a macro was previously defined with 'define_blind', then it can
1 safely be renamed to a new blind macro using plain 'define'; using
1 'define_blind' to rename it just adds another layer of 'ifelse',
1 occupying memory and slowing down execution. And if a macro is a
1 builtin, then it would result in an attempt to define a macro consisting
1 of both text and a builtin token; this is not supported, and the builtin
1 token is flattened to an empty string.
1
1 With that explanation, here's the definition, and some sample usage.
1 Notice that 'define_blind' is itself a blind macro.
1
1 $ m4 -d
1 define(`define_blind', `ifelse(`$#', `0', ``$0'',
1 `_$0(`$1', `$2', `$'`#', `$'`0')')')
1 =>
1 define(`_define_blind', `define(`$1',
1 `ifelse(`$3', `0', ``$4'', `$2')')')
1 =>
1 define_blind
1 =>define_blind
1 define_blind(`foo', `arguments were $*')
1 =>
1 foo
1 =>foo
1 foo(`bar')
1 =>arguments were bar
1 define(`blah', defn(`foo'))
1 =>
1 blah
1 =>blah
1 blah(`a', `b')
1 =>arguments were a,b
1 defn(`blah')
1 =>ifelse(`$#', `0', ``$0'', `arguments were $*')
1
1 Another interesting composition tactic is argument "currying", or
1 factoring a macro that takes multiple arguments for use in a context
1 that provides exactly one argument.
1
1 -- Composite: curry (MACRO, ...)
1 Expand to a macro call that takes exactly one argument, then
1 appends that argument to the original arguments and invokes MACRO
1 with the resulting list of arguments.
1
1 A demonstration of currying makes the intent of this macro a little
1 more obvious. The macro 'stack_foreach' mentioned earlier is an example
1 of a context that provides exactly one argument to a macro name. But
1 coupled with currying, we can invoke 'reverse' with two arguments for
1 each definition of a macro stack. This example uses the file
1 'm4-1.4.18/examples/curry.m4' included in the distribution.
1
1 $ m4 -I examples
1 include(`curry.m4')include(`stack.m4')
1 =>
1 define(`reverse', `ifelse(`$#', `0', , `$#', `1', ``$1'',
1 `reverse(shift($@)), `$1'')')
1 =>
1 pushdef(`a', `1')pushdef(`a', `2')pushdef(`a', `3')
1 =>
1 stack_foreach(`a', `:curry(`reverse', `4')')
1 =>:1, 4:2, 4:3, 4
1 curry(`curry', `reverse', `1')(`2')(`3')
1 =>3, 2, 1
1
1 Now for the implementation. Notice how 'curry' leaves off with a
1 macro name but no open parenthesis, while still in the middle of
1 collecting arguments for '$1'. The macro '_curry' is the helper macro
1 that takes one argument, then adds it to the list and finally supplies
1 the closing parenthesis. The use of a comma inside the 'shift' call
1 allows currying to also work for a macro that takes one argument,
1 although it often makes more sense to invoke that macro directly rather
1 than going through 'curry'.
1
1 $ m4 -I examples
1 undivert(`curry.m4')dnl
1 =>divert(`-1')
1 =># curry(macro, args)
1 =># Expand to a macro call that takes one argument, then invoke
1 =># macro(args, extra).
1 =>define(`curry', `$1(shift($@,)_$0')
1 =>define(`_curry', ``$1')')
1 =>divert`'dnl
1
1 Unfortunately, with M4 1.4.x, 'curry' is unable to handle builtin
1 tokens, which are silently flattened to the empty string when passed
1 through another text macro. This limitation will be lifted in a future
1 release of M4.
1
1 Putting the last few concepts together, it is possible to copy or
1 rename an entire stack of macro definitions.
1
1 -- Composite: copy (SOURCE, DEST)
1 -- Composite: rename (SOURCE, DEST)
1 Ensure that DEST is undefined, then define it to the same stack of
1 definitions currently in SOURCE. 'copy' leaves SOURCE unchanged,
1 while 'rename' undefines SOURCE. There are only a few macros, such
1 as 'copy' or 'defn', which cannot be copied via this macro.
1
1 The implementation is relatively straightforward (although since it
1 uses 'curry', it is unable to copy builtin macros, such as the second
1 definition of 'a' as a synonym for 'divnum'. See if you can design a
11 version that works around this limitation, or ⇒Answers Improved
copy.).
1
1 $ m4 -I examples
1 include(`curry.m4')include(`stack.m4')
1 =>
1 define(`rename', `copy($@)undefine(`$1')')dnl
1 define(`copy', `ifdef(`$2', `errprint(`$2 already defined
1 ')m4exit(`1')',
1 `stack_foreach(`$1', `curry(`pushdef', `$2')')')')dnl
1 pushdef(`a', `1')pushdef(`a', defn(`divnum'))pushdef(`a', `2')
1 =>
1 copy(`a', `b')
1 =>
1 rename(`b', `c')
1 =>
1 a b c
1 =>2 b 2
1 popdef(`a', `c')c a
1 => 0
1 popdef(`a', `c')a c
1 =>1 1
1