home *** CD-ROM | disk | FTP | other *** search
/ Gold Fish 3 / goldfish_volume_3.bin / files / dev / c / cweb / examples / wc.w < prev    next >
Encoding:
Text File  |  1994-08-22  |  8.8 KB  |  255 lines

  1. % wc: An example of CWEB by Silvio Levy and Donald E. Knuth
  2.  
  3. \nocon % omit table of contents
  4. \datethis % print date on listing
  5. \def\SPARC{SPARC\-\kern.1em station}
  6.  
  7. @* An example of {\tt CWEB}.  This example, based on a program by
  8. Klaus Guntermann and Joachim Schrod [{\sl TUGboat\/ \bf7} (1986),
  9. 135--137] presents the ``word count'' program from \UNIX/, rewritten in
  10. \.{CWEB} to demonstrate literate programming in \CEE/.  The level of
  11. detail in this document is intentionally high, for didactic purposes;
  12. many of the things spelled out here don't need to be explained in
  13. other programs.
  14.  
  15. The purpose of \.{wc} is to count lines, words, and/or characters in a
  16. list of files. The number of lines in a file is the number of newline
  17. characters it contains. The number of characters is the file length in bytes.
  18. A ``word'' is a maximal sequence of consecutive characters other than
  19. newline, space, or tab, containing at least one visible ASCII code.
  20. (We assume that the standard ASCII code is in use.)
  21.  
  22. This version of \.{wc} has a nonstandard ``silent'' option (\.{-s}),
  23. which suppresses printing except for the grand totals over all files.
  24.  
  25. @ Most \.{CWEB} programs share a common structure.  It's probably a
  26. good idea to state the overall structure explicitly at the outset,
  27. even though the various parts could all be introduced in unnamed
  28. sections of the code if we wanted to add them piecemeal.
  29.  
  30. Here, then, is an overview of the file \.{wc.c} that is defined
  31. by this \.{CWEB} program \.{wc.w}:
  32.  
  33. @c
  34. @<Header files to include@>@/
  35. @<Global variables@>@/
  36. @<Functions@>@/
  37. @<The main program@>
  38.  
  39. @ We must include the standard I/O definitions, since we want to send
  40. formatted output to |stdout| and |stderr|.
  41.  
  42. @<Header files...@>=
  43. #include <stdio.h>
  44.  
  45. @  The |status| variable will tell the operating system if the run was
  46. successful or not, and |prog_name| is used in case there's an error message to
  47. be printed.
  48.  
  49. @d OK 0 /* |status| code for successful run */
  50. @d usage_error 1 /* |status| code for improper syntax */
  51. @d cannot_open_file 2 /* |status| code for file access error */
  52.  
  53. @<Global variables@>=
  54. int status=OK; /* exit status of command, initially |OK| */
  55. char *prog_name; /* who we are */
  56.  
  57. @ Now we come to the general layout of the |main| function. 
  58.  
  59. @<The main...@>=
  60. main (argc,argv)
  61.     int argc; /* the number of arguments on the \UNIX/ command line */
  62.     char **argv; /* the arguments themselves, an array of strings */
  63. {
  64.   @<Variables local to |main|@>@;
  65.   prog_name=argv[0];
  66.   @<Set up option selection@>;
  67.   @<Process all the files@>;
  68.   @<Print the grand totals if there were multiple files @>;
  69.   exit(status);
  70. }
  71.  
  72. @ If the first argument begins with a `\.{-}', the user is choosing
  73. the desired counts and specifying the order in which they should be
  74. displayed.  Each selection is given by the initial character
  75. (lines, words, or characters).  For example, `\.{-cl}' would cause
  76. just the number of characters and the number of lines to be printed,
  77. in that order. The default, if no special argument is given, is `\.{-lwc}'.
  78.  
  79. We do not process this string now; we simply remember where it is.
  80. It will be used to control the formatting at output time.
  81.  
  82. If the `\.{-}' is immediately followed by `\.{s}', only summary totals
  83. are printed.
  84.  
  85. @<Var...@>=
  86. int file_count; /* how many files there are */
  87. char *which; /* which counts to print */
  88. int silent=0; /* nonzero if the silent option was selected */
  89.  
  90. @ @<Set up o...@>=
  91. which="lwc"; /* if no option is given, print all three values */
  92. if (argc>1 && *argv[1] == '-') {
  93.   argv[1]++;
  94.   if (*argv[1]=='s') silent=1,argv[1]++;
  95.   if (*argv[1]) which=argv[1];
  96.   argc--; argv++;
  97. }
  98. file_count=argc-1;
  99.  
  100. @ Now we scan the remaining arguments and try to open a file, if
  101. possible.  The file is processed and its statistics are given.
  102. We use a |do|~\dots~|while| loop because we should read from the
  103. standard input if no file name is given.
  104.  
  105. @<Process...@>=
  106. argc--;
  107. do@+{
  108.   @<If a file is given, try to open |*(++argv)|; |continue| if unsuccessful@>;
  109.   @<Initialize pointers and counters@>;
  110.   @<Scan file@>;
  111.   @<Write statistics for file@>;
  112.   @<Close file@>;
  113.   @<Update grand totals@>; /* even if there is only one file */
  114. }@+while (--argc>0);
  115.  
  116. @ Here's the code to open the file.  A special trick allows us to
  117. handle input from |stdin| when no name is given.
  118. Recall that the file descriptor to |stdin| is~0; that's what we
  119. use as the default initial value.
  120.  
  121. @<Variabl...@>=
  122. int fd=0; /* file descriptor, initialized to |stdin| */
  123.  
  124. @ @d READ_ONLY 0 /* read access code for system |open| routine */
  125.  
  126. @<If a file...@>=
  127. if (file_count>0 && (fd=open(*(++argv),READ_ONLY))<0) {
  128.   fprintf (stderr, "%s: cannot open file %s\n", prog_name, *argv);
  129. @.cannot open file@>
  130.   status|=cannot_open_file;
  131.   file_count--;
  132.   continue;
  133. }
  134.  
  135. @ @<Close file@>=
  136. close(fd);
  137.  
  138. @ We will do some homemade buffering in order to speed things up: Characters
  139. will be read into the |buffer| array before we process them.
  140. To do this we set up appropriate pointers and counters.
  141.  
  142. @d buf_size BUFSIZ /* \.{stdio.h}'s |BUFSIZ| is chosen for efficiency*/
  143.  
  144. @<Var...@>=
  145. char buffer[buf_size]; /* we read the input into this array */
  146. register char *ptr; /* the first unprocessed character in |buffer| */
  147. register char *buf_end; /* the first unused position in |buffer| */
  148. register int c; /* current character, or number of characters just read */
  149. int in_word; /* are we within a word? */
  150. long word_count, line_count, char_count; /* number of words, lines, 
  151.     and characters found in the file so far */
  152.  
  153. @ @<Init...@>=
  154. ptr=buf_end=buffer; line_count=word_count=char_count=0; in_word=0;
  155.  
  156. @ The grand totals must be initialized to zero at the beginning of the
  157. program. If we made these variables local to |main|, we would have to
  158. do this initialization explicitly; however, \CEE/'s globals are automatically
  159. zeroed. (Or rather, ``statically zeroed.'') (Get it?)
  160. @^Joke@>
  161.  
  162. @<Global var...@>=
  163. long tot_word_count, tot_line_count, tot_char_count;
  164.  /* total number of words, lines, and chars */
  165.  
  166. @ The present section, which does the counting that is \.{wc}'s {\it raison
  167. d'\^etre}, was actually one of the simplest to write. We look at each
  168. character and change state if it begins or ends a word.
  169.  
  170. @<Scan...@>=
  171. while (1) {
  172.   @<Fill |buffer| if it is empty; |break| at end of file@>;
  173.   c=*ptr++;
  174.   if (c>' ' && c<0177) { /* visible ASCII codes */
  175.     if (!in_word) {word_count++; in_word=1;}
  176.     continue;
  177.   }
  178.   if (c=='\n') line_count++;
  179.   else if (c!=' ' && c!='\t') continue;
  180.   in_word=0; /* |c| is newline, space, or tab */
  181. }
  182.  
  183. @ Buffered I/O allows us to count the number of characters almost for free.
  184.  
  185. @<Fill |buff...@>=
  186. if (ptr>=buf_end) {
  187.   ptr=buffer; c=read(fd,ptr,buf_size);
  188.   if (c<=0) break;
  189.   char_count+=c; buf_end=buffer+c;
  190. }
  191.  
  192. @ It's convenient to output the statistics by defining a new function
  193. |wc_print|; then the same function can be used for the totals.
  194. Additionally we must decide here if we know the name of the file
  195. we have processed or if it was just |stdin|.
  196.  
  197. @<Write...@>=
  198. if (!silent) {
  199.   wc_print(which, char_count, word_count, line_count);
  200.   if (file_count) printf (" %s\n", *argv); /* not |stdin| */
  201.   else printf ("\n"); /* |stdin| */
  202. }
  203.  
  204. @ @<Upda...@>=
  205. tot_line_count+=line_count;
  206. tot_word_count+=word_count;
  207. tot_char_count+=char_count;
  208.  
  209. @ We might as well improve a bit on \UNIX/'s \.{wc} by displaying the
  210. number of files too.
  211.  
  212. @<Print the...@>=
  213. if (file_count>1 || silent) {
  214.   wc_print(which, tot_char_count, tot_word_count, tot_line_count);
  215.   if (!file_count) printf("\n");
  216.   else printf(" total in %d file%s\n",file_count,file_count>1?"s":"");
  217. }
  218.  
  219. @ Here now is the function that prints the values according to the
  220. specified options.  The calling routine is supposed to supply a
  221. newline. If an invalid option character is found we inform
  222. the user about proper usage of the command. Counts are printed in
  223. 8-digit fields so that they will line up in columns.
  224.  
  225. @d print_count(n) printf("%8ld",n)
  226.  
  227. @<Fun...@>=
  228. wc_print(which, char_count, word_count, line_count)
  229. char *which; /* which counts to print */
  230. long char_count, word_count, line_count; /* given totals */
  231. {
  232.   while (*which) 
  233.     switch (*which++) {
  234.     case 'l': print_count(line_count); break;
  235.     case 'w': print_count(word_count); break;
  236.     case 'c': print_count(char_count); break;
  237.     default: if ((status & usage_error)==0) {
  238.         fprintf (stderr, "\nUsage: %s [-lwc] [filename ...]\n", prog_name);
  239. @.Usage: ...@>
  240.         status|=usage_error;
  241.       }
  242.     }
  243. }
  244.  
  245. @ Incidentally, a test of this program against the system \.{wc}
  246. command on a \SPARC\ showed that the ``official'' \.{wc} was slightly
  247. slower. Furthermore, although that \.{wc} gave an appropriate error
  248. message for the options `\.{-abc}', it made no complaints about the
  249. options `\.{-labc}'! Dare we suggest that the system routine might have been
  250. better if its programmer had used a more literate approach?
  251.  
  252. @* Index.
  253. Here is a list of the identifiers used, and where they appear. Underlined
  254. entries indicate the place of definition. Error messages are also shown.
  255.