home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The C Users' Group Library 1994 August
/
wc-cdrom-cusersgrouplibrary-1994-08.iso
/
vol_100
/
106_01
/
stdlib2.c
< prev
next >
Wrap
Text File
|
1980-07-09
|
20KB
|
831 lines
/* ---------------------------------------------------------
Mods by H Moran:
In case you wish to adopt some but not all of the mods:
Lines modified by HRM marked with comment 'HRM mod'
Lines added by HRM marked with comment 'HRM'
1) added functions: for list reader and punch devices
lprintf pprintf -- list and punch devices
rscanf -- reader device
rgets -- reader device
lputs pputs -- list and punch devices
lputc pputc -- list and punch devices
rgetc -- reader device
2) sprintf() modified to use '0' for left fill char
when radix is not decimal.
3) binary field spec added to sprintf() and sscanf()
spec used is %b
4) pad char for non-decimal numeric fields in sprintf()
changed to '0'
5) printing of a..f changed to A..F in _uspr()
6) fgets() fixed to remember reception of 0x1a
and handle it as EOF. See the one caveat in
the modified comments.
-------------------------------------------------------
*/
/*
This file contains the source for the following
library functions:
printf ** fprintf ** sprintf *
scanf fscanf sscanf
gets fgets
puts fputs
swapin ***
*) rewritten for 1.31
**) work differently since they use the new "sprintf"
***) brand new for 1.31
An alternate version of "sprintf" is given in the file
FLOAT.C for use with floating point numbers; see FLOAT.C
for details. Note that "sprintf" is used by "printf", so
this really amounts to an alternate version of "printf",
too.
Note that all the upper-level formatted I/O functions
("printf", "fprintf", "scanf", and "fscanf") now use
"sprintf" and "sscanf" for doing conversions. While
this leads to very structured 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.)
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 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' char-
acters 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 maintaining compatibility with the CP/M text
format (so that, for example, a text file can be
"type"d under the CCP.)
To confuse matters further, the "gets" function
(which simply buffers up a line of console input)
terminates a line with '\0' (a zero byte) instead
of CR or LF. Thus, if you wanted to read in lines
of input from the console and write them to a file,
you'd have to manually put out the CR and LF at the
end of every line. Oh, and don't forget the 0x1a
(control-Z) at the end of the file! Hey, kiddies,
isn't CP/M fun to work with???
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. This means that there
are two possible return values from "getc" which
signal and EOF: 0x1a (control-Z), and -1 (or 255
if you assign it to a char variable.)
*/
#define RAM 0 /* start of CP/M RAM area */
#define MAXLINE 132 /* maximum length of text line */
#define EOF -1 /* End of file val returned by getc */
#define NULL 0 /* Returned by fgets on EOF */
char toupper(), isdigit();
struct buf {
int fd;
int nleft;
char *nextp;
char buff[128];
};
/*
printf
usage:
printf(format, arg1, arg2, ...);
Note that since the sprintf 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.
See the "sprintf" documentation, below, for more info.
*/
printf(format)
char *format;
{
char line[MAXLINE];
_mvl();
sprintf(line,format);
puts(line);
}
/*
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:
(-1 returned on error)
usage:
fprintf(iobuf, format, arg1, arg2, ...);
*/
fprintf(iobuf,format)
char *format;
struct buf *iobuf;
{
char text[MAXLINE];
sprintf(text);
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.
*/
int fscanf(iobuf,format,arg1)
char *format;
struct buf *iobuf;
{
char text[MAXLINE];
if (!fgets(text,iobuf)) return 0;
return sscanf(text,format,arg1);
}
/*
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.
*/
scanf(format)
char *format;
{
char line[MAXLINE];
_mvl();
gets(line);
sscanf(line,format);
}
/*
Internal function to move all function arguments
over one place to the right, to make room for
a new argument in the first position. This is
necessary so that, for example, "sprintf" can
be called from within "printf" without clobbering
one of the arguments. This is NOT a portable
feature of BDS C, and in fact represents one of
the biggest kludges in the package. Oh well; live
and learn.
*/
_mvl()
{
int count, *ptr;
ptr = (RAM + 0x3f7 + 0x2e);
count = 23;
while (count--) *ptr = *--ptr;
}
/*
sprintf:
Like fprintf, except a string pointer is specified
instead of a buffer pointer. The text is written
to where the string pointer points.
Usage:
sprintf(string,format,arg1, arg2, ...);
This is my latest version of the "sprintf" 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 GET
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
"sprintf" 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 this version of sprintf 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...
*/
sprintf(line,format)
char *line, *format;
{
char _uspr(), c, base, *sptr;
char wbuf[80], *wptr, pf, ljflag;
int width, precision, *args;
char lpadchr; /* HRM */
args = (RAM + 0x3fb);
while (c = *format++)
if (c == '%') {
wptr = wbuf;
precision = 6;
ljflag = pf = 0;
if (*format == '-') {
format++;
ljflag++;
}
if ( !(width = _gv2(&format))) width++;
if ((c = *format++) == '.') {
precision = _gv2(&format);
pf++;
c = *format++;
}
lpadchr = '0';/* HRM pad char for non-decimal numeric fields */
switch(toupper(c)) {
case 'D': lpadchr = ' ';
if (*args < 0) {
*wptr++ = '-';
*args = -*args;
width--;
}
case 'U': base = 10; goto val;
case 'B': base = 2; goto val; /* HRM */
case 'X': base = 16; goto val;
case 'O': base = 8;
val: width -= _uspr(&wptr,*args++,base);
goto pad;
case 'C': lpadchr = ' '; /* HRM */
*wptr++ = *args++;
width--;
goto pad;
case 'S': lpadchr = ' '; /* HRM */
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++ = lpadchr; /* HRM mod */
while (*line = *wptr++)
line++;
if (ljflag)
while (width-- > 0)
*line++ = ' ';
break;
default: *line++ = c;
}
}
else *line++ = c;
*line = '\0';
}
/*
Internal routine used by "sprintf" 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+('A'-10);/* HRM mod */
return 1;
}
length = _uspr(string, n/base, base);
_uspr(string, n%base, base);
return length + 1;
}
/*
gets:
Accept an input line from the console, and
store it at the location pointed to by the
character pointer argument.
The newline is NOT included as part of the
returned text; i.e., typing "abc" and hitting
carriage-return will result in the string:
"abc\0"
*/
gets(s)
char *s;
{
char c, *temp;
int count, i;
temp = s;
start: count = 0;
s = temp;
while ((c=getchar()) != '\n')
switch(c) {
case 0x15:
putchar('\n');
goto start;
case 0x7f:
if (!count) break;
--count;
--s;
putchar(0x08); putchar(' '); putchar(0x08);
break;
case 0x12:
putchar('\n');
for (i=0; i<count; ++i) putchar(temp[i]);
break;
default:
*s++ = c;
++count;
}
*s = 0;
return temp;
}
/*
puts:
Write out the given string to the console:
*/
puts(s)
char *s;
{
while (*s) putchar(*s++);
}
/*
fgets:
This next function is like "gets", except that
the line is taken from a buffered input file instead
of from the console. 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
under 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 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.)
comment below modified by HRM
The value NULL (defined to be 0 here) is returned
on EOF. If 0x1a is encountered, the string is considered
terminated and the 0x1a is pushed bak using unget().
Note that although unget() will return an error of NULL
this routine does not bother to check because that can
happen only with multiple pushbacks and this routine
will never push back more than a single 0x1a. The next
invocation of getc() will retrieve the 0x1a with as the
first char of the string (which is recognized as EOF)
This means you must not interleave calls to getc() and
fgets() unless you explicitly handle the case of a 0x1a
received from getc().
*/
char *fgets(s,iobuf)
char *s;
struct buf *iobuf;
{
int count, c;
char *cptr;
count = (MAXLINE + 1);
cptr = s;
while (count-- && (c = getc(iobuf)) != EOF && (c != 0x1a))
if ((*cptr++ = c) == '\n') {
if (cptr>s+1 && *(cptr-2) == '\r') {
*(cptr-2) = '\n';
cptr--;
}
break;
}
*cptr = '\0';
if ( (c == 0x1a) ) { /* HRM */
if ( cptr == s ) /* HRM left over from last call */
return NULL; /* HRM */
ungetc(iobuf); /* HRM */
return s; /* HRM */
} /* HRM */
return ( (( c==EOF) && (cptr == s)) ? NULL : 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.
-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) == -1) return -1;
}
return 0;
}
/*
sscanf:
This is the formatted input scanner, taking a
text string, a format string and a list of pointer
arguments, appropriate data is picked up from the
text string and stored where the pointer arguments
point according to the format string. See K&R.
Note that the field width specification is not
supported by this version.
*/
sscanf(line,format)
char *line, *format;
{
char sf, c, base, n, *sptr;
int sign, val, **args;
args = (RAM + 0x3fb);
n = 0;
while (c = *format++) {
if (isspace(c)) continue;
if (c != '%') {
if (c != _igs(&line)) return n;
else line++;
}
else {
sign = 1;
base = 10;
sf = 0;
if ((c = *format++) == '*') {
sf++;
c = *format++;
}
switch (toupper(c)) {
case 'X': base = 16;
goto doval;
case 'O': base = 8;
goto doval;
case 'B': base = 2; /* HRM */
goto doval; /* HRM */
case 'D': if (_igs(&line) == '-') {
sign = -1;
line++;
}
doval: case 'U': val = 0;
if (_bc(_igs(&line),base)== -1)
return n;
while ((c=_bc(*line++,base))!=255)
val = val * base + c;
line--;
break;
case 'S': _igs(&line);
sptr = *args;
while ((c=*line++)&& !isspace(c))
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++;
}
}}
return n;
}
/*
Internal function to position the character
pointer argument to the next non white-space
character in the string:
*/
_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 -1 if illegal character for that
base:
*/
_bc(c,b)
char c,b;
{
if ((c = toupper(c)) > '9') c -= 55;
else c -= 0x30;
return (c > b-1) ? -1 : c;
}
/*
swapin:
This is the swapping routine, to be used by the root
segment to swap in a code segment in the area of memory
between the end of the root segment and the start of the
external data area. See the document "SWAPPING.DOC" for
detailed info on the swapping scheme.
Returns -1 on error, 0 if segment swapped 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)) == -1) {
printf("Swapin: cannot open %s\n",name);
return -1;
}
if ((read(fd,addr,9999)) < 0) {
printf("Swapin: read error on %s\n",name);
return -1;
}
return 0;
}
/* HRM -------- all lines below added by H Moran -------- */
/*
formatted output to list device
usage:
lprintf(format, arg1, arg2, ...);
*/
lprintf(format) /* formatted print to list device */
char *format;
{
char line[MAXLINE];
_mvl();
sprintf(line,format);
lputs(line);
}
/*
formatted print to punch device
usage:
fprintf(iobuf, format, arg1, arg2, ...);
*/
pprintf(format)
char *format;
{
char text[MAXLINE];
sprintf(text);
pputs(text);
}
/*
formatted scan of reader device
Usage:
rscanf(format, ptr1, ptr2, ...);
*/
rscanf(format,arg1)
char *format;
{
char text[MAXLINE];
if ( ! rgets(text) )
return 0;
return sscanf(text,format,arg1);
}
/*
Accept an input line from the reader device, and
store it at the location pointed to by the
character pointer argument.
The newline is NOT included as part of the
returned text; i.e., receiving "abc" and carriage-return
will result in the string: "abc\0"
*/
rgets(s)
char *s;
{
char c,*temp;
int count, i;
char rgetc();
temp = s;
for( count=0; (c=rgetc()) != '\n'; count++ )
*s++ = c;
*s='\0';
return temp;
}
/*
Write out the given string to the list device
*/
lputs(s)
char *s;
{
while ( *s )
lputc(*s++);
}
/*
Write out the given string to the punch device
*/
pputs(s)
char *s;
{
while ( *s )
pputc(*s++);
}
/*
read a char from the reader device
*/
rgetc()
{
return bios(6,0) & 255;
}
/*
send a char to the punch device
*/
pputc(c)
char c;
{
return bios(5,c) & 255;
}
/*
send a char to the list device
*/
lputc(c)
char c;
{
return bios(4,c) & 255;
}