m4: Shift

1 
1 6.3 Recursion in 'm4'
1 =====================
1 
1 There is no direct support for loops in 'm4', but macros can be
1 recursive.  There is no limit on the number of recursion levels, other
1 than those enforced by your hardware and operating system.
1 
1    Loops can be programmed using recursion and the conditionals
1 described previously.
1 
1    There is a builtin macro, 'shift', which can, among other things, be
1 used for iterating through the actual arguments to a macro:
1 
1  -- Builtin: shift (ARG1, ...)
1      Takes any number of arguments, and expands to all its arguments
1      except ARG1, separated by commas, with each argument quoted.
1 
1      The macro 'shift' is recognized only with parameters.
1 
1      shift
1      =>shift
1      shift(`bar')
1      =>
1      shift(`foo', `bar', `baz')
1      =>bar,baz
1 
1    An example of the use of 'shift' is this macro:
1 
1  -- Composite: reverse (...)
1      Takes any number of arguments, and reverses their order.
1 
1    It is implemented as:
1 
1      define(`reverse', `ifelse(`$#', `0', , `$#', `1', ``$1'',
1                                `reverse(shift($@)), `$1'')')
1      =>
1      reverse
1      =>
1      reverse(`foo')
1      =>foo
1      reverse(`foo', `bar', `gnats', `and gnus')
1      =>and gnus, gnats, bar, foo
1 
1    While not a very interesting macro, it does show how simple loops can
1 be made with 'shift', 'ifelse' and recursion.  It also shows that
1 'shift' is usually used with '$@'.  Another example of this is an
1 implementation of a short-circuiting conditional operator.
1 
1  -- Composite: cond (TEST-1, STRING-1, EQUAL-1, [TEST-2], [STRING-2],
1           [EQUAL-2], ..., [NOT-EQUAL])
1      Similar to 'ifelse', where an equal comparison between the first
1      two strings results in the third, otherwise the first three
1      arguments are discarded and the process repeats.  The difference is
1      that each TEST-<N> is expanded only when it is encountered.  This
1      means that every third argument to 'cond' is normally given one
1      more level of quoting than the corresponding argument to 'ifelse'.
1 
1    Here is the implementation of 'cond', along with a demonstration of
1 how it can short-circuit the side effects in 'side'.  Notice how all the
1 unquoted side effects happen regardless of how many comparisons are made
1 with 'ifelse', compared with only the relevant effects with 'cond'.
1 
1      define(`cond',
1      `ifelse(`$#', `1', `$1',
1              `ifelse($1, `$2', `$3',
1                      `$0(shift(shift(shift($@))))')')')dnl
1      define(`side', `define(`counter', incr(counter))$1')dnl
1      define(`example1',
1      `define(`counter', `0')dnl
1      ifelse(side(`$1'), `yes', `one comparison: ',
1             side(`$1'), `no', `two comparisons: ',
1             side(`$1'), `maybe', `three comparisons: ',
1             `side(`default answer: ')')counter')dnl
1      define(`example2',
1      `define(`counter', `0')dnl
1      cond(`side(`$1')', `yes', `one comparison: ',
1           `side(`$1')', `no', `two comparisons: ',
1           `side(`$1')', `maybe', `three comparisons: ',
1           `side(`default answer: ')')counter')dnl
1      example1(`yes')
1      =>one comparison: 3
1      example1(`no')
1      =>two comparisons: 3
1      example1(`maybe')
1      =>three comparisons: 3
1      example1(`feeling rather indecisive today')
1      =>default answer: 4
1      example2(`yes')
1      =>one comparison: 1
1      example2(`no')
1      =>two comparisons: 2
1      example2(`maybe')
1      =>three comparisons: 3
1      example2(`feeling rather indecisive today')
1      =>default answer: 4
1 
1    Another common task that requires iteration is joining a list of
1 arguments into a single string.
1 
1  -- Composite: join ([SEPARATOR], [ARGS...])
1  -- Composite: joinall ([SEPARATOR], [ARGS...])
1      Generate a single-quoted string, consisting of each ARG separated
1      by SEPARATOR.  While 'joinall' always outputs a SEPARATOR between
1      arguments, 'join' avoids the SEPARATOR for an empty ARG.
1 
1    Here are some examples of its usage, based on the implementation
1 'm4-1.4.18/examples/join.m4' distributed in this package:
1 
1      $ m4 -I examples
1      include(`join.m4')
1      =>
1      join,join(`-'),join(`-', `'),join(`-', `', `')
1      =>,,,
1      joinall,joinall(`-'),joinall(`-', `'),joinall(`-', `', `')
1      =>,,,-
1      join(`-', `1')
1      =>1
1      join(`-', `1', `2', `3')
1      =>1-2-3
1      join(`', `1', `2', `3')
1      =>123
1      join(`-', `', `1', `', `', `2', `')
1      =>1-2
1      joinall(`-', `', `1', `', `', `2', `')
1      =>-1---2-
1      join(`,', `1', `2', `3')
1      =>1,2,3
1      define(`nargs', `$#')dnl
1      nargs(join(`,', `1', `2', `3'))
1      =>1
1 
1    Examining the implementation shows some interesting points about
1 several m4 programming idioms.
1 
1      $ m4 -I examples
1      undivert(`join.m4')dnl
1      =>divert(`-1')
1      =># join(sep, args) - join each non-empty ARG into a single
1      =># string, with each element separated by SEP
1      =>define(`join',
1      =>`ifelse(`$#', `2', ``$2'',
1      =>  `ifelse(`$2', `', `', ``$2'_')$0(`$1', shift(shift($@)))')')
1      =>define(`_join',
1      =>`ifelse(`$#$2', `2', `',
1      =>  `ifelse(`$2', `', `', ``$1$2'')$0(`$1', shift(shift($@)))')')
1      =># joinall(sep, args) - join each ARG, including empty ones,
1      =># into a single string, with each element separated by SEP
1      =>define(`joinall', ``$2'_$0(`$1', shift($@))')
1      =>define(`_joinall',
1      =>`ifelse(`$#', `2', `', ``$1$3'$0(`$1', shift(shift($@)))')')
1      =>divert`'dnl
1 
1    First, notice that this implementation creates helper macros '_join'
1 and '_joinall'.  This division of labor makes it easier to output the
1 correct number of SEPARATOR instances: 'join' and 'joinall' are
1 responsible for the first argument, without a separator, while '_join'
1 and '_joinall' are responsible for all remaining arguments, always
1 outputting a separator when outputting an argument.
1 
1    Next, observe how 'join' decides to iterate to itself, because the
1 first ARG was empty, or to output the argument and swap over to '_join'.
1 If the argument is non-empty, then the nested 'ifelse' results in an
1 unquoted '_', which is concatenated with the '$0' to form the next macro
1 name to invoke.  The 'joinall' implementation is simpler since it does
1 not have to suppress empty ARG; it always executes once then defers to
1 '_joinall'.
1 
1    Another important idiom is the idea that SEPARATOR is reused for each
1 iteration.  Each iteration has one less argument, but rather than
1 discarding '$1' by iterating with '$0(shift($@))', the macro discards
1 '$2' by using '$0(`$1', shift(shift($@)))'.
1 
1    Next, notice that it is possible to compare more than one condition
1 in a single 'ifelse' test.  The test of '$#$2' against '2' allows
1 '_join' to iterate for two separate reasons--either there are still more
1 than two arguments, or there are exactly two arguments but the last
1 argument is not empty.
1 
1    Finally, notice that these macros require exactly two arguments to
1 terminate recursion, but that they still correctly result in empty
1 output when given no ARGS (i.e., zero or one macro argument).  On the
1 first pass when there are too few arguments, the 'shift' results in no
1 output, but leaves an empty string to serve as the required second
1 argument for the second pass.  Put another way, '`$1', shift($@)' is not
1 the same as '$@', since only the former guarantees at least two
1 arguments.
1 
1    Sometimes, a recursive algorithm requires adding quotes to each
1 element, or treating multiple arguments as a single element:
1 
1  -- Composite: quote (...)
1  -- Composite: dquote (...)
1  -- Composite: dquote_elt (...)
1      Takes any number of arguments, and adds quoting.  With 'quote',
1      only one level of quoting is added, effectively removing whitespace
1      after commas and turning multiple arguments into a single string.
1      With 'dquote', two levels of quoting are added, one around each
1      element, and one around the list.  And with 'dquote_elt', two
1      levels of quoting are added around each element.
1 
1    An actual implementation of these three macros is distributed as
1 'm4-1.4.18/examples/quote.m4' in this package.  First, let's examine
1 their usage:
1 
1      $ m4 -I examples
1      include(`quote.m4')
1      =>
1      -quote-dquote-dquote_elt-
1      =>----
1      -quote()-dquote()-dquote_elt()-
1      =>--`'-`'-
1      -quote(`1')-dquote(`1')-dquote_elt(`1')-
1      =>-1-`1'-`1'-
1      -quote(`1', `2')-dquote(`1', `2')-dquote_elt(`1', `2')-
1      =>-1,2-`1',`2'-`1',`2'-
1      define(`n', `$#')dnl
1      -n(quote(`1', `2'))-n(dquote(`1', `2'))-n(dquote_elt(`1', `2'))-
1      =>-1-1-2-
1      dquote(dquote_elt(`1', `2'))
1      =>``1'',``2''
1      dquote_elt(dquote(`1', `2'))
1      =>``1',`2''
1 
1    The last two lines show that when given two arguments, 'dquote'
1 results in one string, while 'dquote_elt' results in two.  Now, examine
1 the implementation.  Note that 'quote' and 'dquote_elt' make decisions
1 based on their number of arguments, so that when called without
1 arguments, they result in nothing instead of a quoted empty string; this
1 is so that it is possible to distinguish between no arguments and an
1 empty first argument.  'dquote', on the other hand, results in a string
1 no matter what, since it is still possible to tell whether it was
1 invoked without arguments based on the resulting string.
1 
1      $ m4 -I examples
1      undivert(`quote.m4')dnl
1      =>divert(`-1')
1      =># quote(args) - convert args to single-quoted string
1      =>define(`quote', `ifelse(`$#', `0', `', ``$*'')')
1      =># dquote(args) - convert args to quoted list of quoted strings
1      =>define(`dquote', ``$@'')
1      =># dquote_elt(args) - convert args to list of double-quoted strings
1      =>define(`dquote_elt', `ifelse(`$#', `0', `', `$#', `1', ```$1''',
1      =>                             ```$1'',$0(shift($@))')')
1      =>divert`'dnl
1 
1    It is worth pointing out that 'quote(ARGS)' is more efficient than
1 'joinall(`,', ARGS)' for producing the same output.
1 
1    One more useful macro based on 'shift' allows portably selecting an
1 arbitrary argument (usually greater than the ninth argument), without
11 relying on the GNU extension of multi-digit arguments (⇒
 Arguments).
1 
1  -- Composite: argn (N, ...)
1      Expands to argument N out of the remaining arguments.  N must be a
1      positive number.  Usually invoked as 'argn(`N',$@)'.
1 
1    It is implemented as:
1 
1      define(`argn', `ifelse(`$1', 1, ``$2'',
1        `argn(decr(`$1'), shift(shift($@)))')')
1      =>
1      argn(`1', `a')
1      =>a
1      define(`foo', `argn(`11', $@)')
1      =>
1      foo(`a', `b', `c', `d', `e', `f', `g', `h', `i', `j', `k', `l')
1      =>k
1