home *** CD-ROM | disk | FTP | other *** search
/ Monster Media 1994 #1 / monster.zip / monster / PROG_C / RECIO110.ZIP / TUTOR.TXT < prev    next >
Text File  |  1994-03-28  |  12KB  |  310 lines

  1.     Title: A TUTORIAL INTRODUCTION TO THE C LANGUAGE RECIO LIBRARY
  2. Copyright: (C) 1994 William Pierpoint
  3.   Version: 1.10
  4.      Date: March 28, 1994
  5.  
  6.  
  7.  
  8. 1.0 STDIO AND RECIO
  9.  
  10. The program many people learned when first introduced to the C programming 
  11. language was the "hello, world" program published in Kernighan and Richie's 
  12. "The C Programming Language."  And the first line of that first program,
  13.  
  14. #include <stdio.h>
  15.  
  16. tells the compiler that the functions and macros provided by the standard 
  17. input/output library are needed for the program.  The "hello, world" program 
  18. uses the powerful printf statement for output.  Unfortunately printf's 
  19. counterpart for input, scanf, looks deceptively like printf, but has many 
  20. ways to trap to unwary programmer.  These include failure to provide the 
  21. address of an variable, size of argument mismatched with the specification 
  22. in the format statement, and number of arguments mismatched with the 
  23. specification in the format statement.
  24.  
  25. Suppose you use a library that defines a boolean type as an unsigned 
  26. character.  You develop an output module that writes variables of type 
  27. boolean to a file,
  28.  
  29.     /* output */
  30.     boolean state=0;
  31.     ...
  32.     fprintf(fp, "%6d", state);
  33.  
  34. where fp is a pointer to FILE.  Once you get the output module working, you 
  35. decide to develop the input module to read back into the program the data 
  36. you wrote to disk.
  37.  
  38.     /* input */
  39.     boolean state;
  40.     ...
  41.     fscanf(fp, "%d", &state);
  42.     
  43. So, is this ok?  On one compiler this worked consistently without problems, 
  44. but on another compiler, it overwrote the value in another variable.  Why?  
  45. Because fscanf expects the address of an integer, not an unsigned char.  
  46. One compiler overwrote the adjoining memory address and the other compiler 
  47. apparently did not.  And since compilers don't do type checking on functions 
  48. with variable number of arguments, you don't get any errors or warnings.  That 
  49. is what is so infuriating about this type of error.  You see that another 
  50. variable has the wrong value, you check all the code that uses the other 
  51. variable, and you can't find anything wrong with it.  In the midst of 
  52. development, it is hard to imagine that the problem is caused by code that 
  53. has nothing to do with the variable containing the bad value.
  54.  
  55. The recio (record input/output) library takes a different approach to input.  
  56. To input the boolean variable using the recio library, just write
  57.  
  58.     /* input */
  59.     boolean state;
  60.     ...
  61.     state = rgeti(rp);
  62.  
  63. where rp is a pointer to REC (more about this later).  The rgeti function 
  64. gets an integer from the input and the compiler converts it to a boolean 
  65. when it makes the assignment.  No need to worry about crazy pointers here!
  66.  
  67. Since virtually every program has to do input or output, the stdio library 
  68. is familiar to every C programmer.  Many functions in the recio library 
  69. are analogous to the stdio library.  This makes the learning curve easier.
  70.  
  71.         Analogous stdio/recio components
  72.  
  73.     stdio        recio
  74.     ---------    ---------
  75.     FILE        REC
  76.     FOPEN_MAX    ROPEN_MAX
  77.  
  78.     stdin        recin
  79.  
  80.     fopen        ropen
  81.     fclose        rclose
  82.     fgets        rgetrec
  83.     fscanf        rgeti, rgetd, rgets, ...
  84.     clearerr    rclearerr
  85.     feof        reof
  86.     ferror        rerror
  87.  
  88. At this time recio only includes functions for input.  So why then does it 
  89. stand for record input/output?  Why isn't it called just reci?  Output might 
  90. be added in a later version, and if so, we don't want to have to go through 
  91. our code and change every reci to recio.  
  92.  
  93.  
  94. 2.0 EXAMPLES
  95.  
  96. 2.1 Line Input
  97.  
  98. One of the first things you can do with the recio library to is to substitute 
  99. rgetrec() for fgets() to get a line of text (record) from a file (or standard 
  100. input).  The advantage of rgetrec() is that you don't have to go to the 
  101. trouble to allocate space for a string buffer, or worry about the size of the 
  102. string buffer.  The recio library handles that for you automatically.  The 
  103. rgetrec function is like fgets() in that it gets a string from a stream, but 
  104. it is like gets() in that it trims off the trailing newline character.
  105.  
  106. The echo program demonstrates the use of the rgetrec function.
  107.  
  108. /* echo.c - echo input to output */
  109.  
  110. #include <stdio.h>
  111. #include <stdlib.h>
  112.  
  113. #include "recio.h"
  114.  
  115. main()
  116. {
  117.     /* while input continues to be available */
  118.     while (rgetrec(recin)) {
  119.  
  120.         /* echo record buffer to output */
  121.         puts(rrecs(recin));
  122.     }
  123.  
  124.     /* if exited loop before end-of-file */
  125.     if (!reof(recin)) {
  126.         exit (EXIT_FAILURE);
  127.     }
  128.     return (EXIT_SUCCESS);
  129. }
  130.  
  131. The echo program reads standard input using recin, the recio equivalent to 
  132. stdin.  
  133.  
  134. The rgetrec function returns a pointer to the record buffer, but the echo 
  135. program did not use a variable to hold a pointer to the string (although
  136. it could have).  Instead, the record buffer was accessed through the rrecs 
  137. macro, which provides a pointer to the record buffer.
  138.  
  139. Since rgetrec returns NULL on either error or end-of-file, your program 
  140. needs to find out which condition occurred.  The echo program just exits 
  141. with a failure status if an error occurred before the end of the file was 
  142. reached.
  143.  
  144.  
  145. 2.2 Line, Word, and Character Counting
  146.  
  147. The power of the recio library comes from its facilities to break records 
  148. into fields and from the many functions that operate on fields.  Because 
  149. the default field delimiter is the space character (which breaks on any 
  150. whitespace), the default behavior is equivalent to subdividing a line of 
  151. text into words.  
  152.  
  153. The wc program counts lines, words, and characters for files specified 
  154. on the command line.
  155.  
  156. /* wc.c - count lines, words, characters */
  157.  
  158. #include <stdio.h>
  159. #include <stdlib.h>
  160. #include <string.h>
  161.  
  162. #include "recio.h"
  163.  
  164. #define SIZE_T_MAX (~(size_t)0)
  165.  
  166. main(int argc, char *argv[])
  167. {
  168.     int  nf;    /* number of files */
  169.     REC *rp;    /* pointer to open record stream */
  170.     long nc,    /* number of characters */
  171.          nw,    /* number of words */
  172.          nl;    /* number of lines */
  173.     
  174.     /* loop through all files */
  175.     for (nf=1; nf < argc; nf++) {
  176.     
  177.         /* open record stream */
  178.         rp = ropen(argv[nf], "r");
  179.         if (!rp) {
  180.             if (errno == ENOENT) {
  181.                 printf("ERROR: Could not open %s\n", argv[nf]);
  182.                 continue;
  183.             } else {
  184.                 printf("FATAL ERROR: %s\n", strerror(errno));
  185.                 exit (EXIT_FAILURE);
  186.             }
  187.         }
  188.  
  189.         /* initialize */
  190.         nc = nw = 0;
  191.         rsetfldch(rp, ' ');
  192.         rsettxtch(rp, ' ');
  193.  
  194.         /* loop through all lines (records) */
  195.         while (rgetrec(rp)) {
  196.         
  197.             /* count number of characters in line w/o '\n' */
  198.             nc += strlen(rrecs(rp));
  199.         
  200.             /* count actual number of words (fields) */
  201.             nw += rskipnfld(rp, SIZE_T_MAX);
  202.         }
  203.  
  204.         /* if exited loop on error rather than end-of-file */
  205.         if (rerror(rp)) {
  206.             printf("FATAL ERROR: %s\n", strerror(rerror(recin)));
  207.             exit (EXIT_FAILURE);
  208.         }
  209.  
  210.         /* get number of lines (records) */
  211.         nl = rrecno(rp);
  212.  
  213.         /* adjust nc for operating system line termination character(s) */
  214. #ifdef __MSDOS__
  215.         nc += nl + nl;  /* carriage return and linefeed for each line */
  216. #else
  217.         nc += nl;       /* newline for each line */
  218. #endif
  219.  
  220.         /* output results */
  221.         printf("%s: %ld %ld %ld\n", rnames(rp), nl, nw, nc);
  222.         
  223.         /* close record stream */
  224.         rclose(rp);
  225.     }
  226.     return (EXIT_SUCCESS);
  227. }
  228.  
  229. If ropen() fails, the wc program goes to the trouble to check errno for 
  230. ENOENT rather than just assuming that the failure was caused by a missing 
  231. file.  
  232.  
  233. The wc program also sets the field and text delimiters even though it is 
  234. unneccessary here since they are the same as the default values.  If you 
  235. wanted to read a comma-delimited file, you could set the the delimiters to,
  236.  
  237.     rsetfldch(rp, ',');
  238.     rsettxtch(rp, '"');
  239.  
  240. which allows you to also read text fields containing commas by delimiting 
  241. the text with quotes, such as "Hello, World."
  242.  
  243. Fields are counted using the rskipnfld function.  The program asks the 
  244. function to skip more fields than could possibly exist in the record and 
  245. the function returns with the number of fields actually skipped.  The 
  246. rskipnfld function can be handy to have around.  You might have a data file 
  247. with dozens of fields in a record but your program only needs a couple of 
  248. them.  You can use rskipnfld() to skip over the fields you don't need to read.
  249.  
  250. The recio library gives you more control over your input data than stdio.  
  251. If the last field is missing from a data file, fscanf() starts reading the 
  252. next line.  In a file with a complex structure, it can be difficult to tell 
  253. where you are.  Sometimes every record in a file has a different format.  
  254. The recio library has functions you can use to always find out where you are.  
  255. You only move from record to record when you use the rgetrec function.
  256.  
  257. Note that the wc program assumes that each line ends with a line terminator.  
  258. That assumption may not be correct as the last line could end with the 
  259. file terminator, in which case the character count is off by one or two.
  260.  
  261.  
  262. 2.3 Field Functions
  263.  
  264. For most programs you will want to use those recio functions that read data 
  265. from the fields of a record.  Functions are available to read integer, 
  266. unsigned integer, long, unsigned long, double, float, character, and string 
  267. fields.  Functions are divided into two groups: those that read character 
  268. delimited fields (such as comma-delimited) and those that read column 
  269. delimited fields (such as an integer between columns 0 and 9).  Each group 
  270. is further divided into two subgroups: one for numeric data in base 10 and 
  271. the other for numeric data in any base from 2 to 36.  See the text files 
  272. USAGE.TXT and SPEC.TXT for more information.
  273.  
  274. If you explore the source code for the recio library, you will find that the 
  275. field functions can easily be extended for other data types.  For example 
  276. you could define, in one line of code, a function that would get a boolean 
  277. variable and that function would generate an ERANGE error if the value was 
  278. anything other than 0 or 1.  See the file DESIGN.TXT for more information.  
  279. Of course if you are not concerned with validation, you can always use 
  280. rgeti().
  281.  
  282.  
  283. 2.4 Error Handling
  284.  
  285. Rather than checking errno and rerror() for errors after each call to a recio 
  286. function, or checking the return value from those functions that return an 
  287. error value, you can register a callback error function using the rseterrfn 
  288. function.  The error function gives you one convenient place to handle all 
  289. recio errors.  As you write your error function, you will find that the recio 
  290. library provides many useful functions for determining and reporting the 
  291. location and type of error.
  292.  
  293. One important kind of error is the data error.  Data errors occur when data 
  294. values are too large or too small, when fields contain illegal characters, or 
  295. when data is missing.  Your error function can correct data errors either 
  296. through algorithms you supply or by asking the user for a replacement value.  
  297.  
  298. For an example of a callback error function, see the TEST.C source code.  
  299. A skeleton code structure for a callback error function is given in the 
  300. file DESIGN.TXT.
  301.  
  302.  
  303. 3.0 WHAT NOW?
  304.  
  305. That's it for this brief introduction.  Next, if you haven't already done 
  306. so, spend a few minutes running TEST.EXE, inputting various data values 
  307. (both valid and invalid) to see what happens, and perusing the TEST.C source 
  308. code.  Then to study the recio functions in more detail, move on to the 
  309. remaining documentation.
  310.