home *** CD-ROM | disk | FTP | other *** search
- /*
- STDLIB2.C -- for BDS C v1.45 -- LZ, 11/9/81
-
- This file contains the source for the following
- library functions:
-
- printf fprintf sprintf _spr
- scanf fscanf sscanf _scn
- fgets
- puts fputs
- swapin
-
- Note that all the upper-level formatted I/O functions
- ("printf", "fprintf", "scanf", and "fscanf") now use
- "_spr" and "_scn" for doing conversions. While
- this leads to very modularized source code, it also
- means that calls to "scanf" and "fscanf" must process
- ALL the information on a line of text; if the format
- string runs out and there is still text left in the
- line being processed, the text will be lost (i.e., the
- NEXT scanf or fscanf call will NOT find it.)
-
- An alternate version of "_spr" is given in the file
- FLOAT.C for use with floating point numbers; see FLOAT.C
- for details. Since "_spr" is used by "printf", this
- really amounts to an alternate version of "printf."
-
- Also note that temporary work space is declared within
- each of the high-level functions as a one-dimensional
- character array. The length limit on this array is
- presently set to 132 by the #define MAXLINE statement;
- if you intend to create longer lines through printf,
- fprintf, scanf, or fscanf calls, be SURE to raise this
- limit by changing the #define statement.
-
- Some misc. comments on hacking text files with CP/M:
- The conventional CP/M text format calls for each
- line to be terminated by a CR-LF combination. In the
- world of C programming, though, we like to just use
- a single LF (also called a newline) to terminate
- lines. AND SO, the functions which deal with reading
- and writing text lines from disk files to memory and
- vice-versa ("fgets", "fputs") take special pains to
- convert CR-LF combinations into single '\n' characters
- when reading from disk ("fgets"), and convert '\n'
- characters to CR-LF combinations when writing TO disk
- ("fputs"). This allows the C programmer to do things
- in style, dealing only with a single line terminator
- while the text is in memory, while maintaining compat-
- ibility with the CP/M text format for disk files (so
- that, for example, a text file can be "type"d under
- the CCP.)
-
- Note that the "gets" function (which simply buffers up
- a line of console input at a given buffer location)
- terminates the line with a null byte ('\0') WITHOUT
- any CR or LF.
-
- Remember to put out a CPMEOF (control-Z or 0x1a) byte
- at the end of text files being written out to disk.
-
- Also, watch out when reading in text files using
- "getc". While a text file is USUALLY terminated
- with a control-Z, it MAY NOT BE if the file ends
- on an even sector boundary (although respectable
- editors will now usually make sure the control-Z
- is always there.) This means that there are two
- possible return values from "getc" which can signal
- an End-of file: CPMEOF (0x1a) or ERROR (-1, or 255
- if you assign it to a char variable) should the CPMEOF
- be missing.
- */
-
- #include "bdscio.h"
-
- char toupper(), isdigit();
-
- /*
- printf
-
- usage:
- printf(format, arg1, arg2, ...);
-
- Note that since the "_spr" function is used to
- form the output string, and then "puts" is used to
- actually print it out, care must be taken to
- avoid generating null (zero) bytes in the output,
- since such a byte will terminate printing of the
- string by puts. Thus, a statment such as:
-
- printf("%c foo",'\0');
-
- would print nothing at all.
-
- This is my latest version of the "printf" standard library
- routine. This time, folks, it REALLY IS standard. I've
- tried to make it EXACTLY the same as the version presented
- in Kernighan & Ritchie: right-justification of fields is
- now the default instead of left-justification (you can have
- left-justification by using a dash in the conversion, as
- specified in the book); the "%s" conversion can take a precision
- now as well as a field width; the "e" and "f" conversions, for
- floating point numbers, are supported in a special version of
- "_spr" given in source form in the FLOAT.C file. If you do
- a lot of number crunching and wish to have that version be the
- default (it eats up a K or two more than this version), just
- replace the version of "_spr" in DEFF.CRL with the one in FLOAT.C,
- using the CLIB program, or else be stuck with always typing in
- "float" on the clink command line...
- */
-
- printf(format)
- char *format;
- {
- char line[MAXLINE];
- _spr(line,&format); /* use "_spr" to form the output */
- puts(line); /* and print out the line */
- }
-
-
- /*
- scanf:
- This one accepts a line of input text from the
- console, and converts the text to the required
- binary or alphanumeric form (see Kernighan &
- Ritchie for a more thorough description):
- Usage:
- scanf(format, ptr1, ptr2, ...);
-
- Returns number of items matched.
-
- Since a new line of text must be entered from the
- console each time scanf is called, any unprocessed
- text left over from the last call is lost forever.
- This is a difference between BDS scanf and UNIX
- scanf. Another is that the field width specification
- is not supported here.
- */
-
- int scanf(format)
- char *format;
- {
- char line[MAXLINE];
- gets(line); /* get a line of input from user */
- return _scn(line,&format); /* and scan it with "_scn" */
- }
-
-
- /*
- fprintf:
- Like printf, except that the first argument is
- a pointer to a buffered I/O buffer, and the text
- is written to the file described by the buffer:
- ERROR (-1) returned on error.
-
- usage:
- fprintf(iobuf, format, arg1, arg2, ...);
- */
-
- int fprintf(iobuf,format)
- char *format;
- struct _buf *iobuf;
- {
- char text[MAXLINE];
- _spr(text,&format);
- return fputs(text,iobuf);
- }
-
-
- /*
- fscanf:
- Like scanf, except that the first argument is
- a pointer to a buffered input file buffer, and
- the text is taken from the file instead of from
- the console.
- Usage:
- fscanf(iobuf, format, ptr1, ptr2, ...);
- Returns number of items matched (zero on EOF.)
- Note that any unprocessed text is lost forever. Each
- time scanf is called, a new line of input is gotten
- from the file, and any information left over from
- the last call is wiped out. Thus, the text in the
- file must be arranged such that a single call to
- fscanf will always get all the required data on a
- line. This is not compatible with the way UNIX does
- things, but it eliminates the need for separate
- scanning functions for files, strings, and console
- input; it is more economical to let both "fscanf" and
- "scanf" use "sscanf". If you want to be able to scan
- a partial line with fscanf and have the rest still be
- there on the next fscanf call, you'll have to rewrite
- fscanf to be self contained (not use sscanf) and use
- "ungetc" to push back characters.
-
- Returns number of items succesfully matched.
- */
-
- int fscanf(iobuf,format)
- char *format;
- struct _buf *iobuf;
- {
- char text[MAXLINE];
- if (!fgets(text,iobuf)) return 0;
- return _scn(text,&format);
- }
-
-
- /*
- sprintf:
- Like fprintf, except a string pointer is specified
- instead of a buffer pointer. The text is written
- directly into memory where the string pointer points.
-
- Usage:
- sprintf(string,format,arg1, arg2, ...);
- */
-
- sprintf(buffer,format)
- char *buffer, *format;
- {
- _spr(buffer,&format); /* call _spr to do all the work */
- }
-
-
- /*
- sscanf:
-
- Reads a line of text in from the console and scans it
- for variable values specified in the format string. Uses
- "_scn" for actual conversions; see the comments below in
- the _scn function for more details.
-
- Usage:
- scanf(format,&arg1,&arg2,...);
- */
-
- int sscanf(line,format)
- char *line, *format;
- {
- return _scn(line,&format); /* let _scn do all the work */
- }
-
-
-
- /*
- General formatted output conversion routine, used by
- fprintf and sprintf..."line" is where the output is
- written, and "fmt" is a pointer to an argument list
- which must consist of a format string pointer and
- subsequent list of (optional) values. Having arguments
- passed on the stack works out a heck of a lot neater
- than it did before when the args were passed via an
- absolute vector in low memory!
- */
-
-
- _spr(line,fmt)
- char *line, **fmt;
- {
- char _uspr(), c, base, *sptr, *format;
- char wbuf[MAXLINE], *wptr, pf, ljflag, zfflag;
- int width, precision, *args;
-
- format = *fmt++; /* fmt first points to the format string */
- args = fmt; /* now fmt points to the first arg value */
-
- while (c = *format++)
- if (c == '%') {
- wptr = wbuf;
- precision = 6;
- ljflag = pf = zfflag = 0;
-
- if (*format == '-') {
- format++;
- ljflag++;
- }
-
-
- if (*format == '0') zfflag++; /* zero-fill feature test */
-
- width = (isdigit(*format)) ? _gv2(&format) : 0;
-
- if ((c = *format++) == '.') {
- precision = _gv2(&format);
- pf++;
- c = *format++;
- }
-
- switch(toupper(c)) {
-
- case 'D': if (*args < 0) {
- *wptr++ = '-';
- *args = -*args;
- width--;
- }
-
- case 'U': base = 10; goto val;
-
- case 'X': base = 16; goto val;
-
- case 'O': base = 8; /* note that arbitrary bases can be
- added easily before this line */
-
- val: width -= _uspr(&wptr,*args++,base);
- goto pad;
-
- case 'C': *wptr++ = *args++;
- width--;
- goto pad;
-
- case 'S': if (!pf) precision = 200;
- sptr = *args++;
- while (*sptr && precision) {
- *wptr++ = *sptr++;
- precision--;
- width--;
- }
-
- pad: *wptr = '\0';
- pad2: wptr = wbuf;
- if (!ljflag)
- while (width-- > 0)
- *line++ = zfflag ? '0' : ' ';
-
- while (*line = *wptr++)
- line++;
-
- if (ljflag)
- while (width-- > 0)
- *line++ = ' ';
- break;
-
- default: *line++ = c;
-
- }
- }
- else *line++ = c;
-
- *line = '\0';
- }
-
- /*
- Internal routine used by "_spr" to perform ascii-
- to-decimal conversion and update an associated pointer:
- */
-
- int _gv2(sptr)
- char **sptr;
- {
- int n;
- n = 0;
- while (isdigit(**sptr)) n = 10 * n + *(*sptr)++ - '0';
- return n;
- }
-
-
- /*
- Internal function which converts n into an ASCII
- base `base' representation and places the text
- at the location pointed to by the pointer pointed
- to by `string'. Yes, you read that correctly.
- */
-
- char _uspr(string, n, base)
- char **string;
- unsigned n;
- {
- char length;
- if (n<base) {
- *(*string)++ = (n < 10) ? n + '0' : n + 55;
- return 1;
- }
- length = _uspr(string, n/base, base);
- _uspr(string, n%base, base);
- return length + 1;
- }
-
-
- /*
- General formatted input conversion routine. "line" points
- to a string containing ascii text to be converted, and "fmt"
- points to an argument list consisting of first a format
- string and then a list of pointers to the destination objects.
-
- Appropriate data is picked up from the text string and stored
- where the pointer arguments point according to the format string.
- See K&R for more info. The field width specification is not
- supported by this version.
-
- NOTE: the "%s" termination character has been changed
- from "any white space" to the character following the "%s"
- specification in the format string. That is, the call
-
- sscanf(string, "%s:", &str);
-
- would ignore leading white space (as is the case with all
- format conversions), and then read in ALL subsequent text
- (including newlines) into the buffer "str" until a COLON
- or null byte is encountered.
-
- */
-
- int _scn(line,fmt)
- char *line, **fmt;
- {
- char sf, c, base, n, *sptr, *format;
- int sign, val, **args;
-
- format = *fmt++; /* fmt first points to the format string */
- args = fmt; /* now it points to the arg list */
-
- n = 0;
- while (c = *format++)
- {
- if (isspace(c)) continue; /* skip white space in format string */
- if (c != '%') /* if not %, must match text */
- {
- if (c != _igs(&line)) return n;
- else line++;
- }
- else /* process conversion */
- {
- sign = 1;
- base = 10;
- sf = 0;
- if ((c = *format++) == '*')
- {
- sf++; /* if "*" given, supress assignment */
- c = *format++;
- }
- switch (toupper(c))
- {
- case 'X': base = 16;
- goto doval;
-
- case 'O': base = 8;
- goto doval;
-
- case 'D': if (_igs(&line) == '-') {
- sign = -1;
- line++;
- }
-
- doval: case 'U': val = 0;
- if (_bc(_igs(&line),base) == ERROR)
- return n;
- while ((c = _bc(*line++,base)) != 255)
- val = val * base + c;
- line--;
- break;
-
- case 'S': _igs(&line);
- sptr = *args;
- while (c = *line++) {
- if (c == *format) {
- format++;
- break;
- }
- if (!sf) *sptr++ = c;
- }
- if (!sf) {
- n++;
- *sptr = '\0';
- args++;
- }
- continue;
-
- case 'C': if (!sf) {
- poke(*args++, *line);
- n++;
- }
- line++;
- continue;
-
- default: return n;
- }
- if (!sf)
- {
- **args++ = val * sign;
- n++;
- }
- }
- if (!*line) return n; /* if end of input string, return */
- }
- return n;
- }
-
- /*
- Internal function to position the character
- pointer argument to the next non white-space
- character in the string:
- */
-
- char _igs(sptr)
- char **sptr;
- {
- char c;
- while (isspace(c = **sptr)) ++*sptr;
- return (c);
- }
-
-
- /*
- Internal function to convert character c to value
- in base b , or return ERROR if illegal character for that
- base:
- */
-
- int _bc(c,b)
- char c,b;
- {
- if (isalpha(c = toupper(c))) c -= 55;
- else if (isdigit(c)) c -= 0x30;
- else return ERROR;
- if (c > b-1) return ERROR;
- else return c;
- }
-
-
- /*
- puts:
- Write out the given string to the console.
- A newline is NOT automatically appended:
- */
-
- puts(s)
- char *s;
- {
- while (*s) putchar(*s++);
- }
-
-
- /*
- fgets:
- This next function is like "gets", except that
- a) the line is taken from a buffered input file instead
- of from the console, and b) the newline is INCLUDED in
- the string and followed by a null byte.
-
- This one is a little tricky due to the CP/M convention
- of having a carriage-return AND a linefeed character
- at the end of every text line. In order to make text
- easier to deal with from C programs, this function (fgets)
- automatically strips off the CR from any CR-LF combinations
- that come in from the file. Any CR characters not im-
- mediately followed by LF are left intact. The LF
- is included as part of the string, and is followed
- by a null byte. (Note that LF equals "newline".)
- There is no limit to how long a line
- can be here; care should be taken to make sure the
- string pointer passed to fgets points to an area
- large enough to accept any possible line length
- (a line must be terminated by a newline (LF, or '\n')
- character before it is considered complete.)
-
- The value NULL (defined to be 0 here) is returned
- on EOF, whether it be a physical EOF (attempting to
- read past last sector of the file) OR a logical EOF
- (encountered a control-Z.) The 1.3 version didn't
- recognize logical EOFs, because I did't realize how
- SIMPLE it was to implement a buffered I/O "ungetc"
- function.
- */
-
- char *fgets(s,iobuf)
- char *s;
- struct _buf *iobuf;
- {
- int count, c;
- char *cptr;
- count = MAXLINE;
- cptr = s;
- if ( (c = getc(iobuf)) == CPMEOF || c == EOF) return NULL;
-
- do {
- if ((*cptr++ = c) == '\n') {
- if (cptr>s+1 && *(cptr-2) == '\r')
- *(--cptr - 1) = '\n';
- break;
- }
- } while (count-- && (c=getc(iobuf)) != EOF && c != CPMEOF);
-
- if (c == CPMEOF) ungetc(c,iobuf); /* push back control-Z */
- *cptr = '\0';
- return s;
- }
-
-
-
- /*
- fputs:
- This function writes a string out to a buffered
- output file. The '\n' character is expanded into
- a CR-LF combination, in keeping with the CP/M
- convention.
- If a null ('\0') byte is encountered before a
- newline is encountered, then there will be NO
- automatic termination character appended to the
- line.
- ERROR (-1) returned on error.
- */
-
- fputs(s,iobuf)
- char *s;
- struct _buf *iobuf;
- {
- char c;
- while (c = *s++) {
- if (c == '\n') putc('\r',iobuf);
- if (putc(c,iobuf) == ERROR) return ERROR;
- }
- return OK;
- }
-
-
- /*
- swapin:
- This is the swapping routine, to be used by the root
- segment to swap in code segments under an overlay scheme.
- See the "Overlays" document for details on overlay schemes.
-
- Returns ERROR (-1) on error, OK (0) if segment loaded in OK.
-
- This version does not check to make sure that the code
- yanked in doesn't overlap into the extenal data area (in
- the interests of keeping the function short.) But, if you'd
- like swapin to check for such problems, note that memory
- locations ram+115h and ram+116h contain the 16-bit address
- of the base of the external data area (low order byte first,
- as usual.) By rewriting swapin to read in one sector at a time
- and check the addresses, accidental overlap into the data area
- can be avoided.
- */
-
- swapin(name,addr)
- char *name; /* the file to swap in */
- {
- int fd;
- if (( fd = open(name,0)) == ERROR) {
- printf("Swapin: cannot open %s\n",name);
- return ERROR;
- }
- if ((read(fd,addr,512)) < 0) {
- printf("Swapin: read error on %s\n",name);
- close(fd);
- return ERROR;
- }
- close(fd);
- return OK;
- }
-