-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
In recently fixing the autoconf implementation of m4_wrap to guarantee
FIFO order, I discovered that the example distributed in the m4 manual had
a bug when wrapping text containing $1. This fixes the example to exactly
match m4 1.4.10 LIFO behavior (so that once I switch to FIFO behavior, as
required by POSIX, in the next stage of the argv_ref branch, the manual
will give an accurate way to restore prior behavior for those who need
it). Part of the fix meant documenting how to join an arbitrary number of
arguments with a space between each; useful enough that I added a
subsection on this common use of the shift builtin.
- --
Don't work too hard, make some time for fun as well!
Eric Blake [EMAIL PROTECTED]
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.8 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
iEYEARECAAYFAkfcjfIACgkQ84KuGfSFAYBXXgCgv9NbnE5FMMwl5TkN2Xh/7aGe
lmoAoINzzWStB57avEwOPzEs7QwQxacV
=Quqf
-----END PGP SIGNATURE-----
>From 4cc7916e4dd2c221f37aa7eec159b48d15273157 Mon Sep 17 00:00:00 2001
From: Eric Blake <[EMAIL PROTECTED]>
Date: Sat, 15 Mar 2008 15:12:47 -0600
Subject: [PATCH] Document join, in order to fix bug in m4wrap example.
* examples/join.m4: New file.
* examples/wraplifo2.m4: Likewise.
* examples/Makefile.am (EXTRA_DIST): Add new files.
* doc/m4.texinfo (Improved m4wrap): New node.
(Defn, Location): Enhance tests.
(Shift): Document the composit macro join.
(Incompatibilities): Move documentation of LIFO vs. FIFO...
(M4wrap): ...here, to match improved example.
Signed-off-by: Eric Blake <[EMAIL PROTECTED]>
---
ChangeLog | 12 ++
doc/m4.texinfo | 301 +++++++++++++++++++++++++++++++++++++++++++-----
examples/Makefile.am | 6 +-
examples/join.m4 | 15 +++
examples/wraplifo2.m4 | 9 ++
5 files changed, 309 insertions(+), 34 deletions(-)
create mode 100644 examples/join.m4
create mode 100644 examples/wraplifo2.m4
diff --git a/ChangeLog b/ChangeLog
index bce309d..599d00f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2008-03-15 Eric Blake <[EMAIL PROTECTED]>
+
+ Document join, in order to fix bug in m4wrap example.
+ * examples/join.m4: New file.
+ * examples/wraplifo2.m4: Likewise.
+ * examples/Makefile.am (EXTRA_DIST): Add new files.
+ * doc/m4.texinfo (Improved m4wrap): New node.
+ (Defn, Location): Enhance tests.
+ (Shift): Document the composit macro join.
+ (Incompatibilities): Move documentation of LIFO vs. FIFO...
+ (M4wrap): ...here, to match improved example.
+
2008-03-14 Eric Blake <[EMAIL PROTECTED]>
Stage 19: allow builtin tokens in more macros.
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 7ac9867..f0fbb96 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -269,6 +269,7 @@ Correct version of some examples
* Improved exch:: Solution for @code{exch}
* Improved forloop:: Solution for @code{forloop}
* Improved foreach:: Solution for @code{foreach}
+* Improved m4wrap:: Solution for @code{m4wrap}
* Improved cleardivert:: Solution for @code{cleardivert}
* Improved capitalize:: Solution for @code{capitalize}
* Improved fatal_error:: Solution for @code{fatal_error}
@@ -2284,25 +2285,40 @@ builtin token is preserved only when it occurs in
isolation. A future
version of @acronym{GNU} M4 may lift these restrictions.
@example
+$ @kbd{m4 -d}
define(`a', `A')define(`AA', `b')
@result{}
+traceon(`defn', `define')
[EMAIL PROTECTED]
defn(`a', `divnum', `a')
[EMAIL PROTECTED]:stdin:2: Warning: defn: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]:stdin:3: Warning: defn: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]: -1- defn(`a', `divnum', `a') -> ``A'`A''
@result{}AA
define(`mydivnum', defn(`divnum', `divnum'))mydivnum
[EMAIL PROTECTED]:stdin:3: Warning: defn: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]:stdin:3: Warning: defn: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]:stdin:4: Warning: defn: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]:stdin:4: Warning: defn: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]: -2- defn(`divnum', `divnum')
[EMAIL PROTECTED]: -1- define(`mydivnum', `')
[EMAIL PROTECTED]
+traceoff(`defn', `define')
@result{}
define(`mydivnum', defn(`divnum')defn(`divnum'))mydivnum
[EMAIL PROTECTED]:stdin:4: Warning: define: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]:stdin:4: Warning: define: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]:stdin:6: Warning: define: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]:stdin:6: Warning: define: cannot concatenate builtin `divnum'
@result{}
define(`mydivnum', defn(`divnum')`a')mydivnum
[EMAIL PROTECTED]:stdin:5: Warning: define: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]:stdin:7: Warning: define: cannot concatenate builtin `divnum'
@result{}A
define(`mydivnum', `a'defn(`divnum'))mydivnum
[EMAIL PROTECTED]:stdin:6: Warning: define: cannot concatenate builtin `divnum'
[EMAIL PROTECTED]:stdin:8: Warning: define: cannot concatenate builtin `divnum'
@result{}A
+define(`q', ``$@@'')
[EMAIL PROTECTED]
+define(`foo', q(`a', defn(`divnum')))foo
[EMAIL PROTECTED]:stdin:10: Warning: define: cannot quote builtin
[EMAIL PROTECTED],
+ifdef(`foo', `yes', `no')
[EMAIL PROTECTED]
@end example
@node Pushdef
@@ -2931,6 +2947,8 @@ shift(`foo', `bar', `baz')
An example of the use of @code{shift} is this macro:
[EMAIL PROTECTED] reversing arguments
[EMAIL PROTECTED] arguments, reversing
@deffn Composite reverse (@dots{})
Takes any number of arguments, and reverses their order.
@end deffn
@@ -3008,6 +3026,113 @@ example2(`feeling rather indecisive today')
@result{}default answer: 4
@end example
[EMAIL PROTECTED] joining arguments
[EMAIL PROTECTED] arguments, joining
[EMAIL PROTECTED] concatenating arguments
+Another common task that requires iteration is joining a list of
+arguments into a single string.
+
[EMAIL PROTECTED] Composite join (@ovar{separator}, @[EMAIL PROTECTED])
[EMAIL PROTECTED] Composite joinall (@ovar{separator}, @[EMAIL PROTECTED])
+Generate a single-quoted string, consisting of each @var{arg} separated
+by @var{separator}. While @code{joinall} always outputs a
[EMAIL PROTECTED] between arguments, @code{join} avoids the
[EMAIL PROTECTED] for an empty @var{arg}.
[EMAIL PROTECTED] deffn
+
+Here are some examples of its usage, based on the implementation
[EMAIL PROTECTED]@value{VERSION}/@/examples/@/join.m4} distributed in this
+package:
+
[EMAIL PROTECTED] examples
[EMAIL PROTECTED]
+$ @kbd{m4 -I examples}
+include(`join.m4')
[EMAIL PROTECTED]
+join,join(`-'),join(`-', `'),join(`-', `', `')
[EMAIL PROTECTED],,,
+joinall,joinall(`-'),joinall(`-', `'),joinall(`-', `', `')
[EMAIL PROTECTED],,,-
+join(`-', `1')
[EMAIL PROTECTED]
+join(`-', `1', `2', `3')
[EMAIL PROTECTED]
+join(`', `1', `2', `3')
[EMAIL PROTECTED]
+join(`-', `', `1', `', `', `2', `')
[EMAIL PROTECTED]
+joinall(`-', `', `1', `', `', `2', `')
[EMAIL PROTECTED]
+join(`,', `1', `2', `3')
[EMAIL PROTECTED],2,3
+define(`nargs', `$#')dnl
+nargs(join(`,', `1', `2', `3'))
[EMAIL PROTECTED]
[EMAIL PROTECTED] example
+
+Examining the implementation shows some interesting points about several
+m4 programming idioms.
+
[EMAIL PROTECTED] examples
[EMAIL PROTECTED]
+$ @kbd{m4 -I examples}
+undivert(`join.m4')dnl
[EMAIL PROTECTED](`-1')
[EMAIL PROTECTED] join(sep, args) - join each non-empty ARG into a single
[EMAIL PROTECTED] string, with each element separated by SEP
[EMAIL PROTECTED](`join',
[EMAIL PROTECTED](`$#', `2', ``$2'',
[EMAIL PROTECTED] `ifelse(`$2', `', `', ``$2'_')$0(`$1', shift(shift($@@)))')')
[EMAIL PROTECTED](`_join',
[EMAIL PROTECTED](`$#$2', `2', `',
[EMAIL PROTECTED] `ifelse(`$2', `', `', ``$1$2'')$0(`$1',
shift(shift($@@)))')')
[EMAIL PROTECTED] joinall(sep, args) - join each ARG, including empty ones,
[EMAIL PROTECTED] into a single string, with each element separated by SEP
[EMAIL PROTECTED](`joinall', ``$2'_$0(`$1', shift($@@))')
[EMAIL PROTECTED](`_joinall',
[EMAIL PROTECTED](`$#', `2', `', ``$1$3'$0(`$1', shift(shift($@@)))')')
[EMAIL PROTECTED]'dnl
[EMAIL PROTECTED] example
+
+First, notice that this implementation creates helper macros
[EMAIL PROTECTED] and @code{_joinall}. This division of labor makes it
+easier to output the correct number of @var{separator} instances:
[EMAIL PROTECTED] and @code{joinall} are responsible for the first argument,
+without a separator, while @code{_join} and @code{_joinall} are
+responsible for all remaining arguments, always outputting a separator
+when outputting an argument.
+
+Next, observe how @code{join} decides to iterate to itself, because the
+first @var{arg} was empty, or to output the argument and swap over to
[EMAIL PROTECTED] If the argument is non-empty, then the nested
[EMAIL PROTECTED] results in an unquoted @samp{_}, which is concatenated
+with the @samp{$0} to form the next macro name to invoke. The
[EMAIL PROTECTED] implementation is simpler since it does not have to
+suppress empty @var{arg}; it always executes once then defers to
[EMAIL PROTECTED]
+
+Another important idiom is the idea that @var{separator} is reused for
+each iteration. Each iteration has one less argument, but rather than
+discarding @samp{$1} by iterating with @code{$0(shift($@@))}, the macro
+discards @samp{$2} by using @code{$0(`$1', shift(shift($@@)))}.
+
+Next, notice that it is possible to compare more than one condition in a
+single @code{ifelse} test. The test of @samp{$#$2} against @samp{2}
+allows @code{_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 @var{args} (i.e., zero or one macro argument). On
+the first pass when there are too few arguments, the @code{shift}
+results in no output, but leaves an empty string to serve as the
+required second argument for the second pass. Put another way,
[EMAIL PROTECTED]', shift($@@)} is not the same as @samp{$@@}, since only the
+former guarantees at least two arguments.
+
[EMAIL PROTECTED] quote manipulation
[EMAIL PROTECTED] manipulating quotes
Sometimes, a recursive algorithm requires adding quotes to each element,
or treating multiple arguments as a single element:
@@ -3074,6 +3199,9 @@ undivert(`quote.m4')dnl
@result{}divert`'dnl
@end example
+It is worth pointing out that @samp{quote(@var{args})} is more efficient
+than @samp{joinall(`,', @var{args})} for producing the same output.
+
@cindex nine arguments, more than
@cindex more than nine arguments
@cindex arguments, more than nine
@@ -4484,6 +4612,64 @@ in which they were saved (LIFO---last in, first out).
However, this
behavior is likely to change in a future release, to match
@acronym{POSIX}, so you should not depend on this order.
+It is possible to emulate @acronym{POSIX} behavior even
+with older versions of @acronym{GNU} M4 by including the file
[EMAIL PROTECTED]@value{VERSION}/@/examples/@/wrapfifo.m4} from the
+distribution:
+
[EMAIL PROTECTED] examples
[EMAIL PROTECTED]
+$ @kbd{m4 -I examples}
+undivert(`wrapfifo.m4')dnl
[EMAIL PROTECTED] Redefine m4wrap to have FIFO semantics.
[EMAIL PROTECTED](`_m4wrap_level', `0')dnl
[EMAIL PROTECTED](`m4wrap',
[EMAIL PROTECTED](`m4wrap'_m4wrap_level,
[EMAIL PROTECTED] `define(`m4wrap'_m4wrap_level,
[EMAIL PROTECTED] defn(`m4wrap'_m4wrap_level)`$1')',
[EMAIL PROTECTED] `builtin(`m4wrap', `define(`_m4wrap_level',
[EMAIL PROTECTED] incr(_m4wrap_level))dnl
[EMAIL PROTECTED]'_m4wrap_level)dnl
[EMAIL PROTECTED](`m4wrap'_m4wrap_level, `$1')')')dnl
+include(`wrapfifo.m4')
[EMAIL PROTECTED]
+m4wrap(`a`'m4wrap(`c
+', `d')')m4wrap(`b')
[EMAIL PROTECTED]
+^D
[EMAIL PROTECTED]
[EMAIL PROTECTED] example
+
+It is likewise possible to emulate LIFO behavior without resorting to
+the @acronym{GNU} M4 extension of @code{builtin}, by including the file
[EMAIL PROTECTED]@value{VERSION}/@/examples/@/wraplifo.m4} from the
+distribution. (Unfortunately, both examples shown here share some
+subtle bugs. See if you can find and correct them; or @pxref{Improved
+m4wrap, , Answers}).
+
[EMAIL PROTECTED] examples
[EMAIL PROTECTED]
+$ @kbd{m4 -I examples}
+undivert(`wraplifo.m4')dnl
[EMAIL PROTECTED] Redefine m4wrap to have LIFO semantics.
[EMAIL PROTECTED](`_m4wrap_level', `0')dnl
[EMAIL PROTECTED](`_m4wrap', defn(`m4wrap'))dnl
[EMAIL PROTECTED](`m4wrap',
[EMAIL PROTECTED](`m4wrap'_m4wrap_level,
[EMAIL PROTECTED] `define(`m4wrap'_m4wrap_level,
[EMAIL PROTECTED] `$1'defn(`m4wrap'_m4wrap_level))',
[EMAIL PROTECTED] `_m4wrap(`define(`_m4wrap_level',
incr(_m4wrap_level))dnl
[EMAIL PROTECTED]'_m4wrap_level)dnl
[EMAIL PROTECTED](`m4wrap'_m4wrap_level, `$1')')')dnl
+include(`wraplifo.m4')
[EMAIL PROTECTED]
+m4wrap(`a`'m4wrap(`c
+', `d')')m4wrap(`b')
[EMAIL PROTECTED]
+^D
[EMAIL PROTECTED]
[EMAIL PROTECTED] example
+
Here is an example of implementing a factorial function using
@code{m4wrap}:
@@ -6423,7 +6609,11 @@ __line__
@result{}8
__line__
@result{}11
+m4wrap(`__line__
+')
[EMAIL PROTECTED]
^D
[EMAIL PROTECTED]
@result{}6
@result{}6
@end example
@@ -6873,31 +7063,6 @@ argument to @code{m4wrap} is saved for later evaluation,
but
@acronym{GNU} @code{m4} saves and processes all arguments, with output
separated by spaces.
-However, it is possible to emulate @acronym{POSIX} behavior by
-including the file @[EMAIL PROTECTED]/@/examples/@/wrapfifo.m4}
-from the distribution:
-
[EMAIL PROTECTED]
-undivert(`wrapfifo.m4')dnl
[EMAIL PROTECTED] Redefine m4wrap to have FIFO semantics.
[EMAIL PROTECTED](`_m4wrap_level', `0')dnl
[EMAIL PROTECTED](`m4wrap',
[EMAIL PROTECTED](`m4wrap'_m4wrap_level,
[EMAIL PROTECTED] `define(`m4wrap'_m4wrap_level,
[EMAIL PROTECTED] defn(`m4wrap'_m4wrap_level)`$1')',
[EMAIL PROTECTED] `builtin(`m4wrap', `define(`_m4wrap_level',
[EMAIL PROTECTED] incr(_m4wrap_level))dnl
[EMAIL PROTECTED]'_m4wrap_level)dnl
[EMAIL PROTECTED](`m4wrap'_m4wrap_level, `$1')')')dnl
-include(`wrapfifo.m4')
[EMAIL PROTECTED]
-m4wrap(`a`'m4wrap(`c
-', `d')')m4wrap(`b')
[EMAIL PROTECTED]
-^D
[EMAIL PROTECTED]
[EMAIL PROTECTED] example
-
@item
@acronym{POSIX} states that builtins that require arguments, but are
called without arguments, have undefined behavior. Traditional
@@ -7104,6 +7269,7 @@ presented here.
* Improved exch:: Solution for @code{exch}
* Improved forloop:: Solution for @code{forloop}
* Improved foreach:: Solution for @code{foreach}
+* Improved m4wrap:: Solution for @code{m4wrap}
* Improved cleardivert:: Solution for @code{cleardivert}
* Improved capitalize:: Solution for @code{capitalize}
* Improved fatal_error:: Solution for @code{fatal_error}
@@ -7557,6 +7723,77 @@ include(`loop.m4')dnl
@end ignore
[EMAIL PROTECTED] Improved m4wrap
[EMAIL PROTECTED] Solution for @code{m4wrap}
+
+The replacement @code{m4wrap} versions presented above, designed to
+guarantee FIFO or LIFO order regardless of the underlying M4
+implementation, share a bug when dealing with wrapped text that looks
+like parameter expansion. Note how the invocation of
[EMAIL PROTECTED]@var{n}} interprets these parameters, while using the
+builtin preserves them for their intended use.
+
[EMAIL PROTECTED] examples
[EMAIL PROTECTED]
+$ @kbd{m4 -I examples}
+include(`wraplifo.m4')
[EMAIL PROTECTED]
+m4wrap(`define(`foo', ``$0:'-$1-$*-$#-')foo(`a', `b')
+')
[EMAIL PROTECTED]
+builtin(`m4wrap', ``'define(`bar', ``$0:'-$1-$*-$#-')bar(`a', `b')
+')
[EMAIL PROTECTED]
+^D
[EMAIL PROTECTED]:-a-a,b-2-
[EMAIL PROTECTED]:---0-
[EMAIL PROTECTED] example
+
+Additionally, the computation of @code{_m4wrap_level} and creation of
+multiple @[EMAIL PROTECTED] placeholders in the original examples is
+more expensive in time and memory than strictly necessary. Notice how
+the improved version grabs the wrapped text via @code{defn} to avoid
+parameter expansion, then undefines @code{_m4wrap_text}, before
+stripping a level of quotes with @code{_arg1} to expand the text. That
+way, each level of wrapping reuses the single placeholder, which starts
+each nesting level in an undefined state.
+
+Finally, it is worth emulating the @acronym{GNU} M4 extension of saving
+all arguments to @code{m4wrap}, separated by a space, rather than saving
+just the first argument. This is done with the @code{join} macro
+documented previously (@pxref{Shift}). The improved LIFO example is
+shipped as @[EMAIL PROTECTED]/@/examples/@/wraplifo2.m4}, and can
+easily be converted to a FIFO solution by swapping the adjacent
+invocations of @code{joinall} and @code{defn}.
+
[EMAIL PROTECTED] examples
[EMAIL PROTECTED]
+$ @kbd{m4 -I examples}
+include(`wraplifo2.m4')
[EMAIL PROTECTED]
+undivert(`wraplifo2.m4')dnl
[EMAIL PROTECTED] Redefine m4wrap to have LIFO semantics, improved example.
[EMAIL PROTECTED](`join.m4')dnl
[EMAIL PROTECTED](`_m4wrap', defn(`m4wrap'))dnl
[EMAIL PROTECTED](`_arg1', `$1')dnl
[EMAIL PROTECTED](`m4wrap',
[EMAIL PROTECTED](`_$0_text',
[EMAIL PROTECTED] `define(`_$0_text', joinall(` ', $@@)defn(`_$0_text'))',
[EMAIL PROTECTED] `_$0(`_arg1(defn(`_$0_text')undefine(`_$0_text'))')dnl
[EMAIL PROTECTED](`_$0_text', joinall(` ', $@@))')')dnl
+m4wrap(`define(`foo', ``$0:'-$1-$*-$#-')foo(`a', `b')
+')
[EMAIL PROTECTED]
+m4wrap(`lifo text
+m4wrap(`nested', `', `$@@
+')')
[EMAIL PROTECTED]
+^D
[EMAIL PROTECTED] text
[EMAIL PROTECTED]:-a-a,b-2-
[EMAIL PROTECTED] $@@
[EMAIL PROTECTED] example
+
@node Improved cleardivert
@section Solution for @code{cleardivert}
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 3450eac..254d2ab 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -1,6 +1,6 @@
## Makefile.am - template for generating Makefile via Automake
##
-## Copyright (C) 2006, 2007 Free Software Foundation, Inc.
+## Copyright (C) 2006, 2007, 2008 Free Software Foundation, Inc.
##
## This file is part of GNU M4.
##
@@ -42,6 +42,7 @@ incl-test.m4 \
incl.m4 \
include.m4 \
indir.m4 \
+join.m4 \
loop.m4 \
misc.m4 \
multiquotes.m4 \
@@ -62,4 +63,5 @@ undivert.incl \
undivert.m4 \
wrap.m4 \
wrapfifo.m4 \
-wraplifo.m4
+wraplifo.m4 \
+wraplifo2.m4
diff --git a/examples/join.m4 b/examples/join.m4
new file mode 100644
index 0000000..8687ac7
--- /dev/null
+++ b/examples/join.m4
@@ -0,0 +1,15 @@
+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
diff --git a/examples/wraplifo2.m4 b/examples/wraplifo2.m4
new file mode 100644
index 0000000..5b450a7
--- /dev/null
+++ b/examples/wraplifo2.m4
@@ -0,0 +1,9 @@
+dnl Redefine m4wrap to have LIFO semantics, improved example.
+include(`join.m4')dnl
+define(`_m4wrap', defn(`m4wrap'))dnl
+define(`_arg1', `$1')dnl
+define(`m4wrap',
+`ifdef(`_$0_text',
+ `define(`_$0_text', joinall(` ', $@)defn(`_$0_text'))',
+ `_$0(`_arg1(defn(`_$0_text')undefine(`_$0_text'))')dnl
+define(`_$0_text', joinall(` ', $@))')')dnl
--
1.5.4
_______________________________________________
M4-patches mailing list
[email protected]
http://lists.gnu.org/mailman/listinfo/m4-patches