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);