§1  WC  AN EXAMPLE OF CWEB 1

November 9, 1994 at 16:39

1. An example of CWEB. This example, based on a program by Klaus Guntermann and Joachim Schrod [TUGboat 7 (1986), 135–137] presents the “word count” program from UNIX, rewritten in to demonstrate literate programming in C. 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 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.)

2. Most 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 that is defined by this program :

⟨ Header files to include 3 ⟩

⟨ Global variables 4 ⟩

⟨ Functions 20 ⟩

⟨ The main program 5 ⟩

3. We must include the standard I/O definitions, since we want to send formatted output to stdout and stderr . ⟨ Header files to include 3 ⟩ ≡ #include

This code is used in section 2.

4. 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. #define 0 /∗ status code for successful run ∗/ #define usage error 1 /∗ status code for improper syntax ∗/ #define cannot open file 2 /∗ status code for file access error ∗/ ⟨ Global variables 4 ⟩ ≡

int status = ; /∗ exit status of command, initially ∗/

char ∗prog name; /∗ who we are ∗/

See also section 14. This code is used in section 2.

5. Now we come to the general layout of the main function. ⟨ The main program 5 ⟩ ≡

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 6 ⟩ prog name = argv [0]; ⟨ Set up option selection 7 ⟩; ⟨ Process all the files 8 ⟩; ⟨ Print the grand totals if there were multiple files 19 ⟩; exit (status );

}

This code is used in section 2.

2  AN EXAMPLE OF CWEB  WC §6

6. 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, ‘ ’ would cause just the number of characters and the number of lines to be printed, in that order.

We do not process this string now; we simply remember where it is. It will be used to control the formatting at output time. ⟨ Variables local to main 6 ⟩ ≡

int file count ; /∗ how many files there are ∗/

char ∗which; /∗ which counts to print ∗/

See also sections 9 and 12. This code is used in section 5.

7. ⟨ Set up option selection 7 ⟩ ≡ which = ; /∗ if no option is given, print all three values ∗/ if (argc > 1 ∧ ∗argv [1] ≡ ) {

which = argv [1] + 1;

argc^{−}; argv^{++};

}

file count = argc − 1;

This code is used in section 5.

8. 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 … while loop because we should read from the standard input if no file name is given. ⟨ Process all the files 8 ⟩ ≡

argc^{−};

do { ⟨ If a file is given, try to open ∗(^{++}argv ); continue if unsuccessful 10 ⟩; ⟨ Initialize pointers and counters 13 ⟩; ⟨ Scan file 15 ⟩; ⟨ Write statistics for file 17 ⟩; ⟨ Close file 11 ⟩; ⟨ Update grand totals 18 ⟩; /∗ even if there is only one file ∗/

} while (^{−}argc > 0);

This code is used in section 5.

9. 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. ⟨ Variables local to main 6 ⟩ +≡

int fd = 0; /∗ file descriptor, initialized to stdin ∗/

10. #define  0  /∗ read access code for system open routine ∗/

⟨ If a file is given, try to open ∗(^{++}argv ); continue if unsuccessful 10 ⟩ ≡

if (file count > 0 ∧ (fd = open(∗(^{++}argv ), )) < 0) { fprintf (stderr , ␣ ␣ ␣ ␣ , prog name, ∗argv ); status |= cannot open file; file count^{−}; continue;

}

This code is used in section 8.

11. ⟨ Close file 11 ⟩ ≡ close(fd );

This code is used in section 8.

12. 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. #define buf size /∗ ’s is chosen for efficiency ∗/ ⟨ Variables local to main 6 ⟩ +≡

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 ∗/

13. ⟨ Initialize pointers and counters 13 ⟩ ≡ ptr = buf end = buffer ; line count = word count = char count = 0; in word = 0;

This code is used in section 8.

14. 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, C’s globals are automatically zeroed. (Or rather, “statically zeroed.”) (Get it?) ⟨ Global variables 4 ⟩ +≡

long tot word count , tot line count , tot char count ; /∗ total number of words, lines, and chars ∗/

15. The present section, which does the counting that is ’s raison d’être, was actually one of the simplest to write. We look at each character and change state if it begins or ends a word. ⟨ Scan file 15 ⟩ ≡

while (1) { ⟨ Fill buffer if it is empty; break at end of file 16 ⟩; c = ∗ptr^{++}; if (c > ␣ ∧ c < ^{∘}177 ) { /∗ visible ASCII codes ∗/

if (¬in word ) { word count^{++}; in word = 1;

}

continue; } if (c ≡ ) line count^{++}; else if (c ≠ ␣ ∧ c ≠ ) continue; in word = 0; /∗ c is newline, space, or tab ∗/

}

