m4-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: branch-1_4 forloop documentation


From: Eric Blake
Subject: Re: branch-1_4 forloop documentation
Date: Thu, 19 Oct 2006 16:18:58 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

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  <address@hidden>

        * 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:
+
address@hidden ignore
address@hidden
+$ @kbd{cd address@hidden
address@hidden 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.
address@hidden 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
address@hidden, 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.
address@hidden deffn
+
+An actual implementation of these three macros is distributed as
address@hidden@value{VERSION}/@/examples/@/quote.m4} in this package.  First,
+let's examine their usage:
 
-Here is an implementation, along with an example usage.
-
address@hidden FIXME - these macros are worth reusing in other examples;
address@hidden factor them into examples/quote.m4.
address@hidden 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')
address@hidden
 -quote-dquote-dquote_elt-
 @result{}----
+-quote()-dquote()-dquote_elt()-
address@hidden'-`'-
 -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'))-
address@hidden
+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
address@hidden 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.
+
address@hidden examples
address@hidden
+$ @kbd{m4 -I examples}
+undivert(`quote.m4')dnl
address@hidden(`-1')
address@hidden quote(args) - convert args to single-quoted string
address@hidden(`quote', `ifelse(`$#', `0', `', ``$*'')')
address@hidden dquote(args) - convert args to quoted list of quoted strings
address@hidden(`dquote', ``$@@'')
address@hidden dquote_elt(args) - convert args to list of double-quoted strings
address@hidden(`dquote_elt', `ifelse(`$#', `0', `', `$#', `1', ```$1''',
address@hidden                             ```$1'',$0(shift($@@))')')
address@hidden'dnl
address@hidden 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.
address@hidden 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:
 
address@hidden FIXME - include(`forloop.m4')
address@hidden ignore
address@hidden examples
 @example
-forloop(`i', 1, 8, `i ')
address@hidden 2 3 4 5 6 7 8
+$ @kbd{m4 -I examples}
+include(`forloop.m4')
address@hidden
+forloop(`i', `1', `8', `i ')
address@hidden 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:
 
address@hidden ignore
address@hidden examples
 @example
-forloop(`i', 1, 4, `forloop(`j', 1, 8, `(i, j) ')
+$ @kbd{m4 -I examples}
+include(`forloop.m4')
address@hidden
+forloop(`i', `1', `4', `forloop(`j', `1', `8', ` (i, j)')
 ')
address@hidden(1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8)
address@hidden(2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8)
address@hidden(3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8)
address@hidden(4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (4, 6) (4, 7) (4, 8)
address@hidden (1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8)
address@hidden (2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8)
address@hidden (3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8)
address@hidden (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},
address@hidden), and recurses.
-
-Here is the actual implementation of @code{forloop}:
-
address@hidden ignore
address@hidden
-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
address@hidden, @pxref{Incr}), and recurses.
+
+Here is an actual implementation of @code{forloop}, distributed as
address@hidden@value{VERSION}/@/examples/@/forloop.m4} in this package:
+
address@hidden examples
address@hidden
+$ @kbd{m4 -I examples}
+undivert(`forloop.m4')dnl
address@hidden(`-1')
address@hidden forloop(var, from, to, stmt) - simple version
address@hidden(`forloop', `pushdef(`$1', `$2')_forloop($@@)popdef(`$1')')
address@hidden(`_forloop',
address@hidden       `$4`'ifelse($1, `$3', `', `define(`$1', 
incr($1))$0($@@)')')
address@hidden'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
address@hidden less than @var{end}, @var{end} not numeric, or
address@hidden 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
address@hidden@value{VERSION}/@/tests}, and uses the file
+This section uses the file
 @address@hidden/@/examples/@/incl.m4} included in the
address@hidden M4 package.  The file @file{incl.m4} contains the lines:
address@hidden M4 package:
+
 @comment ignore
 @example
-Include file start
-foo
-Include file end
+$ @kbd{cat examples/incl.m4}
address@hidden file start
address@hidden
address@hidden 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:
 
address@hidden options: -I"$abs_top_srcdir"/examples
address@hidden 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}:
 
address@hidden options: -I"$abs_top_srcdir"/examples
address@hidden 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
address@hidden@value{VERSION}/@/tests}, and reuses the file
address@hidden@value{VERSION}/@/examples/@/incl.m4} mentioned earlier
+This example reuses the file @file{incl.m4} mentioned earlier
 (@pxref{Include}):
 
address@hidden options: -I"$abs_top_srcdir"/examples
address@hidden 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
address@hidden called at ../examples/incl.m4:2
address@hidden 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
 
address@hidden Improved forloop
address@hidden 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 @address@hidden/@/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}.
+
address@hidden examples
address@hidden status: 1
address@hidden
+$ @kbd{m4 -I examples}
+undivert(`forloop2.m4')dnl
address@hidden(`-1')
address@hidden forloop(var, from, to, stmt) - improved version:
address@hidden   works even if VAR is not a strict macro name
address@hidden   performs sanity check that FROM is larger than TO
address@hidden   allows complex numerical expressions in TO and FROM
address@hidden(`forloop', `ifelse(eval(`($3) >= ($2)'), `1',
address@hidden  `pushdef(`$1', eval(`$2'))_forloop(`$1',
address@hidden    eval(`$3'), `$4')popdef(`$1')')')
address@hidden(`_forloop',
address@hidden  `$3`'ifelse(indir(`$1'), `$2', `',
address@hidden    `define(`$1', incr(indir(`$1')))$0($@@)')')
address@hidden'dnl
+include(`forloop2.m4')
address@hidden
+forloop(`i', `2', `1', `no iteration occurs')
address@hidden
+forloop(`', `1', `2', ` odd iterator name')
address@hidden odd iterator name odd iterator name
+forloop(`i', `5 + 5', `0xc', ` 0x`'eval(i, `16')')
address@hidden 0xa 0xb 0xc
+forloop(`i', `a', `b', `non-numeric bounds')
address@hidden:stdin:6: eval: bad input: (b) >= (a)
address@hidden
address@hidden 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 ;
 }
 
-/address@hidden / {
+/address@hidden / { # Start a new test group.
   if (seq > 0)
     print "AT_CLEANUP";
 
@@ -44,31 +44,35 @@
   seq = 0;
 }
 
-/address@hidden file: / {
+/address@hidden file: / { # Produce a data file instead of a test.
   file = $3;
 }
 
-/address@hidden options: / {
+/address@hidden options: / { # Pass additional options to m4.
   options = $0;
   gsub ("@comment options:", "", options);
 }
 
-/address@hidden xfail$/ {
+/address@hidden xfail$/ { # Expect the test to fail.
   xfail = 1;
 }
 
-/address@hidden ignore$/ {
+/address@hidden examples$/ { # The test uses files from the examples dir.
+  examples = 1;
+}
+
+/address@hidden ignore$/ { # This is just formatted doc text, not an actual 
test.
   getline;
-  status = xfail = 0;
+  status = xfail = examples = 0;
   options = file = "";
   next;
 }
 
-/address@hidden status: / {
+/address@hidden status: / { # Expected exit status of a test.
   status = $3;
 }
 
-/address@hidden/, /address@hidden example$/ {
+/address@hidden/, /address@hidden 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);







reply via email to

[Prev in Thread] Current Thread [Next in Thread]