Re: branch-1_4 forloop documentation

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

> 
> This patch is against the branch, but I will be applying a similar one to
> head.

Like so:

2006-10-19  Eric Blake  <[email protected]>

	* tests/generate.awk: For ease of doc-writing, simplify selection
	of '-Ipath/to/examples' to '@comment examples'.
	* examples/forloop.m4: Simplify.
	* examples/forloop2.m4: New file.
	* examples/quote.m4: New file.
	* doc/m4.texinfo (Improved forloop): New node.
	(Manual): Clarify use of examples directory.
	(Shift, Forloop): Resync from branch.
	(Include, Location): Update to new usage of examples directory.

Index: doc/m4.texinfo
===================================================================
RCS file: /sources/m4/m4/doc/m4.texinfo,v
retrieving revision 1.66
diff -u -r1.66 m4.texinfo
--- doc/m4.texinfo	16 Oct 2006 13:17:50 -0000	1.66
+++ doc/m4.texinfo	19 Oct 2006 16:15:59 -0000
@@ -282,6 +282,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 cleardivert::        Solution for @code{cleardivert}
 * Improved fatal_error::        Solution for @code{fatal_error}
@@ -463,7 +464,14 @@
 The majority of these examples are self-contained, and you can run them
 with similar results.  In fact, the testsuite that is bundled in the
 @acronym{GNU} M4 package consists in part of the examples
-in this document!
+in this document!  Some of the examples assume that your current
+directory is located where you unpacked the installation, so if you plan
+on following along, you may find it helpful to do this now:
+
+@comment ignore
+@example
+$ @kbd{cd m4-@value{VERSION}}
+@end example
 
 As each of the predefined macros in @code{m4} is described, a prototype
 call of the macro will be shown, giving descriptive names to the
@@ -2478,7 +2486,7 @@
 An example of the use of @code{shift} is this macro:
 
 @deffn Composite reverse (@dots{})
-Takes any number of arguments, and reverse their order.
+Takes any number of arguments, and reverses their order.
 @end deffn
 
 It is implemented as:
@@ -2503,37 +2511,65 @@
 @deffn Composite quote (@dots{})
 @deffnx Composite dquote (@dots{})
 @deffnx Composite dquote_elt (@dots{})
-Takes any number of arguments, and quoting.  With @code{quote}, only one
-level of quoting is added, effectively removing whitespace after commas
-and turning the arguments into a string.  With @code{dquote}, two
-levels of quoting are added, one around each element, and one around
-the list.  And with @code{dquote_elt}, two levels of quoting are added
-around each element.
-@end deffn
+Takes any number of arguments, and adds quoting.  With @code{quote},
+only one level of quoting is added, effectively removing whitespace
+after commas and turning multiple arguments into a single string.  With
+@code{dquote}, two levels of quoting are added, one around each element,
+and one around the list.  And with @code{dquote_elt}, two levels of
+quoting are added around each element.
+@end deffn
+
+An actual implementation of these three macros is distributed as
+@file{m4-@value{VERSION}/@/examples/@/quote.m4} in this package.  First,
+let's examine their usage:
 
-Here is an implementation, along with an example usage.
-
-@comment FIXME - these macros are worth reusing in other examples;
-@comment factor them into examples/quote.m4.
+@comment examples
 @example