This code is used in section 8.

16. Buffered I/O allows us to count the number of characters almost for free. ⟨ Fill buffer if it is empty; break at end of file 16 ⟩ ≡

if (ptr ≥ buf end ) { ptr = buffer ; c = read (fd , ptr , buf size); if (c ≤ 0) break; char count += c; buf end = buffer + c;

}

This code is used in section 15.

17. 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 statistics for file 17 ⟩ ≡

wc print (which, char count , word count , line count );

if (file count ) printf ( ␣  , ∗argv );  /∗ not stdin ∗/
else printf (  );  /∗ stdin ∗/

This code is used in section 8.

18. ⟨ Update grand totals 18 ⟩ ≡ tot line count += line count ; tot word count += word count ; tot char count += char count ;

This code is used in section 8.

19. We might as well improve a bit on UNIX’s by displaying the number of files too. ⟨ Print the grand totals if there were multiple files 19 ⟩ ≡

if (file count > 1) { wc print (which, tot char count , tot word count , tot line count ); printf ( ␣ ␣ ␣ ␣ , file count );

}

This code is used in section 5.

20. 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. #define print count (n) printf ( , n) ⟨ Functions 20 ⟩ ≡

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 : print count (line count );

break; case : print count (word count );

break; case : print count (char count );

break; default:

if ((status & usage error ) ≡ 0) { fprintf (stderr , ␣ ␣ ␣ ␣ , prog name); status |= usage error ;

}

}

}

This code is used in section 2.

21. Incidentally, a test of this program against the system command on a SPARCstation showed that the “official” was slightly slower. Furthermore, although that gave an appropriate error message for the options ‘ ’, it made no complaints about the options ‘ ’! Dare we suggest that the system routine might have been better if its programmer had used a more literate approach?

6  INDEX  WC §22

22. 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.

argc: \underline{5}, 7, 8. argv : \underline{5}, 7, 10, 17. buf end : \underline{12}, 13, 16. buf size: \underline{12}, 16. buffer : \underline{12}, 13, 16. : 12. c: \underline{12}. : 10. cannot open file: \underline{4}, 10. char count : \underline{12}, 13, 16, 17, 18, \underline{20}. close: 11. exit : 5. fd : \underline{9}, 10, 11, 16. file count : \underline{6}, 7, 10, 17, 19. fprintf : 10, 20. in word : \underline{12}, 13, 15. Joke: 14. line count : \underline{12}, 13, 15, 17, 18, \underline{20}. main: \underline{5}, 14. : \underline{4}. open: 10. print count : \underline{20}. printf : 17, 19, 20. prog name: \underline{4}, 5, 10, 20. ptr : \underline{12}, 13, 15, 16. read : 16. : \underline{10}. status : \underline{4}, 5, 10, 20. stderr : 3, 10, 20. stdin: 9, 17. stdout : 3. tot char count : \underline{14}, 18, 19. tot line count : \underline{14}, 18, 19. tot word count : \underline{14}, 18, 19. : 20. usage error : \underline{4}, 20. wc print : 17, 19, \underline{20}. which: \underline{6}, 7, 17, 19, \underline{20}. word count : \underline{12}, 13, 15, 17, 18, \underline{20}.

WC NAMES OF THE SECTIONS 7

⟨ Close file 11 ⟩ Used in section 8.

⟨ Fill buffer if it is empty; break at end of file 16 ⟩ Used in section 15.

⟨ Functions 20 ⟩ Used in section 2. ⟨ Global variables 4, 14 ⟩ Used in section 2.

⟨ Header files to include 3 ⟩ Used in section 2. ⟨ If a file is given, try to open ∗(^{++}argv ); continue if unsuccessful 10 ⟩ Used in section 8. ⟨ Initialize pointers and counters 13 ⟩ Used in section 8. ⟨ Print the grand totals if there were multiple files 19 ⟩ Used in section 5. ⟨ Process all the files 8 ⟩ Used in section 5.

⟨ Scan file 15 ⟩ Used in section 8.

⟨ Set up option selection 7 ⟩ Used in section 5. ⟨ The main program 5 ⟩ Used in section 2. ⟨ Update grand totals 18 ⟩ Used in section 8.

⟨ Variables local to main 6, 9, 12 ⟩ Used in section 5.

⟨ Write statistics for file 17 ⟩ Used in section 8.