[Top][All Lists]
[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);