📄 gb_io.w
字号:
to specify digits greater than~9. (Thus, for example, |'A'| representsthe hexadecimal digit for decimal~10.)If the next character is a valid |d|-git,|cur_pos| moves to the next character and the numerical value is returned.Otherwise |cur_pos| stays in the same place and $-1$ is returned.The second routine, |gb_number(d)|, reads characters and forms anunsigned radix-|d| number until the first non-digit is encountered.The resulting number is returned; it is zero if no digits were found.No errors are possible with this routine, because it uses|unsigned long| arithmetic.@(gb_io.h@>=extern long gb_digit(); /* |gb_digit(d)| reads a digit between 0 and |d-1| */extern unsigned long gb_number(); /* |gb_number(d)| reads a radix-|d| number */@ The value of |d| should be at most 127, if users want their programs to beportable, because \CEE/ does not treat larger |char| values in awell-defined manner. In most applications, |d| is of course either 10 or 16.@<External f...@>=long gb_digit(d) char d;{ icode[0]=d; /* make sure |'\0'| is a nondigit */ if (imap_ord(*cur_pos)<d) return icode[*cur_pos++]; return -1;}@#unsigned long gb_number(d) char d;{@+register unsigned long a=0; icode[0]=d; /* make sure |'\0'| is a nondigit */ while (imap_ord(*cur_pos)<d) a=a*d+icode[*cur_pos++]; return a;}@ The final subroutine for fetching data is |gb_string(p,c)|, whichstores a null-terminated string into locations starting at~|p|.The string starts at |cur_pos| and ends just before the first appearanceof character |c|. If |c=='\n'|, the string will stop at the end of the line.If |c| doesn't appear in the buffer at or after |cur_pos|, the last characterof the string will be the |'\n'| that is always inserted at the endof a line, unless the entire line has already been read. (If the entireline has previously been read, the empty string is always returned.)After the string has been copied, |cur_pos| advances past it.In order to use this routine safely, the user should first check thatthere is room to store up to 81 characters beginning at location~|p|.A suitable place to put the result, called |str_buf|, is providedfor the user's convenience.The location following the stored string is returned. Thus, if thestored string has length~|l| (not counting the null character that isstored at the end), the value returned will be |p+l+1|.@(gb_io.h@>=#define STR_BUF_LENGTH 160extern char str_buf[]; /* safe place to receive output of |gb_string| */extern char *gb_string(); /* |gb_string(p,c)| reads a string delimited by |c| into bytes starting at |p| */@ @d STR_BUF_LENGTH 160@<External f...@>=char str_buf[STR_BUF_LENGTH]; /* users can put strings here if they wish */char *gb_string(p,c) char *p; /* where to put the result */ char c; /* character following the string */{ while (*cur_pos && *cur_pos!=c) *p++=*cur_pos++; *p++=0; return p;}@ Here's how we test those routines in \.{test\_io}: The first line of testdata consists of 79 characters, beginning with 64 zeroes and ending with`\.{123456789ABCDEF}'. The second line is completely blank. The thirdand final line says `\.{Oops:(intentional mistake)}'.@<Test the sample data lines...@>=if (gb_number(10)!=123456789) io_errors |= 1L<<20; /* decimal number not working */if (gb_digit(16)!=10) io_errors |= 1L<<21; /* we missed the \.A following the decimal number */gb_backup();@+ gb_backup(); /* get set to read `\.{9A}' again */if (gb_number(16)!=0x9ABCDEF) io_errors |= 1L<<22; /* hexadecimal number not working */gb_newline(); /* now we should be scanning a blank line */if (gb_char()!='\n') io_errors |= 1L<<23; /* newline not inserted at end */if (gb_char()!='\n') io_errors |= 1L<<24; /* newline not implied after end */if (gb_number(60)!=0) io_errors |= 1L<<25; /* number should stop at null character */{@+char temp[100]; if (gb_string(temp,'\n')!=temp+1) io_errors |= 1L<<26; /* string should be null after end of line */ gb_newline(); if (gb_string(temp,':')!=temp+5 || strcmp(temp,"Oops")) io_errors |= 1L<<27; /* string not read properly */}if (io_errors) exit_test("Sorry, it failed. Look at the error code for clues");if (gb_digit(10)!=-1) exit_test("Digit error not detected");if (gb_char()!=':') io_errors |= 1L<<28; /* lost synch after |gb_string| and |gb_digit| */if (gb_eof()) io_errors |= 1L<<29; /* premature end-of-file indication */gb_newline();if (!gb_eof()) io_errors |= 1L<<30; /* postmature end-of-file indication */@* Opening a file. The call |gb_raw_open("foo")| will open file |"foo"| andinitialize the checksumming process. If the file cannot be opened,|io_errors| will be set to |cant_open_file|, otherwise|io_errors| will be initialized to zero.The call |gb_open("foo")| is a stronger version of |gb_raw_open|, whichis used for standard GraphBase data files like |"words.dat"| to makedoubly sure that they have not been corrupted. It returns the current valueof |io_errors|, which will be nonzero if any problems were detectedat the beginning of the file.@<Test the |gb_open| routine...@>=if (gb_open("test.dat")!=0) exit_test("Can't open test.dat");@ @d gb_raw_open gb_r_open /* abbreviation for Procrustean external linkage */@(gb_io.h@>=#define gb_raw_open gb_r_openextern void gb_raw_open(); /* open a file for GraphBase input */extern long gb_open(); /* open a GraphBase data file; return 0 if OK */@ @<External f...@>=void gb_raw_open(f) char *f;{ @<Make sure that |icode|...@>; @<Try to open |f|@>; if (cur_file) { io_errors=0; more_data=1; line_no=magic=0; tot_lines=0x7fffffff; /* allow ``infinitely many'' lines */ fill_buf(); }@+else io_errors=cant_open_file;}@ Here's a possibly system-dependent part of the code: We try first toopen the data file by using the file name itself as the path name;failing that, we try to prefix the file name with the name of thestandard directory for GraphBase data, if the program has been compiledwith |DATA_DIRECTORY| defined.@^system dependencies@>@<Try to open |f|@>=cur_file=fopen(f,"r");@^system dependencies@>#ifdef DATA_DIRECTORYif (!cur_file && (strlen(DATA_DIRECTORY)+strlen(f)<STR_BUF_LENGTH)) { sprintf(str_buf,"%s%s",DATA_DIRECTORY,f); cur_file=fopen(str_buf,"r");}#endif@ @<External f...@>=long gb_open(f) char *f;{ strncpy(file_name,f,sizeof(file_name)-1); /* save the name for use by |gb_close| */ gb_raw_open(f); if (cur_file) { @<Check the first line; return if unsuccessful@>; @<Check the second line; return if unsuccessful@>; @<Check the third line; return if unsuccessful@>; @<Check the fourth line; return if unsuccessful@>; gb_newline(); /* the first line of real data is now in the buffer */ } return io_errors;}@ @<Private...@>=static char file_name[20]; /* name of the data file, without a prefix */@ The first four lines of a typical data file should look something like this:$$\halign{\hskip5em\.{#}\hfill\cr * File "words.dat" from the Stanford GraphBase (C) 1993 Stanford University\cr * A database of English five-letter words\cr * This file may be freely copied but please do not change it in any way!\cr * (Checksum parameters 5757,526296596)\cr}$$We actually verify only that the first four lines of a data file named |"foo"|begin respectively with the characters$$\halign{\hskip5em\.{#}\hfill\cr * File "foo"\cr *\cr *\cr * (Checksum parameters $l$,$m$)\cr}$$where $l$ and $m$ are decimal numbers. The values of $l$ and~$m$are stored away as |tot_lines| and |final_magic|, to be matched at theend of the file.@<Check the first line...@>=sprintf(str_buf,"* File \"%s\"",f);if (strncmp(buffer,str_buf,strlen(str_buf))) return (io_errors |= bad_first_line);@ @<Check the second line...@>=fill_buf();if (*buffer!='*') return (io_errors |= bad_second_line);@ @<Check the third line...@>=fill_buf();if (*buffer!='*') return (io_errors |= bad_third_line);@ @<Check the fourth line; return if unsuccessful@>=fill_buf();if (strncmp(buffer,"* (Checksum parameters ",23)) return (io_errors |= bad_fourth_line);cur_pos +=23;tot_lines=gb_number(10);if (gb_char()!=',') return (io_errors |= bad_fourth_line);final_magic=gb_number(10);if (gb_char()!=')') return (io_errors |= bad_fourth_line);@* Closing a file. After all data has been input, or should have been input,we check that the file was open and that it had the correct number oflines, the correct magic number, and a correct final line. Thesubroutine |gb_close|, like |gb_open|, returns the value of|io_errors|, which will be nonzero if at least one problem was noticed.@<Test the |gb_close| routine; exit if there's trouble@>=if (gb_close()!=0) exit_test("Bad checksum, or difficulty closing the file");@ @<External f...@>=long gb_close(){ if (!cur_file) return (io_errors |= no_file_open); fill_buf(); sprintf(str_buf,"* End of file \"%s\"",file_name); if (strncmp(buffer,str_buf,strlen(str_buf))) io_errors |= bad_last_line; more_data=buffer[0]=0; /* now the {\sc GB\_\,IO} routines are effectively shut down */ /* we have |cur_pos=buffer| */ if (fclose(cur_file)!=0) return (io_errors |= cant_close_file); cur_file=NULL; if (line_no!=tot_lines+1) return (io_errors |= wrong_number_of_lines); if (magic!=final_magic) return (io_errors |= wrong_checksum); return io_errors;}@ There is also a less paranoid routine, |gb_raw_close|, thatcloses user-generated files. It simply closes the current file, if any,and returns the value of the |magic| checksum.Example: The |restore_graph| subroutine in {\sc GB\_\,SAVE} uses|gb_raw_open| and |gb_raw_close| to provide system-independent inputthat is almost as foolproof as the reading of standard GraphBase data.@ @d gb_raw_close gb_r_close /* for Procrustean external linkage */@(gb_io.h@>=#define gb_raw_close gb_r_closeextern long gb_close(); /* close a GraphBase data file; return 0 if OK */extern long gb_raw_close(); /* close file and return the checksum */@ @<External f...@>=long gb_raw_close(){ if (cur_file) { fclose(cur_file); more_data=buffer[0]=0; cur_pos=buffer; cur_file=NULL; } return magic;}@* Index. Here is a list that shows where the identifiers of this program aredefined and used.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -