home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.barnyard.co.uk
/
2015.02.ftp.barnyard.co.uk.tar
/
ftp.barnyard.co.uk
/
cpm
/
walnut-creek-CDROM
/
CPM
/
BDSC
/
BDSC-1
/
STDLIB2.C
< prev
next >
Wrap
Text File
|
2000-06-30
|
17KB
|
652 lines
/*
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;
}