2007-10-16 Eric Blake <ebb9@byu.net>
+ Fix m4_map, and add some more utility macros.
+ * lib/m4sugar/m4sugar.m4 (m4_apply, m4_count, m4_dquote_elt)
+ (m4_echo, m4_make_list): New documented macros.
+ (_m4_quote, _m4_shift2): New helper macros.
+ (m4_map): Change semantics to allow calling macro without
+ arguments.
+ (m4_map_sep): Likewise. Also change semantics to quote separator,
+ to match m4_join and m4_append.
+ (m4_version_unletter): Fix use of m4_map.
+ * doc/autoconf.texi (Evaluation Macros): Document m4_apply,
+ m4_count, m4_dquote_elt, m4_echo, m4_make_list.
+ (Text processing Macros): Mention m4_dquote as a faster
+ alternative to joining with commas.
+ (Looping constructs): Document m4_map, m4_map_sep.
+ * NEWS: Mention new macros.
+
A few more m4sugar improvements, to benefit libtool.
* lib/m4sugar/m4sugar.m4 (m4_bpatsubsts, _m4_shiftn): Reduce size
of expansion by avoiding extra uses of $@.
should be analyzed to make sure they will still work with the
new documented behavior.
- m4_cmp m4_list_cmp m4_join m4_sign m4_text_box m4_text_wrap
- m4_version_compare
+ m4_cmp m4_list_cmp m4_join m4_map m4_map_sep m4_sign
+ m4_text_box m4_text_wrap m4_version_compare
- Packages using the undocumented m4sugar macro m4_PACKAGE_VERSION
should consider using the new AC_AUTOCONF_VERSION instead.
be used to take action depending on whether anything was appended.
** The following m4sugar macros are new:
- m4_cond m4_expand m4_ignore m4_max m4_min m4_newline
- m4_shift2 m4_shift3 m4_unquote
+ m4_apply m4_cond m4_count m4_dquote_elt m4_echo m4_expand
+ m4_ignore m4_make_list m4_max m4_min m4_newline m4_shift2
+ m4_shift3 m4_unquote
** Warnings are now generated by default when an installer invokes
'configure' with an unknown --enable-* or --with-* option.
only one element. Generally, when using quoted lists of quoted
elements, @code{m4_cdr} should be called without any extra quotes.
-For example, this is an implementation of @code{m4_map}; note how each
-iteration of the helper macro @code{_m4_map} checks for the end of
-recursion, then merely applies the first argument to the first element
-of the list, then recurses with the rest of the list.
+For example, this is a simple implementation of @code{m4_map}; note how
+each iteration checks for the end of recursion, then merely applies the
+first argument to the first element of the list, then recurses with the
+rest of the list. (The actual implementation in M4sugar is a bit more
+involved, to gain some speed and share code with @code{m4_map_sep}).
@example
-m4_define([m4_map], [m4_if([$2], [[]], [], [_$0($@@)])])dnl
-m4_define([_m4_map], [m4_ifval([$2],
- [$1(m4_unquote(m4_car($2)))[]$0([$1], m4_cdr($2))])])dnl
-m4_map([ m4_eval], [[1],[1+1]])
-@result{} 1 2
+m4_define([m4_map], [m4_ifval([$2],
+ [m4_apply([$1], m4_car($2))[]$0([$1], m4_cdr($2))])])dnl
+m4_map([ m4_eval], [[[1]], [[1+1]], [[10],[16]]])
+@result{} 1 2 a
@end example
@end defmac
@code{m4_foreach_w}.
@end defmac
-@c TODO document m4_map, m4_map_sep
+@defmac m4_map (@var{macro}, @var{list})
+@defmacx m4_map_sep (@var{macro}, @var{separator}, @var{list})
+Loop over the comma separated quoted list of argument descriptions in
+@var{list}, and invoke @var{macro} with the arguments. An argument
+description is in turn a comma-separated quoted list of quoted elements,
+suitable for @code{m4_apply}, making it possible to invoke @var{macro}
+without arguments if an argument description is empty.
+@code{m4_map_sep} additionally outputs @var{separator} between macro
+invocations, with no additional expansion of the separator.
+@example
+m4_map([m4_count], [])
+@result{}
+m4_map([ m4_count], [[],
+ [[1]],
+ [[1], [2]]])
+@result{} 0 1 2
+m4_map_sep([m4_eval], [,], [[[1+2]],
+ [[10], [16]]])
+@result{}3,a
+@end example
+@end defmac
@defmac m4_shiftn (@var{count}, @dots{})
@defmacx m4_shift2 (@dots{})
The following macros give some control over the order of the evaluation
by adding or removing levels of quotes.
+@defmac m4_apply (@var{macro}, @var{list})
+@msindex apply
+Apply the elements of the quoted, comma-separated @var{list} as the
+arguments to @var{macro}. If @var{list} is empty, invoke @var{macro}
+without arguments.
+@example
+m4_apply([m4_count], [])
+@result{}0
+m4_apply([m4_count], [[]])
+@result{}1
+m4_apply([m4_count], [[1], [2]])
+@result{}2
+m4_apply([m4_join], [[|], [1], [2]])
+@result{}1|2
+@end example
+@end defmac
+
+@defmac m4_count (@var{arg1}, @dots{})
+@msindex{count}
+This macro returns the decimal count of the number of arguments it was
+passed.
+@end defmac
+
@defmac m4_do (@var{arg1}, @dots{})
@msindex{do}
This macro loops over its arguments and expands each @var{arg} in
level of quoting.
@end defmac
+@defmac m4_dquote_elt (@var{arg1}, @dots{})
+@msindex{dquote_elt}
+Return the arguments as a series of double-quoted arguments. Whereas
+@code{m4_dquote} returns a single argument, @code{m4_dquote_elt} returns
+as many arguments as it was passed.
+@end defmac
+
+@defmac m4_echo (@var{arg1}, @dots{})
+@msindex{echo}
+Return the arguments, with the same level of quoting. Other than
+discarding whitespace after unquoted commas, this macro is a no-op.
+@end defmac
+
@defmac m4_expand (@var{arg})
@msindex{expand}
Return the expansion of @var{arg} as a quoted string. Whereas
serve the same purpose, although it is less readable.
@end defmac
+@defmac m4_make_list (@var{arg1}, @dots{})
+@msindex{make_list}
+This macro exists to aid debugging of M4sugar algorithms. Its net
+effect is similar to @code{m4_dquote}---it produces a quoted list of
+quoted arguments, for each @var{arg}. The difference is that this
+version uses a comma-newline separator instead of just comma, to improve
+readability of the list; with the result that it is less efficient than
+@code{m4_dquote}.
+@example
+m4_define([zero],[0])m4_define([one],[1])m4_define([two],[2])dnl
+m4_dquote(zero, [one], [[two]])
+@result{}[0],[one],[[two]]
+m4_make_list(zero, [one], [[two]])
+@result{}[0],
+@result{}[one],
+@result{}[[two]]
+m4_foreach([number], m4_dquote(zero, [one], [[two]]), [ number])
+@result{} 0 1 two
+m4_foreach([number], m4_make_list(zero, [one], [[two]]), [ number])
+@result{} 0 1 two
+@end example
+@end defmac
+
@c m4_noquote is too dangerous to document - it invokes macros that
@c probably rely on @samp{[]} nested quoting for proper operation. The
@c user should generally prefer m4_unquote instead.
m4_join([|], [one], [], [active], [two])
@result{}one|active|two
@end example
+
+Note that if all you intend to do is join @var{args} with commas between
+them, to form a quoted list suitable for @code{m4_foreach}, it is more
+efficient to use @code{m4_dquote}.
@end defmac
@defmac m4_newline
# m4_map(MACRO, LIST)
# -------------------
# Invoke MACRO($1), MACRO($2) etc. where $1, $2... are the elements
-# of LIST (which can be lists themselves, for multiple arguments MACROs).
+# of LIST. $1, $2... must in turn be lists, appropriate for m4_apply.
+#
+# Since LIST may be quite large, we want to minimize how often it appears
+# in the expansion. Rather than use m4_car/m4_cdr iteration, we unbox the
+# list, and use _m4_shift2 to detect the end of recursion.
m4_define([m4_map],
-[m4_if([$2], [[]], [],
- [_m4_map([$1], [$2])])])
+[m4_if([$2], [], [],
+ [_$0([$1], $2)])])
m4_define([_m4_map],
[m4_if([$#], [1], [],
- [$1(m4_unquote(m4_car($2)))[]_m4_map([$1]_m4_cdr($2))])])
+ [m4_apply([$1], [$2])$0([$1]_m4_shift2($@))])])
# m4_map_sep(MACRO, SEPARATOR, LIST)
# ----------------------------------
# Invoke MACRO($1), SEPARATOR, MACRO($2), ..., MACRO($N) where $1, $2... $N
-# are the elements of LIST (which can be lists themselves, for multiple
-# arguments MACROs).
+# are the elements of LIST, and are in turn lists appropriate for m4_apply.
+# SEPARATOR is not further expanded.
m4_define([m4_map_sep],
-[m4_if([$3], [[]], [],
- [$1(m4_unquote(m4_car($3)))[]_m4_map([$2[]$1]_m4_cdr($3))])])
+[m4_if([$3], [], [],
+ [m4_apply([$1], m4_car($3))m4_map([[$2]$1]_m4_cdr($3))])])
## ---------------------------------------- ##
m4_define([m4_shift2], [m4_shift(m4_shift($@))])
m4_define([m4_shift3], [m4_shift(m4_shift(m4_shift($@)))])
+# _m4_shift2(...)
# _m4_shift3(...)
# ---------------
-# Like m4_shift3, except include a leading comma unless there were exactly
-# three arguments. Why? Because in recursion, it is nice to distinguish
-# between 1 element left and 0 elements left, based on how many arguments
-# this shift expands to.
+# Like m4_shift2 or m4_shift3, except include a leading comma unless shifting
+# consumes all arguments. Why? Because in recursion, it is nice to
+# distinguish between 1 element left and 0 elements left, based on how many
+# arguments this shift expands to.
+m4_define([_m4_shift2],
+[m4_if([$#], [2], [],
+ [, m4_shift(m4_shift($@))])])
m4_define([_m4_shift3],
[m4_if([$#], [3], [],
[, m4_shift(m4_shift(m4_shift($@)))])])
## 7. Quoting manipulation. ##
## ------------------------- ##
+
+# m4_apply(MACRO, LIST)
+# ---------------------
+# Invoke MACRO, with arguments provided from the quoted list of
+# comma-separated quoted arguments. If LIST is empty, invoke MACRO
+# without arguments.
+m4_define([m4_apply],
+[m4_if([$2], [], [$1], [$1($2)])[]])
+
+
+# m4_count(ARGS)
+# --------------
+# Return a count of how many ARGS are present.
+m4_define([m4_count], [$#])
+
+
# m4_do(STRING, ...)
# ------------------
# This macro invokes all its arguments (in sequence, of course). It is
m4_define([m4_dquote], [[$@]])
+# m4_dquote_elt(ARGS)
+# -------------------
+# Return ARGS as an unquoted list of double-quoted arguments.
+m4_define([m4_dquote_elt],
+[m4_if([$#], [0], [],
+ [$#], [1], [[[$1]]],
+ [[[$1]],$0(m4_shift($@))])])
+
+
+# m4_echo(ARGS)
+# -------------
+# Return the ARGS, with the same level of quoting. Whitespace after
+# unquoted commas are consumed.
+m4_define([m4_echo], [$@])
+
+
# m4_expand(ARG)
# --------------
# Return the expansion of ARG as a single string. Unlike m4_quote($1), this
m4_define([m4_ignore])
+# m4_make_list(ARGS)
+# ------------------
+# Similar to m4_dquote, this creates a quoted list of quoted ARGS. This
+# version is less efficient than m4_dquote, but separates each argument
+# with a comma and newline, rather than just comma, for readability.
+# When developing an m4sugar algorithm, you could temporarily use
+# m4_pushdef([m4_dquote],m4_defn([m4_make_list]))
+# around your code to make debugging easier.
+m4_define([m4_make_list], [m4_join([,
+], m4_dquote_elt($@))])
+
+
# m4_noquote(STRING)
# ------------------
# Return the result of ignoring all quotes in STRING and invoking the
# m4_quote(ARGS)
# --------------
# Return ARGS as a single argument. Any whitespace after unquoted commas
-# is stripped.
+# is stripped. There is always output, even when there were no arguments.
#
# It is important to realize the difference between `m4_quote(exp)' and
# `[exp]': in the first case you obtain the quoted *result* of the
m4_define([m4_quote], [[$*]])
+# _m4_quote(ARGS)
+# ---------------
+# Like m4_quote, except that when there are no arguments, there is no
+# output. For conditional scenarios (such as passing _m4_quote as the
+# macro name in m4_map), this feature can be used to distinguish between
+# one argument of the empty string vs. no arguments. However, in the
+# normal case with arguments present, this is less efficient than m4_quote.
+m4_define([_m4_quote],
+[m4_if([$#], [0], [], [[$*]])])
+
+
# m4_unquote(ARGS)
# ----------------
# Remove one layer of quotes from each ARG, performing one level of
# m4_join(SEP, ARG1, ARG2...)
# ---------------------------
# Produce ARG1SEPARG2...SEPARGn. Avoid back-to-back SEP when a given ARG
-# is the empty string.
+# is the empty string. No expansion is performed on SEP or ARGs.
#
# Since the number of arguments to join can be arbitrarily long, we
# want to avoid having more than one $@ in the macro definition;
# used by m4_version_compare, but since [0r36:a] is less readable than 10,
# we provide a wrapper for human use.
m4_define([m4_version_unletter],
-[m4_map_sep([m4_eval], [.], _$0([$1]))])
+[m4_map_sep([m4_eval], [.],
+ m4_dquote(m4_dquote_elt(m4_unquote(_$0([$1])))))])
m4_define([_m4_version_unletter],
[m4_translit(m4_bpatsubst([[[$1]]], ]dnl
m4_dquote(m4_dquote(m4_defn([m4_cr_Letters])))[[+],