* lib/m4sugar/m4sugar.m4 (_m4_apply): New macro.
(m4_map): Ignore empty sublists. For a list consisting of only an
empty sublist, this restores 2.61 behavior of being a no-op.
(m4_map_sep): Likewise, and expand separator.
(m4_mapall, m4_mapall_sep): New macros, to regain 2.62 behavior.
(_m4_map): Rewrite, to be common base for all four variants.
* lib/m4sugar/foreach.m4 (_m4_map): Adjust to new prototype.
* tests/m4sugar.at (m4@&t@_map): Add tests.
* doc/autoconf.texi (Looping constructs) <m4_map>: Document new
macros, and mention ramifications of expanded separator.
* NEWS: Mention the change.
Signed-off-by: Eric Blake <ebb9@byu.net>
+2008-08-15 Eric Blake <ebb9@byu.net>
+
+ Fix m4_map regression from 2007-10-16.
+ * lib/m4sugar/m4sugar.m4 (_m4_apply): New macro.
+ (m4_map): Ignore empty sublists. For a list consisting of only an
+ empty sublist, this restores 2.61 behavior of being a no-op.
+ (m4_map_sep): Likewise, and expand separator.
+ (m4_mapall, m4_mapall_sep): New macros, to regain 2.62 behavior.
+ (_m4_map): Rewrite, to be common base for all four variants.
+ * lib/m4sugar/foreach.m4 (_m4_map): Adjust to new prototype.
+ * tests/m4sugar.at (m4@&t@_map): Add tests.
+ * doc/autoconf.texi (Looping constructs) <m4_map>: Document new
+ macros, and mention ramifications of expanded separator.
+ * NEWS: Mention the change.
+
2008-08-14 Eric Blake <ebb9@byu.net>
Implement m4_transform_pair, to speed up AS_IF.
AC_TYPE_SIGNAL
+** The macros m4_map and m4_map_sep now ignore any list elements
+ consisting of just empty quotes, and m4_map_sep now expands its
+ separator. This fixes a regression in 2.62 when these macros were
+ first documented, for the sake of clients expecting the semantics
+ that these macros had prior to that time. The new macros m4_mapall
+ and m4_mapall_sep, along with extra quoting of the separator, can
+ be used to get the semantics that m4_map_sep had in 2.62.
+
** Clients of m4_expand, such as AS_HELP_STRING and AT_SETUP, can now
handle properly quoted but otherwise unbalanced parentheses (for
some macros, this fixes a regression in 2.62).
allowing the output of unbalanced parantheses in more contexts.
** The following m4sugar macros are new:
- m4_joinall m4_reverse m4_set_add m4_set_add_all m4_set_contains
- m4_set_contents m4_set_delete m4_set_difference m4_set_dump
- m4_set_empty m4_set_foreach m4_set_intersection m4_set_list
- m4_set_listc m4_set_remove m4_set_size m4_set_union
+ m4_joinall m4_mapall m4_mapall_sep m4_reverse m4_set_add
+ m4_set_add_all m4_set_contains m4_set_contents m4_set_delete
+ m4_set_difference m4_set_dump m4_set_empty m4_set_foreach
+ m4_set_intersection m4_set_list m4_set_listc m4_set_remove
+ m4_set_size m4_set_union
** The following m4sugar macros now accept multiple arguments, as is the
case with underlying m4:
@end defmac
@defmac m4_map (@var{macro}, @var{list})
+@defmacx m4_mapall (@var{macro}, @var{list})
@defmacx m4_map_sep (@var{macro}, @var{separator}, @var{list})
+@defmacx m4_mapall_sep (@var{macro}, @var{separator}, @var{list})
+@msindex{map}
+@msindex{mapall}
+@msindex{map_sep}
+@msindex{mapall_sep}
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.
+suitable for @code{m4_apply}. The macros @code{m4_map} and
+@code{m4_map_sep} ignore empty argument descriptions, while
+@code{m4_mapall} and @code{m4_mapall_sep} invoke @var{macro} with no
+arguments. The macros @code{m4_map_sep} and @code{m4_mapall_sep}
+additionally expand @var{separator} between invocations of @var{macro}.
+
+Note that @var{separator} is expanded, unlike in @code{m4_join}. When
+separating output with commas, this means that the map result can be
+used as a series of arguments, by using a single-quoted comma as
+@var{separator}, or as a single string, by using a double-quoted comma.
+
@example
m4_map([m4_count], [])
@result{}
m4_map([ m4_count], [[],
[[1]],
[[1], [2]]])
+@result{} 1 2
+m4_mapall([ m4_count], [[],
+ [[1]],
+ [[1], [2]]])
@result{} 0 1 2
m4_map_sep([m4_eval], [,], [[[1+2]],
[[10], [16]]])
@result{}3,a
+m4_map_sep([m4_echo], [,], [[[a]], [[b]]])
+@result{}a,b
+m4_count(m4_map_sep([m4_echo], [,], [[[a]], [[b]]]))
+@result{}2
+m4_map_sep([m4_echo], [[,]], [[[a]], [[b]]])
+@result{}a,b
+m4_count(m4_map_sep([m4_echo], [[,]], [[[a]], [[b]]]))
+@result{}1
@end example
@end defmac
# of LIST. $1, $2... must in turn be lists, appropriate for m4_apply.
#
# m4_map/m4_map_sep only execute once; the speedup comes in fixing
-# _m4_map. m4_foreach to the rescue.
+# _m4_map. The mismatch in () is intentional, since $1 supplies the
+# opening `(' (but it sure looks odd!). Build the temporary _m4_m:
+# $1, [$3])$1, [$4])...$1, [$m])_m4_popdef([_m4_m])
m4_define([_m4_map],
[m4_if([$#], [2], [],
- [m4_foreach([_m4_elt], [m4_shift2($@)],
- [m4_apply([$1], m4_defn([_m4_elt]))])])])
+ [m4_define([_m4_m], m4_pushdef([_m4_m])_m4_for([_m4_m], [3], [$#], [1],
+ [$0_([1], _m4_m)])[_m4_popdef([_m4_m])])_m4_m($@)])])
+
+m4_define([_m4_map_],
+[[$$1, [$$2])]])
# m4_transform(EXPRESSION, ARG...)
# --------------------------------
# ---------------------
# Invoke MACRO, with arguments provided from the quoted list of
# comma-separated quoted arguments. If LIST is empty, invoke MACRO
-# without arguments.
+# without arguments. The expansion will not be concatenated with
+# subsequent text.
m4_define([m4_apply],
[m4_if([$2], [], [$1], [$1($2)])[]])
+# _m4_apply(MACRO, LIST)
+# ----------------------
+# Like m4_apply, except do nothing if LIST is empty.
+m4_define([_m4_apply],
+[m4_if([$2], [], [], [$1($2)[]])])
+
# m4_count(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
+# macro name in m4_mapall), 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_map(MACRO, LIST)
-# -------------------
-# Invoke MACRO($1), MACRO($2) etc. where $1, $2... are the elements
-# 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, ignore the second argument, and use m4_shift2 to detect the end of
-# recursion.
+# m4_mapall(MACRO, LIST)
+# ----------------------
+# Invoke MACRO($1), MACRO($2) etc. where $1, $2... are the elements of
+# LIST. $1, $2... must in turn be lists, appropriate for m4_apply.
+# If LIST contains an empty sublist, m4_map skips the expansion of
+# MACRO, while m4_mapall expands MACRO with no arguments.
+#
+# 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, ignore the second argument, and use m4_shift2 to
+# detect the end of recursion. The mismatch in () is intentional; see
+# _m4_map. For m4_map, an empty list behaves like an empty sublist
+# and gets ignored; for m4_mapall, we must special-case the empty
+# list.
m4_define([m4_map],
+[_m4_map([_m4_apply([$1]], [], $2)])
+
+m4_define([m4_mapall],
[m4_if([$2], [], [],
- [_$0([$1], [], $2)])])
-m4_define([_m4_map],
-[m4_if([$#], [2], [],
- [m4_apply([$1], [$3])$0([$1], m4_shift2($@))])])
+ [_m4_map([m4_apply([$1]], [], $2)])])
# m4_map_sep(MACRO, SEPARATOR, LIST)
-# ----------------------------------
-# Invoke MACRO($1), SEPARATOR, MACRO($2), ..., MACRO($N) where $1, $2... $N
-# are the elements of LIST, and are in turn lists appropriate for m4_apply.
-# SEPARATOR is not further expanded.
+# m4_mapall_sep(MACRO, SEPARATOR, LIST)
+# -------------------------------------
+# Invoke MACRO($1), SEPARATOR, MACRO($2), ..., MACRO($N) where $1,
+# $2... $N are the elements of LIST, and are in turn lists appropriate
+# for m4_apply. SEPARATOR is expanded, in order to allow the creation
+# of a list of arguments by using a single-quoted comma as the
+# separator. For each empty sublist, m4_map_sep skips the expansion
+# of MACRO and SEPARATOR, while m4_mapall_sep expands MACRO with no
+# arguments.
+#
+# For m4_mapall_sep, merely expand the first iteration without the
+# separator, then include separator as part of subsequent recursion.
+# For m4_map_sep, things are trickier - we don't know if the first
+# list element is an empty sublist, so we must define a self-modifying
+# helper macro and use that as the separator instead.
m4_define([m4_map_sep],
+[m4_pushdef([m4_Sep], [m4_define([m4_Sep], _m4_defn([m4_unquote]))])]dnl
+[_m4_map([_m4_apply([m4_Sep([$2])[]$1]], [], $3)m4_popdef([m4_Sep])])
+
+m4_define([m4_mapall_sep],
[m4_if([$3], [], [],
- [m4_apply([$1], m4_car($3))_m4_map([[$2]$1], $3)])])
+ [m4_apply([$1], m4_car($3))_m4_map([m4_apply([$2[]$1]], $3)])])
+# _m4_map(PREFIX, IGNORED, SUBLIST, ...)
+# --------------------------------------
+# Common implementation for all four m4_map variants. The mismatch in
+# the number of () is intentional. PREFIX must supply a form of
+# m4_apply, the open `(', and the MACRO to be applied. Each iteration
+# then appends `,', the current SUBLIST and the closing `)', then
+# recurses to the next SUBLIST. IGNORED is an aid to ending recursion
+# efficiently.
+m4_define([_m4_map],
+[m4_if([$#], [2], [],
+ [$1, [$3])$0([$1], m4_shift2($@))])])
# m4_transform(EXPRESSION, ARG...)
# --------------------------------
AT_CLEANUP
-## --------------- ##
-## m4_map{,_sep}. ##
-## --------------- ##
+## --------------------- ##
+## m4_map{,all}{,_sep}. ##
+## --------------------- ##
AT_SETUP([m4@&t@_map])
-AT_KEYWORDS([m4@&t@_apply])
+AT_KEYWORDS([m4@&t@_apply m4@&t@_map_sep m4@&t@_mapall m4@&t@_mapall_sep])
AT_KEYWORDS([m4@&t@_count])
AT_CHECK_M4SUGAR_TEXT([[dnl
m4_map([ m4_count], [[],
[[1]],
[[1], [2]]])
+m4_mapall([ m4_count], [[],
+ [[1]],
+ [[1], [2]]])
m4_map_sep([m4_eval], [,], [[[1+2]],
[[10], [16]]])
+m4_count(m4_map_sep([m4_echo], [,], [[], [[1]], [[2]]]))
+m4_count(m4_mapall_sep([m4_echo], [,], [[], [[1]], [[2]]]))
+m4_map_sep([m4_eval], [[,]], [[[1+2]],
+ [[10], [16]]])
+m4_count(m4_map_sep([m4_echo], [[,]], [[], [[1]], [[2]]]))
+m4_count(m4_mapall_sep([m4_echo], [[,]], [[], [[1]], [[2]]]))
+m4_map([-], [[]])
+m4_mapall([-], [[]])
+m4_map_sep([-], [:], [[]])
+m4_mapall_sep([-], [:], [[]])
m4_define([a], [m4_if([$#], [0], [oops], [$1], [a], [pass], [oops])])dnl
m4_define([a1], [oops])dnl
m4_define([pass1], [oops])dnl
m4_map([m4_unquote([a])], [m4_dquote([a])])
]],
[[
+ 1 2
0 1 2
3,a
+2
+3
+3,a
+1
+1
+
+-
+
+-
pass1
pass
]], [])