m4: Shift

 
 6.3 Recursion in '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:
 
  -- Builtin: shift (ARG1, ...)
      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:
 
  -- Composite: reverse (...)
      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.
 
  -- Composite: cond (TEST-1, STRING-1, EQUAL-1, [TEST-2], [STRING-2],
           [EQUAL-2], ..., [NOT-EQUAL])
      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.
 
  -- Composite: join ([SEPARATOR], [ARGS...])
  -- Composite: joinall ([SEPARATOR], [ARGS...])
      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.18/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:
 
  -- Composite: quote (...)
  -- Composite: dquote (...)
  -- Composite: dquote_elt (...)
      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.18/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 (⇒
 Arguments).
 
  -- Composite: argn (N, ...)
      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