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