Listing 9.2:The header filefetch-line.hisfetch-linemodule’s interface.
1 #ifndef H_FETCH_LINE_H
2 #define H_FETCH_LINE_H
3 #include <stdio.h>
4 char *fetch_line(char *buf, int buflen, FILE *stream, int *lineno);
5 #endif /∗H_FETCH_LINE_H*/
You may change thestd=c89tostd=c99if you wish. The program is compatible with both C89 and C99.
Here is a transcript of an interactive session with the program:
$ ./fetch-line-demo <fgets-demo.c trimmed line 3: int main(void) trimmed line 4: {
trimmed line 5: char buf[BUFLEN];
*** reading error: input line 6 too long for fetch_line’s buf[40]
As you see, we are feeding the filefgets-demo.c(which we encountered in Listing 9.1 on page 65) to our demo program.28 Compare the program’s output shown here to the con- tents offgets-demo.c. Fromfetch_line()’s point of view, the first two lines of the in- put are comments(!) since each begins with the#character; therefore they are discarded.
The third line receivesfetch_line()’s approval; hence it duly prints it, prefacing it with the phrase “trimmed line 3:”. Line 4, consisting of a single{character, is also printed without change. Line 5’s leading whitespace is trimmed away, and what remains is printed. Line 6 is not acceptable tofetch_line(); it is too long to fit in a buffer of length 40 which it is given to work with. Therefore it prints an error message and quits.
If we increase the buffer size sufficiently (see theBUFLENpreprocessor macro in Part 9.1 of Section 9.6), the program will read the file all the way to the end.
9.5 The files fetch-line.[ch]
The filesfetch-line.hand fetch-line.ccontain ourfetch-linemodule’s interface and imple- mentation, respectively. The contents offetch-line.hare shown in their entirety in List- ing 9.2. That file’s sole purpose is to declare the prototype of the functionfetch_line() (on line 4). The standard library’sstdio.hheader file is#includedon line 3 to make the FILEsymbol available for use in the next line. You will find an explanation of the#in- clude guards#ifndef ..., etc., on page 51.
The contents of the implementation filefetch-line.care shown in an outline form in Listing 9.3. It defines two functions,trim_line()andfetch_line(), whose pur- poses I will now describe.
9.5.1 The functiontrim_line()
The functiontrim_line()that appears on line 5 in Listing 9.3 is declaredstatic29, indicating that it is only for internal use within the filefetch-line.c. The function’s sole ar- gument is a pointersto a buffer which is expected to contain a properlyNUL-terminated string. It trims the string of any leading or trailing whitespace and comments, as detailed
28You may feed any text file to the program; there is nothing special aboutfgets-demo.c, but it works nicely for demonstrating some of the features of ourfetch-linemodule.
29See Section 1.6 regarding thestaticdeclaration specifier.
68 Chapter 9. Reading lines:fetch_line()
Listing 9.3:An outline of the filefetch-line.c, which isfetch-linemodule’s implementation.
1 #include <string.h>
2 #include <ctype.h>
3 #include <stdlib.h>
4 #include "fetch-line.h"
5 static char *trim_line(char *s) ...
6 char *fetch_line(char *buf,
7 int buflen, FILE *stream, int *lineno) ...
⊗ ⊗ ⊗ ⊗ # ⊗ ⊗ ⊗ \n \0 ×
whitespace data whitespace comment
⊗ ⊗ ⊗ ⊗ \0 \0 \0 \0 ⊗ ⊗ ⊗ \n \0 ×
the trimmed string
s t’’ t’
Figure 9.1: The top drawing shows a string buffer that contains a data segment along with leading and trailing whitespace and comments. The bottom drawing shows the associated trimmed string. A⊗indicates any character other than a\0, and a×indicates any character at all. The pointerspoints to the first character of the trimmed string in a dark outline. The pointertstarts at sand slides down to either\#or\0, whichever comes first, and arrives at the positiont’. Then in a retrograde motion it overwrites all whitespace by\0until it arrives at a nonwhitespace character at the positiont’’.
in Section 9.3, and returns a pointer to the trimmed string’s first character. To help you get started, I have shown its operation graphically in Figure 9.1. The top part of Fig- ure 9.1 shows a buffer that contains a data part along with leading and trailing whitespace characters and a comment. The bottom part shows the trimmed buffer in a dark outline.
The arrows show pointer positions at the various stages of the processing of the string according to the following procedure:
1. Move the pointersforward, one character at a time, skipping over whitespaces, if any, and stopping at the first nonwhitespace character. This ends the trimming of the data segment’sleading whitespace. Now the pointers is stationed at the start of the data segment. It is shown with the arrow markedsin Figure 9.1.
2. Set a second pointer,t, initially ats, and then move it forward until you arrive at either a\0or a#character. If it’s#, change it to\0to truncate the comment as per the instructions in subsection 9.3.1. The stopping position oftis shown with the arrow markedt’in Figure 9.1 (but it will be just a plaintin your code).
3. If the positiont’is other thans, movetbackward, one character at a time, over- writing any whitespace with a\0, stopping at the first nonwhitespace character.
This trims the data segment’strailing whitespace, if any. Now the pointer is sta- tioned at the position marked with the arrowt’’in Figure 9.1 (but it will be just a plaintin your code).
9.5. The filesfetch-line.[ch] 69
Listing 9.4:The functionfetch_line()reads and returns the next nonempty trimmed line from the input stream. If the input line is too long to fit in the given buffer, it prints a diagnostic and callsexit().
1 char *fetch_line(char *buf, int buflen, FILE *stream, int *lineno)
2 {
3 char *s;
4 if (fgets(buf, buflen, stream) == NULL)
5 return NULL;
6 ++*lineno;
7 if (buf[strlen(buf) - 1] = ’\n’) {
8 fprintf(stderr, "*** reading error: input line %d too "
9 "long for %s’s buf[%d]\n",
10 *lineno, __func__, buflen);
11 exit(EXIT_FAILURE);
12 }
13 s = trim_line(buf);
14 if (*s = ’\0’)
15 return s;
16 else
17 return fetch_line(buf, buflen, stream, lineno); //recurse
18 }
At the completion of the process, the pointerspoints to the trimmed data string. The functiontrim_line()returns that pointer to the caller. You have all the necessary information now to implement your owntrim_line().
Remark 9.2. If a string contains nothing but comments and whitespace, then it is reduced to an empty string after trimming. Consequently, the pointer returned by trim_line()points to\0. We use this observation in the next subsection to skip over empty lines in an input stream.
9.5.2 The functionfetch_line()
The functionfetch_line() that appears on line 6 in Listing 9.3 is presented in its entirety in Listing 9.4. The caller offetch_line()supplies the bufferbufof length buflenand astreamwhich is open for reading. fetch_line()callsfgets()to read a line fromstreamintobufand then passes that line totrim_line()to strip it of comments and leading and trailing whitespace. If the trimmed string is nonempty, thenfetch_line()returns a pointer to the start of that string; otherwise it calls itself recursively in search of a nonempty line. If the end of the input is reached, or if the call tofgets()experiences an error,fetch_line()returnsNULL.
Ourfetch_line()is not designed to resize the buffer. If an input line is too long to fit,fetch_line()takes avery drastic action:it prints a diagnostic message tostderr and then callsexit()to abort the program! There are other ways of handling such contingencies; I have chosen this extremely drastic route because it’s the only useful action for our purposes.
How doesfetch_line()tell if a line is too long to fit? Since the lines are read byfgets(), and sincefgets()includes the line’s terminating newline character in the fetched buffer, as shown in the “case 3” figure on page 64, then a properly fitting line will be a string terminated by the two characters\n\0; that is, the string’s last character,
70 Chapter 9. Reading lines:fetch_line()
Listing 9.5:The filefetch-line-demo.cis a driver for thefetch-linemodule.
1 #include <stdio.h>
2 #include "fetch-line.h"
3 #define BUFLEN 40
4 int main(void)
5 {
6 char buf[BUFLEN];
7 char *s;
8 int lineno = 0;
9 while ((s = fetch_line(buf, BUFLEN, stdin, &lineno)) = NULL)
10 printf("trimmed line %3d: %s\n", lineno, s);
11 return 0;
12 }
just before the null terminator, will be \n. In Listing 9.4 we determine a string’s last character with the help of the standard library’sstrlen()function, which returns a string’s length.
fetch_line()’s fourth argument,lineno, is a pointer to an integer that holds the value of the current line number. Wheneverfetch_line()reads a line, it increments the number stored there. The caller may refer to that number when issuing diagnostics about possible errors in the input.