summaryrefslogtreecommitdiff
path: root/web/c_cpp/cweb/examples/wc.w
diff options
context:
space:
mode:
Diffstat (limited to 'web/c_cpp/cweb/examples/wc.w')
-rw-r--r--web/c_cpp/cweb/examples/wc.w254
1 files changed, 254 insertions, 0 deletions
diff --git a/web/c_cpp/cweb/examples/wc.w b/web/c_cpp/cweb/examples/wc.w
new file mode 100644
index 0000000000..522cfc6573
--- /dev/null
+++ b/web/c_cpp/cweb/examples/wc.w
@@ -0,0 +1,254 @@
+% wc: An example of CWEB by Silvio Levy and Donald E. Knuth
+
+\nocon % omit table of contents
+\datethis % print date on listing
+\def\SPARC{SPARC\-\kern.1em station}
+
+@* An example of {\tt CWEB}. This example, based on a program by
+Klaus Guntermann and Joachim Schrod [{\sl TUGboat\/ \bf7} (1986),
+134--137] presents the ``word count'' program from \UNIX/, rewritten in
+\.{CWEB} to demonstrate literate programming in \CEE/. The level of
+detail in this document is intentionally high, for didactic purposes;
+many of the things spelled out here don't need to be explained in
+other programs.
+
+The purpose of \.{wc} is to count lines, words, and/or characters in a
+list of files. The number of lines in a file is the number of newline
+characters it contains. The number of characters is the file length in bytes.
+A ``word'' is a maximal sequence of consecutive characters other than
+newline, space, or tab, containing at least one visible ASCII code.
+(We assume that the standard ASCII code is in use.)
+
+This version of \.{wc} has a nonstandard ``silent'' option (\.{-s}),
+which suppresses printing except for the grand totals over all files.
+
+@ Most \.{CWEB} programs share a common structure. It's probably a
+good idea to state the overall structure explicitly at the outset,
+even though the various parts could all be introduced in unnamed
+sections of the code if we wanted to add them piecemeal.
+
+Here, then, is an overview of the file \.{wc.c} that is defined
+by this \.{CWEB} program \.{wc.w}:
+
+@c
+@<Header files to include@>@/
+@<Global variables@>@/
+@<Functions@>@/
+@<The main program@>
+
+@ We must include the standard I/O definitions, since we want to send
+formatted output to |stdout| and |stderr|.
+
+@<Header files...@>=
+#include <stdio.h>
+
+@ The |status| variable will tell the operating system if the run was
+successful or not, and |prog_name| is used in case there's an error message to
+be printed.
+
+@d OK 0 /* |status| code for successful run */
+@d usage_error 1 /* |status| code for improper syntax */
+@d cannot_open_file 2 /* |status| code for file access error */
+
+@<Global variables@>=
+int status=OK; /* exit status of command, initially |OK| */
+char *prog_name; /* who we are */
+
+@ Now we come to the general layout of the |main| function.
+
+@<The main...@>=
+main (argc,argv)
+ int argc; /* the number of arguments on the \UNIX/ command line */
+ char **argv; /* the arguments themselves, an array of strings */
+{
+ @<Variables local to |main|@>@;
+ prog_name=argv[0];
+ @<Set up option selection@>;
+ @<Process all the files@>;
+ @<Print the grand totals if there were multiple files @>;
+ exit(status);
+}
+
+@ If the first argument begins with a `\.{-}', the user is choosing
+the desired counts and specifying the order in which they should be
+displayed. Each selection is given by the initial character
+(lines, words, or characters). For example, `\.{-cl}' would cause
+just the number of characters and the number of lines to be printed,
+in that order. The default, if no special argument is given, is `\.{-lwc}'.
+
+We do not process this string now; we simply remember where it is.
+It will be used to control the formatting at output time.
+
+If the `\.{-}' is immediately followed by `\.{s}', only summary totals
+are printed.
+
+@<Var...@>=
+int file_count; /* how many files there are */
+char *which; /* which counts to print */
+int silent=0; /* nonzero if the silent option was selected */
+
+@ @<Set up o...@>=
+which="lwc"; /* if no option is given, print all three values */
+if (argc>1 && *argv[1] == '-') {
+ argv[1]++;
+ if (*argv[1]=='s') silent=1,argv[1]++;
+ if (*argv[1]) which=argv[1];
+ argc--; argv++;
+}
+file_count=argc-1;
+
+@ Now we scan the remaining arguments and try to open a file, if
+possible. The file is processed and its statistics are given.
+We use a |do|~\dots~|while| loop because we should read from the
+standard input if no file name is given.
+
+@<Process...@>=
+argc--;
+do@+{
+ @<If a file is given, try to open |*(++argv)|; |continue| if unsuccessful@>;
+ @<Initialize pointers and counters@>;
+ @<Scan file@>;
+ @<Write statistics for file@>;
+ @<Close file@>;
+ @<Update grand totals@>; /* even if there is only one file */
+}@+while (--argc>0);
+
+@ Here's the code to open the file. A special trick allows us to
+handle input from |stdin| when no name is given.
+Recall that the file descriptor to |stdin| is~0; that's what we
+use as the default initial value.
+
+@<Variabl...@>=
+int fd=0; /* file descriptor, initialized to |stdin| */
+
+@ @d READ_ONLY 0 /* read access code for system |open| routine */
+
+@<If a file...@>=
+if (file_count>0 && (fd=open(*(++argv),READ_ONLY))<0) {
+ fprintf (stderr, "%s: cannot open file %s\n", prog_name, *argv);
+@.cannot open file@>
+ status|=cannot_open_file;
+ file_count--;
+ continue;
+}
+
+@ @<Close file@>=
+close(fd);
+
+@ We will do some homemade buffering in order to speed things up: Characters
+will be read into the |buffer| array before we process them.
+To do this we set up appropriate pointers and counters.
+
+@d buf_size BUFSIZ /* \.{stdio.h}'s |BUFSIZ| is chosen for efficiency*/
+
+@<Var...@>=
+char buffer[buf_size]; /* we read the input into this array */
+register char *ptr; /* the first unprocessed character in |buffer| */
+register char *buf_end; /* the first unused position in |buffer| */
+register int c; /* current character, or number of characters just read */
+int in_word; /* are we within a word? */
+long word_count, line_count, char_count; /* number of words, lines,
+ and characters found in the file so far */
+
+@ @<Init...@>=
+ptr=buf_end=buffer; line_count=word_count=char_count=0; in_word=0;
+
+@ The grand totals must be initialized to zero at the beginning of the
+program. If we made these variables local to |main|, we would have to
+do this initialization explicitly; however, \CEE/'s globals are automatically
+zeroed. (Or rather, ``statically zeroed.'') (Get it?)
+@^Joke@>
+
+@<Global var...@>=
+long tot_word_count, tot_line_count, tot_char_count;
+ /* total number of words, lines, and chars */
+
+@ The present section, which does the counting that is \.{wc}'s {\it raison
+d'\^etre}, was actually one of the simplest to write. We look at each
+character and change state if it begins or ends a word.
+
+@<Scan...@>=
+while (1) {
+ @<Fill |buffer| if it is empty; |break| at end of file@>;
+ c=*ptr++;
+ if (c>' ' && c<0177) { /* visible ASCII codes */
+ if (!in_word) {word_count++; in_word=1;}
+ continue;
+ }
+ if (c=='\n') line_count++;
+ else if (c!=' ' && c!='\t') continue;
+ in_word=0; /* |c| is newline, space, or tab */
+}
+
+@ Buffered I/O allows us to count the number of characters almost for free.
+
+@<Fill |buff...@>=
+if (ptr>=buf_end) {
+ ptr=buffer; c=read(fd,ptr,buf_size);
+ if (c<=0) break;
+ char_count+=c; buf_end=buffer+c;
+}
+
+@ It's convenient to output the statistics by defining a new function
+|wc_print|; then the same function can be used for the totals.
+Additionally we must decide here if we know the name of the file
+we have processed or if it was just |stdin|.
+
+@<Write...@>=
+if (!silent) {
+ wc_print(which, char_count, word_count, line_count);
+ if (file_count) printf (" %s\n", *argv); /* not |stdin| */
+ else printf ("\n"); /* |stdin| */
+}
+
+@ @<Upda...@>=
+tot_line_count+=line_count;
+tot_word_count+=word_count;
+tot_char_count+=char_count;
+
+@ We might as well improve a bit on \UNIX/'s \.{wc} by displaying the
+number of files too.
+
+@<Print the...@>=
+if (file_count>1 || silent) {
+ wc_print(which, tot_char_count, tot_word_count, tot_line_count);
+ if (!file_count) printf("\n");
+ else printf(" total in %d file%s\n",file_count,file_count>1?"s":"");
+}
+
+@ Here now is the function that prints the values according to the
+specified options. The calling routine is supposed to supply a
+newline. If an invalid option character is found we inform
+the user about proper usage of the command. Counts are printed in
+8-digit fields so that they will line up in columns.
+
+@d print_count(n) printf("%8ld",n)
+
+@<Fun...@>=
+wc_print(which, char_count, word_count, line_count)
+char *which; /* which counts to print */
+long char_count, word_count, line_count; /* given totals */
+{
+ while (*which)
+ switch (*which++) {
+ case 'l': print_count(line_count); break;
+ case 'w': print_count(word_count); break;
+ case 'c': print_count(char_count); break;
+ default: if ((status & usage_error)==0) {
+ fprintf (stderr, "\nUsage: %s [-lwc] [filename ...]\n", prog_name);
+@.Usage: ...@>
+ status|=usage_error;
+ }
+ }
+}
+
+@ Incidentally, a test of this program against the system \.{wc}
+command on a \SPARC\ showed that the ``official'' \.{wc} was slightly
+slower. Furthermore, although that \.{wc} gave an appropriate error
+message for the options `\.{-abc}', it made no complaints about the
+options `\.{-labc}'! Dare we suggest that the system routine might have been
+better if its programmer had used a more literate approach?
+
+@* Index.
+Here is a list of the identifiers used, and where they appear. Underlined
+entries indicate the place of definition. Error messages are also shown.