/* * Copyright (c) 1993 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * * File I/O routines callable by users. */ #include "stdarg.h" #include "calc.h" #define READSIZE 1024 /* buffer size for reading */ /* * Definition of opened files. */ typedef struct { FILEID id; /* id to identify this file */ FILE *fp; /* real file structure for I/O */ char *name; /* file name */ BOOL reading; /* TRUE if opened for reading */ BOOL writing; /* TRUE if opened for writing */ char *mode; /* open mode */ } FILEIO; /* * Table of opened files. * The first three entries always correspond to stdin, stdout, and stderr, * and cannot be closed. Their file ids are always 0, 1, and 2. */ static FILEIO files[MAXFILES] = { FILEID_STDIN, stdin, "(stdin)", TRUE, FALSE, "reading", FILEID_STDOUT, stdout, "(stdout)", FALSE, TRUE, "writing", FILEID_STDERR, stderr, "(stderr)", FALSE, TRUE, "writing" }; static FILEID lastid = FILEID_STDERR; /* last allocated file id */ /* * Open the specified file name for reading or writing as determined by * the specified mode ("r", "w", or "a"). Returns a file id which can be * used to do I/O to the file, or else FILEID_NONE if the open failed. * Aborts with an error if too many files are opened or the mode is illegal. */ FILEID openid(name, mode) char *name; /* file name */ char *mode; /* open mode */ { FILEIO *fiop; /* file structure */ FILEID id; /* new file id */ int count; if (((*mode != 'r') && (*mode != 'w') && (*mode != 'a')) || mode[1]) error("Illegal mode for fopen"); count = MAXFILES; do { if (--count < 0) error("Too many open files"); id = ++lastid; fiop = &files[id % MAXFILES]; } while (fiop->reading || fiop->writing); fiop->name = (char *)malloc(strlen(name) + 1); if (fiop->name == NULL) { lastid--; error("No memory for filename"); } strcpy(fiop->name, name); fiop->fp = f_open(name, mode); if (fiop->fp == NULL) { free(fiop->name); fiop->name = NULL; lastid--; return FILEID_NONE; } switch (*mode) { case 'r': fiop->mode = "reading"; fiop->reading = TRUE; break; case 'w': fiop->mode = "writing"; fiop->writing = TRUE; break; case 'a': fiop->mode = "appending"; fiop->writing = TRUE; break; } fiop->id = id; return id; } /* * Find the file I/O structure for the specified file id, and verify that * it is opened in the required manner ('r' for reading or 'w' for writing). * If mode is 0, then no open checks are made at all, and NULL is then * returned if the id represents a closed file. */ static FILEIO * findid(id, mode) FILEID id; { FILEIO *fiop; /* file structure */ char *msg; BOOL flag; if ((id < 0) || (id > lastid)) error("Illegal file id"); fiop = &files[id % MAXFILES]; switch (mode) { case 'r': msg = "Reading from"; flag = fiop->reading; break; case 'w': msg = "Writing to"; flag = fiop->writing; break; case 0: msg = NULL; break; default: error("Unknown findid mode"); } if (fiop->id != id) { if (msg) error("%s closed file", msg); return NULL; } if (msg && !flag) error("%s file not opened that way", msg); return fiop; } /* * Return whether or not a file id is valid. This is used for if tests. */ BOOL validid(id) FILEID id; { return (findid(id, 0) != NULL); } /* * Return the file id for the entry in the file table at the specified index. * Returns FILEID_NONE if the index is illegal or the file is closed. */ FILEID indexid(index) long index; { FILEIO *fiop; /* file structure */ if ((index < 0) || (index >= MAXFILES)) return FILEID_NONE; fiop = &files[index]; if (fiop->reading || fiop->writing) return fiop->id; return FILEID_NONE; } /* * Close the specified file id. Returns TRUE if there was an error. * Closing of stdin, stdout, or stderr is illegal, but closing of already * closed files is allowed. */ BOOL closeid(id) FILEID id; { FILEIO *fiop; /* file structure */ int err; if ((id == FILEID_STDIN) || (id == FILEID_STDOUT) || (id == FILEID_STDERR)) error("Cannot close stdin, stdout, or stderr"); fiop = findid(id, 0); if (fiop == NULL) return FALSE; fiop->id = FILEID_NONE; if (!fiop->reading && !fiop->writing) error("Closing non-opened file"); fiop->reading = FALSE; fiop->writing = FALSE; if (fiop->name) free(fiop->name); fiop->name = NULL; err = ferror(fiop->fp); err |= fclose(fiop->fp); fiop->fp = NULL; return (err != 0); } /* * Return whether or not an error occurred to a file. */ BOOL errorid(id) FILEID id; { FILEIO *fiop; /* file structure */ fiop = findid(id, 0); if (fiop == NULL) error("Closed file for ferror"); return (ferror(fiop->fp) != 0); } /* * Return whether or not end of file occurred to a file. */ BOOL eofid(id) FILEID id; { FILEIO *fiop; /* file structure */ fiop = findid(id, 0); if (fiop == NULL) error("Closed file for feof"); return (feof(fiop->fp) != 0); } /* * Flush output to an opened file. */ void flushid(id) FILEID id; { FILEIO *fiop; /* file structure */ fiop = findid(id, 'w'); fflush(fiop->fp); } /* * Read the next line from an opened file. * Returns a pointer to an allocated string holding the null-terminated * line (without any terminating newline), or else a NULL pointer on an * end of file or error. */ void readid(id, retptr) FILEID id; /* file to read from */ char **retptr; /* returned pointer to string */ { FILEIO *fiop; /* file structure */ char *str; /* current string */ int len; /* current length of string */ int totlen; /* total length of string */ char buf[READSIZE]; /* temporary buffer */ totlen = 0; str = NULL; fiop = findid(id, 'r'); while (fgets(buf, READSIZE, fiop->fp) && buf[0]) { len = strlen(buf); if (totlen) str = (char *)realloc(str, totlen + len + 1); else str = (char *)malloc(len + 1); if (str == NULL) error("No memory in freadline"); strcpy(&str[totlen], buf); totlen += len; if (buf[len - 1] == '\n') { str[totlen - 1] = '\0'; *retptr = str; return; } } if (totlen && ferror(fiop->fp)) { free(str); str = NULL; } *retptr = str; } /* * Return the next character from an opened file. * Returns EOF if there was an error or end of file. */ int getcharid(id) FILEID id; { return fgetc(findid(id, 'r')->fp); } /* * Print out the name of an opened file. * If the file has been closed, a null name is printed. * If flags contain PRINT_UNAMBIG then extra information is printed * identifying the output as a file and some data about it. */ void printid(id, flags) FILEID id; { FILEIO *fiop; /* file structure */ FILE *fp; fiop = findid(id, 0); if (fiop == NULL) { math_str((flags & PRINT_UNAMBIG) ? "FILE (closed)" : "\"\""); return; } if ((flags & PRINT_UNAMBIG) == 0) { math_chr('"'); math_str(fiop->name); math_chr('"'); return; } fp = fiop->fp; math_fmt("FILE \"%s\" (%s, pos %ld", fiop->name, fiop->mode, ftell(fp)); if (ferror(fp)) math_str(", error"); if (feof(fp)) math_str(", eof"); math_chr(')'); } /* * Print a formatted string similar to printf. Various formats of output * are possible, depending on the format string AND the actual types of the * values. Mismatches do not cause errors, instead something reasonable is * printed instead. The output goes to the file with the specified id. */ void idprintf(id, fmt, count, vals) FILEID id; /* file id to print to */ char *fmt; /* standard format string */ VALUE **vals; /* table of values to print */ { FILEIO *fiop; VALUE *vp; char *str; int ch, len; int oldmode, newmode; long olddigits, newdigits; long width, precision; BOOL didneg, didprecision; fiop = findid(id, 'w'); setfp(fiop->fp); while ((ch = *fmt++) != '\0') { if (ch == '\\') { ch = *fmt++; switch (ch) { case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'f': ch = '\f'; break; case 'v': ch = '\v'; break; case 'b': ch = '\b'; break; case 0: setfp(stdout); return; } math_chr(ch); continue; } if (ch != '%') { math_chr(ch); continue; } /* * Here to handle formats. */ didneg = FALSE; didprecision = FALSE; width = 0; precision = 0; ch = *fmt++; if (ch == '-') { didneg = TRUE; ch = *fmt++; } while ((ch >= '0') && (ch <= '9')) { width = width * 10 + (ch - '0'); ch = *fmt++; } if (ch == '.') { didprecision = TRUE; ch = *fmt++; while ((ch >= '0') && (ch <= '9')) { precision = precision * 10 + (ch - '0'); ch = *fmt++; } } if (ch == 'l') ch = *fmt++; oldmode = _outmode_; newmode = oldmode; olddigits = _outdigits_; newdigits = olddigits; if (didprecision) newdigits = precision; switch (ch) { case 'd': case 's': case 'c': break; case 'f': newmode = MODE_REAL; break; case 'e': newmode = MODE_EXP; break; case 'r': newmode = MODE_FRAC; break; case 'o': newmode = MODE_OCTAL; break; case 'x': newmode = MODE_HEX; break; case 'b': newmode = MODE_BINARY; break; case 0: setfp(stdout); return; default: math_chr(ch); continue; } if (--count < 0) error("Not enough arguments for fprintf"); vp = *vals++; setdigits(newdigits); set_mode(newmode); /* * If there is no width specification, or if the type of * value requires multiple lines, then just output the * value directly. */ if ((width == 0) || (vp->v_type == V_MAT) || (vp->v_type == V_LIST)) { printvalue(vp, PRINT_NORMAL); set_mode(oldmode); setdigits(olddigits); continue; } /* * There is a field width. Collect the output in a string, * print it padded appropriately with spaces, and free it. * However, if the output contains a newline, then ignore * the field width. */ divertio(); printvalue(vp, PRINT_NORMAL); str = getdivertedio(); if (strchr(str, '\n')) width = 0; len = strlen(str); while (!didneg && (width > len)) { width--; math_chr(' '); } math_str(str); free(str); while (didneg && (width > len)) { width--; math_chr(' '); } set_mode(oldmode); setdigits(olddigits); } setfp(stdout); } /* END CODE */