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