Re: more testsuite coverage for autoconf idioms

Eric Blake <[email protected]>
Newsgroups gmane.comp.gnu.m4.patches
Message-ID <[email protected]>
Eric Blake <ebb9 <at> byu.net> writes:

> I have been working away on a series of patches to speed up recursive
> macro handling (with a goal to make $@ handling linear instead of
> quadratic).  That series is not ready yet,

Still not ready to post, although I have made progress, and uncovered a couple 
more corner cases to be aware of.  Included in the patch below.

Also, as part of my patching progress, I have been hammering on a stress-test 
(also included in this patch).  So far, I have been able to shave the peak 
memory usage of 'm4 -Dlimit=1500 loop.m4' from 41 megabytes down to 14, with a 
slight speedup, once I convinced the input engine to handle references to 
previously parsed arguments; but I'm still trying to hammer out why 'm4 -
Dlimit=1500 -Dalt loop.m4' went from 19 megabytes to 109, even though it 
completes in the same amount of time.  And the quadratic scaling factor of tail-
recursive parsing is not gone yet, until I can convince shift to handle a 
reference to a group of arguments, rather than a group of references to 
individual arguments.

> For the branch, the testsuite is contained entirely within the
> m4.texinfo, hence adding some tests within @ignore blocks.  But for head,
> I will move those tests to a more appropriate tests/*.at file.

Some of these changes are in documented tests, but for those that aren't, the 
same rules will apply to my port of this patch to head.

From: Eric Blake <[email protected]>
Date: Wed, 31 Oct 2007 08:50:30 -0600
Subject: [PATCH] Test more corner cases.

* doc/m4.texinfo (Changecom, Pseudo Arguments): Beef up tests.
(Improved foreach): Document alternate foreachq style.
* examples/foreachq3.m4: New file.
* examples/loop.m4: New file.
* examples/Makefile.am (EXTRA_DIST): Distribute them.

Signed-off-by: Eric Blake <[email protected]>
---
 ChangeLog             |    9 +++++
 doc/m4.texinfo        |   85 +++++++++++++++++++++++++++++++++++++++++++-----
 examples/Makefile.am  |    2 +
 examples/foreachq3.m4 |   10 ++++++
 examples/loop.m4      |   16 +++++++++
 5 files changed, 113 insertions(+), 9 deletions(-)
 create mode 100644 examples/foreachq3.m4
 create mode 100644 examples/loop.m4

diff --git a/ChangeLog b/ChangeLog
index 737a6ec..a25c84d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2007-10-31  Eric Blake  <[email protected]>
+
+	Test more corner cases.
+	* doc/m4.texinfo (Changecom, Pseudo Arguments): Beef up tests.
+	(Improved foreach): Document alternate foreachq style.
+	* examples/foreachq3.m4: New file.
+	* examples/loop.m4: New file.
+	* examples/Makefile.am (EXTRA_DIST): Distribute them.
+
 2007-10-28  Eric Blake  <[email protected]>
 
 	More test coverage for autoconf usage patterns.
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index d7c8140..3fc6dfa 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -1942,11 +1942,17 @@ foo)
 @comment levels.
 
 @example
-define(`echo', `$@')dnl
+define(`echo', `$@@')dnl
 len((echo(`01234567890123456789',
           `01234567890123456789')echo(`98765432109876543210',
                                       `98765432109876543210')))
 @result{}84
+define(`argn', `$#')dnl
+define(`echo1', `-$@@-')define(`echo2', `,$@@,')dnl
+echo1(`1', `2', `3') argn(echo1(`1', `2', `3'))
+@result{}-1,2,3- 3
+echo2(`1', `2', `3') argn(echo2(`1', `2', `3'))
+@result{},1,2,3, 5
 @end example
 @end ignore
 
@@ -3801,26 +3807,32 @@ implementations, it is a good idea to avoid @samp{(}, 
@samp{,}, and
 @samp{)} as the first character in @var{start}.
 
 @example
-define(`echo', `$#:$@@:')
+define(`echo', `$#:$*:$@@:')
 @result{}
 define(`hi', `HI')
 @result{}
 changecom(`(',`)')
 @result{}
 echo(hi)
-@result{}0::(hi)
+@result{}0:::(hi)
 changecom
 @result{}
 changecom(`((', `))')
 @result{}
 echo(hi)
-@result{}1:HI:
+@result{}1:HI:HI:
 echo((hi))
-@result{}0::((hi))
+@result{}0:::((hi))
 changecom(`,', `)')
 @result{}
 echo(hi,hi)bye)
-@result{}1:HI,hi)bye:
+@result{}1:HI,hi)bye:HI,hi)bye:
+changecom
+@result{}
+echo(hi,`,`'hi',hi)
+@result{}3:HI,,HI,HI:HI,,`'hi,HI:
+echo(hi,`,`'hi',hi`'changecom(`,,', `hi'))
+@result{}3:HI,,`'hi,HI:HI,,`'hi,HI:
 @end example
 
 It is an error if the end of file occurs within a comment.
@@ -6803,7 +6815,60 @@ Contrast the use of @code{@w{_arg1q}}, which quotes the 
first list
 element, with @code{@w{_arg1}} of the earlier implementation that
 returned the first list element directly.
 
-For a different approach, the improved version of @code{foreach},
+The astute m4 programmer might notice that the solution above still uses
+more memory, and thus more time, than strictly necessary.  Note that
+@samp{$2}, which contains an arbitrarily long quoted list, is expanded
+and rescanned three times per iteration of @code{_foreachq}.
+Furthermore, every iteration of the algorithm effectively unboxes then
+reboxes the list, which costs a couple of macro invocations.
+It is possible to rewrite the algorithm for a bit more speed by swapping
+the order of the arguments to @code{_foreachq} in order to operate on an
+unboxed list in the first place, and by using the fixed-length @samp{$#}
+instead of an arbitrary length list as the key to end recursion.  This
+alternative approach is available as
+@file{m4-@value{VERSION}/@/examples/@/foreach3.m4}:
+
+@example
+$ @kbd{m4 -I examples}
+include(`foreachq3.m4')
+@result{}
+undivert(`foreachq3.m4')dnl
+@result{}divert(`-1')
+@result{}# foreachq(x, `item_1, item_2, ..., item_n', stmt)
+@result{}#   quoted list, alternate improved version
+@result{}define(`foreachq',
+@result{}`pushdef(`$1')_$0(`$1', `$3'ifelse(`$2', `', `',
+@result{}  `, $2'))popdef(`$1')')
+@result{}define(`_foreachq', `ifelse(`$#', `2', `',
+@result{}  `define(`$1', `$3')$2`'$0(`$1', `$2'ifelse(`$#', `3', `',
+@result{}    `, shift(shift(shift($@@)))'))')')
+@result{}divert`'dnl
+traceon(`shift')debugmode(`aq')
+@result{}
+foreachq(`x', ``1', `2', `3', `4'', `x
+')dnl
+@result{}1
+@error{}m4trace: -4- shift(`x', `x
+@error{}', `1', `2', `3', `4')
+@error{}m4trace: -3- shift(`x
+@error{}', `1', `2', `3', `4')
+@error{}m4trace: -2- shift(`1', `2', `3', `4')
+@result{}2
+@error{}m4trace: -4- shift(`x', `x
+@error{}', `2', `3', `4')
+@error{}m4trace: -3- shift(`x
+@error{}', `2', `3', `4')
+@error{}m4trace: -2- shift(`2', `3', `4')
+@result{}3
+@error{}m4trace: -4- shift(`x', `x
+@error{}', `3', `4')
+@error{}m4trace: -3- shift(`x
+@error{}', `3', `4')
+@error{}m4trace: -2- shift(`3', `4')
+@result{}4
+@end example
+
+For yet another approach, the improved version of @code{foreach},
 available in @file{m4-@value{VERSION}/@/examples/@/foreach2.m4}, simply
 overquotes the arguments to @code{@w{_foreach}} to begin with, using
 @code{dquote_elt}.  Then @code{@w{_foreach}} can just use
@@ -6846,9 +6911,11 @@ In summary, recursion over list elements is trickier 
than it appeared at
 first glance, but provides a powerful idiom within @code{m4} processing.
 As a final demonstration, both list styles are now able to handle
 several scenarios that would wreak havoc on the original
-implementations.  This points out one other difference between the two
+implementations.  This points out one other difference between the
 list styles.  @code{foreach} evaluates unquoted list elements only once,
-in preparation for calling @code{@w{_foreach}}.  But @code{foreachq}
+in preparation for calling @code{@w{_foreach}}, similary for
+@code{foreachq} as provided by @file{foreachq3.m4}.  But
+@code{foreachq}, as provided by @file{foreachq2.m4},
 evaluates unquoted list elements twice while visiting the first list
 element, once in @code{@w{_arg1q}} and once in @code{@w{_rest}}.  When
 deciding which list style to use, one must take into account whether
diff --git a/examples/Makefile.am b/examples/Makefile.am
index e7672f9..b1ef68a 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -33,6 +33,7 @@ foreach.m4 \
 foreach2.m4 \
 foreachq.m4 \
 foreachq2.m4 \
+foreachq3.m4 \
 forloop.m4 \
 forloop2.m4 \
 fstab.m4 \
@@ -41,6 +42,7 @@ incl-test.m4 \
 incl.m4 \
 include.m4 \
 indir.m4 \
+loop.m4 \
 misc.m4 \
 multiquotes.m4 \
 patsubst.m4 \
diff --git a/examples/foreachq3.m4 b/examples/foreachq3.m4
new file mode 100644
index 0000000..beab455
--- /dev/null
+++ b/examples/foreachq3.m4
@@ -0,0 +1,10 @@
+divert(`-1')
+# foreachq(x, `item_1, item_2, ..., item_n', stmt)
+#   quoted list, alternate improved version
+define(`foreachq',
+`pushdef(`$1')_$0(`$1', `$3'ifelse(`$2', `', `',
+  `, $2'))popdef(`$1')')
+define(`_foreachq', `ifelse(`$#', `2', `',
+  `define(`$1', `$3')$2`'$0(`$1', `$2'ifelse(`$#', `3', `',
+    `, shift(shift(shift($@)))'))')')
+divert`'dnl
diff --git a/examples/loop.m4 b/examples/loop.m4
new file mode 100644
index 0000000..ee278fd
--- /dev/null
+++ b/examples/loop.m4
@@ -0,0 +1,16 @@
+dnl Stress test for recursion algorithms.  Usage:
+dnl m4 -Ipath/to/examples [-Doptions] loop.m4
+dnl Options include:
+dnl -Dalt - test with foreachq3 instead of foreachq2
+dnl -Dlimit=<num> - set upper limit of sequence to <num>, default 1000
+dnl -Dverbose - print the sequence to the screen, rather than discarding
+dnl -Ddebug[=<code>] - execute <code> after forloop but before foreach
+dnl -Dsleep=<num> - sleep for <num> seconds before exit, to allow time
+dnl   to examine peak process memory usage
+include(`forloop2.m4')dnl
+include(ifdef(`alt', ``foreachq3.m4'', ``foreachq2.m4''))dnl
+ifdef(`limit', `', `define(`limit', `1000')')dnl
+ifdef(`verbose', `', `divert(`-1')')dnl
+ifdef(`debug', `', `define(`debug')')
+foreachq(`i', dquote(1forloop(`i', `2', limit, `,i'))debug, ` i')
+ifdef(`sleep',`syscmd(`echo done>/dev/tty;sleep 'sleep)')dnl
-- 
1.5.3.2
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.