home *** CD-ROM | disk | FTP | other *** search
- \def\9#1{}
- @* An example of {\tt WEB}. This example presents the ``word count''
- program from UNIX. It was rewritten in \.{WEB} to demonstrate
- literate programming in C. The level of detail is high for didactic
- purposes; many of the things spelled out here don't have to be
- explained in other programs.
-
- @ Most \.{CWEB} programs share a common structure. It's probably a
- good idea to have one module explicitly stating this common structure,
- even though if the functions don't need to be introduced in any
- special order they can all be unnamed modules.
-
- @c
- @<Global |#include|s@>@/
- @<Global variables@>@/
- @<All functions@>@/
- @<Main@>
-
- @ We must include the standard I/O definitions to use formatted output
- to |stdout| and |stderr|. The file \.{stdio.h} includes a typedef for
- the identifier |FILE|, which is not, strictly speaking, part of C.
- It turns out \.{WEAVE} knows that |FILE| is a reserved word (after all,
- |FILE| is almost as common as |int|);
- but if you're using other types like {\bf caddr\_t},
- @:caddr_t}{\bf caddr_t@>
- which is defined in \.{/usr/include/sys/types.h}, you should let
- \.{WEAVE} know that this is a type, either by including the \.{.h} file
- at \.{WEB} time (saying \.{@@i /usr/include/sys/types.h}), or by
- using \.{WEB}'s format command (saying \.{@@f caddr\_t int}). Either of
- these will make {\bf caddr\_t} be treated in the same way as |int|.
-
- @<Global |#in...@>=
- #include <stdio.h>
-
- @ Now we come to the general layout of the |main| function. The
- |status| variable communicates to 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
- @d usage_error 1
- @d cannot_open_file 2
-
- @<Global variables@>=
- int status=OK; /* exit status of command */
- char *prog_name;
-
- @ @<Main@>=
- main (argc,argv)
- char **argv;
- {
- @<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 counts that he wants. Each selection is given by the initial character
- (lines, words or characters). We do not process this string now. It is
- sufficient just to suppress unwanted figures at output time.
-
- @<Var...@>=
- int filect; /* how many files there are */
- char *which; /* which counts to print */
-
- @ @<Set up o...@>=
- which="lwc"; /* if no option is given, print all three values */
- if (argc>1 && *argv[1] == '-') { which=++argv[1]; argc--; argv++; }
- filect=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 the |do| \dots\ |while| loop because if no file name is given
- we should read from the standard input.
-
- @<Process...@>=
- argc--;
- do {
- @<If a file is given try to open it@>;@/
- @<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, so that's what we
- initialize our file descriptor to.
-
- @<Variabl...@>=
- int fd=0; /* file descriptor, initialized to |stdin| */
-
- @ @<If a fi...@>=
- if (filect>0 && (fd=open(*(++argv),0))<0) {
- fprintf (stderr, "%s: cannot open file %s\n", prog_name, *argv);
- @.cannot open file@>
- status+=cannot_open_file;
- continue;
- }
-
- @ @<Close file@>=
- close(fd);
-
- @ Since buffered I/O speeds things up very much we use it, but we do
- the buffering ourselves. 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 */
- register char *ptr, *buf_end; /* pointers into |buffer| */
- register int c; /* character read or |EOF| */
- int in_word; /* are we within a word? */
- long wordct, linect, charct; /* number of words, lines and chars in a file */
- long twordct, tlinect, tcharct; /* total number of words, lines and chars */
-
- @ @<Init...@>=
- ptr=buf_end=buffer; linect=wordct=charct=0; in_word=0;
-
- @ @<Scan...@>=
- while (1) {
- @<Fill |buffer| if it is empty@>;
- c=*ptr++;
- if (c>' ' && c<0177) { /* visible ASCII codes */
- if (!in_word) {wordct++; in_word++;}
- continue;
- }
- if (c=='\n') linect++;
- else if (c!=' ' && c!='\t') continue;
- in_word=0;
- }
-
- @ Using buffered I/O makes it very easy 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;
- charct+=c; buf_end=buffer+c;
- }
-
- @ Output of the statistics is done by means of a function. This makes
- it easy to use it for the totals, too. Additionally we must decide
- here if we know the name of the file we have processed of if it was
- just |stdin|.
-
- @<Write...@>=
- wc_print(which, charct, wordct, linect);
- if (filect) printf (" %s\n", *argv);
- else printf ("\n");
-
- @ @<Upda...@>=
- tlinect+=linect;
- twordct+=wordct;
- tcharct+=charct;
-
- @ @<Print the...@>=
- if (filect>1) {
- wc_print(which, tcharct, twordct, tlinect);
- printf(" total\n");
- }
-
- @ Here now is the function that prints the values according to the
- specified options. If an invalid option character is found we inform
- the reader about the usage of the command.
-
- @d prt_value(n) printf("%7ld",n)
-
- @<All f...@>=
- wc_print(which, charct, wordct, linect)
- char *which; /* which counts to print */
- long charct, wordct, linect; /* number of words, lines and chars */
- {
- static int usage_error_flag=0;
- while (*which)
- switch (*which++) {
- case 'l': prt_value(linect); break;
- case 'w': prt_value(wordct); break;
- case 'c': prt_value(charct); break;
- default: if (usage_error_flag++==0) {
- fprintf (stderr, "usage: %s [-clw] [name ...]\n", prog_name);
- @.usage: ...@>
- status+=usage_error;
- }
- continue;
- }
- }
-