-define(`quote', `ifelse(`$#', `0', `', ``$*'')')dnl
-define(`dquote', ``$@@'')dnl
-define(`dquote_elt', `ifelse(`$#', `0', `', `$#', `1', ```$1''',
-                             ```$1'',dquote_elt(shift($@@))')')dnl
+$ @kbd{m4 -I examples}
+include(`quote.m4')
+@result{}
 -quote-dquote-dquote_elt-
 @result{}----
+-quote()-dquote()-dquote_elt()-
+@result{}--`'-`'-
 -quote(`1')-dquote(`1')-dquote_elt(`1')-
 @result{}-1-`1'-`1'-
--quote(`1',`2')-dquote(`1',`2')-dquote_elt(`1',`2')-
+-quote(`1', `2')-dquote(`1', `2')-dquote_elt(`1', `2')-
 @result{}-1,2-`1',`2'-`1',`2'-
-dquote(dquote_elt(`1',`2'))
+define(`n', `$#')dnl
+-n(quote(`1', `2'))-n(dquote(`1', `2'))-n(dquote_elt(`1', `2'))-
+@result{}-1-1-2-
+dquote(dquote_elt(`1', `2'))
 @result{}``1'',``2''
-dquote_elt(dquote(`1',`2'))
+dquote_elt(dquote(`1', `2'))
 @result{}``1',`2''
 @end example
 
 The last two lines show that when given two arguments, @code{dquote}
-results in one string, while @code{dquote_elt} results in two.
+results in one string, while @code{dquote_elt} results in two.  Now,
+examine the implementation.  Note that @code{quote} and
+@code{dquote_elt} make decisions based on their number of arguments, so
+that when called without arguments, they result in nothing instead of a
+quoted empty string; this is so that it is possible to distinquish
+between no arguments and an empty first argument.  @code{dquote}, on the
+other hand, results in a string no matter what, since it is still
+possible to tell whether it was invoked without arguments based on the
+resulting string.
+
+@comment examples
+@example
+$ @kbd{m4 -I examples}
+undivert(`quote.m4')dnl
+@result{}divert(`-1')
+@result{}# quote(args) - convert args to single-quoted string
+@result{}define(`quote', `ifelse(`$#', `0', `', ``$*'')')
+@result{}# dquote(args) - convert args to quoted list of quoted strings
+@result{}define(`dquote', ``$@@'')
+@result{}# dquote_elt(args) - convert args to list of double-quoted strings
+@result{}define(`dquote_elt', `ifelse(`$#', `0', `', `$#', `1', ```$1''',
+@result{}                             ```$1'',$0(shift($@@))')')
+@result{}divert`'dnl
+@end example
 
 @node Forloop
 @section Iteration by counting
@@ -2542,7 +2578,6 @@
 @cindex loops, counting
 @cindex counting loops
 Here is an example of a loop macro that implements a simple for loop.
-@comment FIXME - this section still needs some work done
 
 @deffn Composite forloop (@var{iterator}, @var{start}, @var{end}, @var{text})
 Takes the name in @var{iterator}, which must be a valid macro name, and
@@ -2555,28 +2590,28 @@
 
 It can, for example, be used for simple counting:
 
-@comment FIXME - include(`forloop.m4')
-@comment ignore
+@comment examples
 @example
-forloop(`i', 1, 8, `i ')
-@result{}1 2 3 4 5 6 7 8
+$ @kbd{m4 -I examples}
+include(`forloop.m4')
+@result{}
+forloop(`i', `1', `8', `i ')
+@result{}1 2 3 4 5 6 7 8 @comment
 @end example
 
-The arguments are a name for the iteration variable, the starting value,
-the final value, and the text to be expanded for each iteration.  With
-this macro, the macro @code{i} is defined only within the loop.  After
-the loop, it retains whatever value it might have had before.
-
-For-loops can be nested, like
+For-loops can be nested, like:
 
-@comment ignore
+@comment examples
 @example
-forloop(`i', 1, 4, `forloop(`j', 1, 8, `(i, j) ')
+$ @kbd{m4 -I examples}
+include(`forloop.m4')
+@result{}
+forloop(`i', `1', `4', `forloop(`j', `1', `8', ` (i, j)')
 ')
-@result{}(1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8)
-@result{}(2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8)
-@result{}(3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8)
-@result{}(4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (4, 6) (4, 7) (4, 8)
+@result{} (1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8)
+@result{} (2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8)
+@result{} (3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8)
+@result{} (4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (4, 6) (4, 7) (4, 8)
 @result{}
 @end example
 
@@ -2587,28 +2622,33 @@
 the first argument.
 
 The macro @code{_forloop} expands the fourth argument once, and tests
-to see if it is finished.  If it has not finished, it increments
-the iteration variable (using the predefined macro @code{incr},
-@pxref{Incr}), and recurses.
-
-Here is the actual implementation of @code{forloop}:
-
-@comment ignore
-@example
-define(`forloop', `pushdef(`$1', `$2')_forloop($@@)popdef(`$1')')
-define(`_forloop',
-       `$4`'ifelse($1, `$3', `', `define(`$1', incr($1))$0($@@)')')
+to see if the iterator has reached the final value.  If it has not
+finished, it increments the iterator (using the predefined macro
+@code{incr}, @pxref{Incr}), and recurses.
+
+Here is an actual implementation of @code{forloop}, distributed as
+@file{m4-@value{VERSION}/@/examples/@/forloop.m4} in this package:
+
+@comment examples
+@example
+$ @kbd{m4 -I examples}
+undivert(`forloop.m4')dnl
+@result{}divert(`-1')
+@result{}# forloop(var, from, to, stmt) - simple version
+@result{}define(`forloop', `pushdef(`$1', `$2')_forloop($@@)popdef(`$1')')
+@result{}define(`_forloop',
+@result{}       `$4`'ifelse($1, `$3', `', `define(`$1', incr($1))$0($@@)')')
+@result{}divert`'dnl
 @end example
 
-Notice the careful use of quotes.  Certain macro arguments are
+Notice the careful use of quotes.  Certain macro arguments are left
 unquoted, each for its own reason.  Try to find out @emph{why} these
-arguments are left unquoted, and see what happens if they are
-quoted.
-
-Now, even though these two macros are useful, they are still not robust
-enough for general use. They lack even basic error handling of cases
-like start value less than final value, and the first argument not being
-a name.  Correcting these errors are left as an exercise to the reader.
+arguments are left unquoted, and see what happens if they are quoted.
+(As presented, these two macros are useful but not very robust for
+general use.  They lack even basic error handling for cases like
+@var{start} less than @var{end}, @var{end} not numeric, or
+@var{iterator} not being a macro name.  See if you can improve these
+macros; or @pxref{Improved forloop, , Answers}).
 
 @node Foreach
 @section Iteration by list contents
@@ -3927,24 +3967,25 @@
 @result{}
 @end example
 
-This section assumes that the current directory is
-@file{m4-@value{VERSION}/@/tests}, and uses the file
+This section uses the file
 @file{m4-@value{VERSION}/@/examples/@/incl.m4} included in the
-@acronym{GNU} M4 package.  The file @file{incl.m4} contains the lines:
+@acronym{GNU} M4 package:
+
 @comment ignore
 @example
-Include file start
-foo
-Include file end
+$ @kbd{cat examples/incl.m4}
+@result{}Include file start
+@result{}foo
+@result{}Include file end
 @end example
 
 Normally file inclusion is used to insert the contents of a file
 into the input stream.  The contents of the file will be read by
 @code{m4} and macro calls in the file will be expanded:
 
-@comment options: -I"$abs_top_srcdir"/examples
+@comment examples
 @example
-$ @kbd{m4 -I ../examples}
+$ @kbd{m4 -I examples}
 define(`foo', `FOO')
 @result{}
 include(`incl.m4')
@@ -3959,9 +4000,9 @@
 Here is an example, which defines @samp{bar} to expand to the contents
 of @file{incl.m4}:
 
-@comment options: -I"$abs_top_srcdir"/examples
+@comment examples
 @example
-$ @kbd{m4 -I ../examples}
+$ @kbd{m4 -I examples}
 define(`bar', include(`incl.m4'))
 @result{}
 This is `bar':  >>bar<<
@@ -5227,21 +5268,19 @@
 location macros has no effect on syncline, debug, warning, or error
 message output.
 
-This example assumes that the current directory is
-@file{m4-@value{VERSION}/@/tests}, and reuses the file
-@file{m4-@value{VERSION}/@/examples/@/incl.m4} mentioned earlier
+This example reuses the file @file{incl.m4} mentioned earlier
 (@pxref{Include}):
 
-@comment options: -I"$abs_top_srcdir"/examples
+@comment examples
 @example
-$ @kbd{m4 -I ../examples}
+$ @kbd{m4 -I examples}
 define(`foo', ``$0' called at __file__:__line__')
 @result{}
 foo
 @result{}foo called at stdin:2
 include(`incl.m4')
 @result{}Include file start
-@result{}foo called at ../examples/incl.m4:2
+@result{}foo called at examples/incl.m4:2
 @result{}Include file end
 @result{}
 @end example
@@ -5760,6 +5799,7 @@
 
 @menu
 * Improved exch::               Solution for @code{exch}
+* Improved forloop::            Solution for @code{forloop}
 * Improved foreach::            Solution for @code{foreach}
 * Improved cleardivert::        Solution for @code{cleardivert}
 * Improved fatal_error::        Solution for @code{fatal_error}
@@ -5783,6 +5823,52 @@
 @result{}expansion text
 @end example
 
+@node Improved forloop
+@section Solution for @code{forloop}
+
+The @code{forloop} macro (@pxref{Forloop}) as presented earlier can go
+into an infinite loop if given an iterator that is not parsed as a macro
+name.  It does not do any sanity checking on its numeric bounds, and
+only permits decimal numbers for bounds.  Here is an improved version,
+shipped as @file{m4-@value{VERSION}/@/examples/@/forloop2.m4}; this
+version also optimizes based on the fact that the starting bound does
+not need to be passed to the helper @code{_forloop}.
+
+@comment examples
+@comment status: 1
+@example
+$ @kbd{m4 -I examples}
+undivert(`forloop2.m4')dnl
+@result{}divert(`-1')
+@result{}# forloop(var, from, to, stmt) - improved version:
+@result{}#   works even if VAR is not a strict macro name
+@result{}#   performs sanity check that FROM is larger than TO
+@result{}#   allows complex numerical expressions in TO and FROM
+@result{}define(`forloop', `ifelse(eval(`($3) >= ($2)'), `1',
+@result{}  `pushdef(`$1', eval(`$2'))_forloop(`$1',
+@result{}    eval(`$3'), `$4')popdef(`$1')')')
+@result{}define(`_forloop',
+@result{}  `$3`'ifelse(indir(`$1'), `$2', `',
+@result{}    `define(`$1', incr(indir(`$1')))$0($@@)')')
+@result{}divert`'dnl
+include(`forloop2.m4')
+@result{}
+forloop(`i', `2', `1', `no iteration occurs')
+@result{}
+forloop(`', `1', `2', ` odd iterator name')
+@result{} odd iterator name odd iterator name
+forloop(`i', `5 + 5', `0xc', ` 0x`'eval(i, `16')')
+@result{} 0xa 0xb 0xc
+forloop(`i', `a', `b', `non-numeric bounds')
+@error{}m4:stdin:6: eval: bad input: (b) >= (a)
+@result{}
+@end example
+
+Of course, it is possible to make even more improvements, such as
+adding an optional step argument, or allowing iteration through
+descending sequences.  @acronym{GNU} Autoconf provides some of these
+additional bells and whistles in its @code{m4_for} macro.
+
 @node Improved foreach
 @section Solution for @code{foreach}
 
Index: examples/forloop.m4
===================================================================
RCS file: /sources/m4/m4/examples/forloop.m4,v
retrieving revision 1.1.1.2
diff -u -r1.1.1.2 forloop.m4
--- examples/forloop.m4	17 Feb 2000 03:09:47 -0000	1.1.1.2
+++ examples/forloop.m4	19 Oct 2006 16:15:59 -0000
@@ -1,10 +1,6 @@
-divert(-1)
-# forloop(i, from, to, stmt)
-
-define(`forloop', `pushdef(`$1', `$2')_forloop(`$1', `$2', `$3', `$4')popdef
(`$1')')
+divert(`-1')
+# forloop(var, from, to, stmt) - simple version
+define(`forloop', `pushdef(`$1', `$2')_forloop($@)popdef(`$1')')
 define(`_forloop',
-       `$4`'ifelse($1, `$3', ,
-			 `define(`$1', incr($1))_forloop(`$1', `$2', `$3', 
`$4')')')
-divert
-forloop(`x', 1, 10, `2**x = eval(2**x)
-')
+       `$4`'ifelse($1, `$3', `', `define(`$1', incr($1))$0($@)')')
+divert`'dnl
Index: examples/forloop2.m4
===================================================================
RCS file: examples/forloop2.m4
diff -N examples/forloop2.m4
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ examples/forloop2.m4	19 Oct 2006 16:15:59 -0000
@@ -0,0 +1,12 @@
+divert(`-1')
+# forloop(var, from, to, stmt) - improved version:
+#   works even if VAR is not a strict macro name
+#   performs sanity check that FROM is larger than TO
+#   allows complex numerical expressions in TO and FROM
+define(`forloop', `ifelse(eval(`($3) >= ($2)'), `1',
+  `pushdef(`$1', eval(`$2'))_forloop(`$1',
+    eval(`$3'), `$4')popdef(`$1')')')
+define(`_forloop',
+  `$3`'ifelse(indir(`$1'), `$2', `',
+    `define(`$1', incr(indir(`$1')))$0($@)')')
+divert`'dnl
Index: examples/quote.m4
===================================================================
RCS file: examples/quote.m4
diff -N examples/quote.m4
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ examples/quote.m4	19 Oct 2006 16:15:59 -0000
@@ -0,0 +1,9 @@
+divert(`-1')
+# quote(args) - convert args to single-quoted string
+define(`quote', `ifelse(`$#', `0', `', ``$*'')')
+# dquote(args) - convert args to quoted list of quoted strings
+define(`dquote', ``$@'')
+# dquote_elt(args) - convert args to list of double-quoted strings
+define(`dquote_elt', `ifelse(`$#', `0', `', `$#', `1', ```$1''',
+                             ```$1'',$0(shift($@))')')
+divert`'dnl
Index: tests/generate.awk
===================================================================
RCS file: /sources/m4/m4/tests/generate.awk,v
retrieving revision 1.21
diff -u -r1.21 generate.awk
--- tests/generate.awk	4 Oct 2006 23:30:46 -0000	1.21
+++ tests/generate.awk	19 Oct 2006 16:15:59 -0000
@@ -22,7 +22,7 @@
 
 BEGIN {
   seq = -1;
-  status = xfail = 0;
+  status = xfail = examples = 0;
   file = options = "";
   print "# This file is part of the GNU m4 test suite.  -*- Autotest -*-";
   # I don't know how to get this file's name, so it's hard coded :(
@@ -35,7 +35,7 @@
   print ;
 }
 
-/^@node / {
+/^@node / { # Start a new test group.
   if (seq > 0)
     print "AT_CLEANUP";
 
@@ -44,31 +44,35 @@
   seq = 0;
 }
 
-/^@comment file: / {
+/^@comment file: / { # Produce a data file instead of a test.
   file = $3;
 }
 
-/^@comment options: / {
+/^@comment options: / { # Pass additional options to m4.
   options = $0;
   gsub ("@comment options:", "", options);
 }
 
-/^@comment xfail$/ {
+/^@comment xfail$/ { # Expect the test to fail.
   xfail = 1;
 }
 
-/^@comment ignore$/ {
+/^@comment examples$/ { # The test uses files from the examples dir.
+  examples = 1;
+}
+
+/^@comment ignore$/ { # This is just formatted doc text, not an actual test.
   getline;
-  status = xfail = 0;
+  status = xfail = examples = 0;
   options = file = "";
   next;
 }
 
-/^@comment status: / {
+/^@comment status: / { # Expected exit status of a test.
   status = $3;
 }
 
-/^@example$/, /^@end example$/ {
+/^@example$/, /^@end example$/ { # The body of the test.
   if (seq < 0)
     next;
 
@@ -97,9 +101,9 @@
 	}
       else
 	{
-	  new_test(input, status, output, error, options, xfail);
+	  new_test(input, status, output, error, options, xfail, examples);
 	}
-      status = xfail = 0;
+      status = xfail = examples = 0;
       file = input = output = error = options = "";
       next;
     }
@@ -162,7 +166,7 @@
   printf ("AT_KEYWORDS([[documentation]])\n\n");
 }
 
-function new_test(input, status, output, error, options, xfail) {
+function new_test(input, status, output, error, options, xfail, examples) {
   input = normalize(input);
   output = normalize(output);
   error = normalize(error);
@@ -172,18 +176,18 @@
   if (xfail == 1)
     printf ("AT_XFAIL_IF([:])\n");
 
-  if (options ~ /-I/)
+  if (examples == 1)
     {
       printf ("AT_DATA([expout1],\n[[%s]])\n", output);
-      printf ("sed -e \"s|\\\\.\\\\./examples|"\
-	      "$abs_top_srcdir/examples|g\" \\\n");
+      printf ("sed -e \"s|examples|$abs_top_srcdir/examples|g\" \\\n");
       printf ("  < expout1 > expout\n\n");
+      options = options " -I\"$abs_top_srcdir/examples\"";
     }
 
   printf ("AT_DATA([[input.m4]],\n[[%s]])\n\n", input);
   # Some of these tests `include' files from tests/.
   printf ("AT_CHECK_M4([[%s input.m4]], %s,", options, status);
-  if (options ~ /-I/)
+  if (examples == 1)
     printf ("\n[expout]");
   else if (output)
     printf ("\n[[%s]]", output);
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.