BSD 4_1c_2 release
[unix-history] / usr / src / ucb / ex / printf.c
/* The pwb version this is based on */
static char *printf_id = "@(#) printf.c:2.2 6/5/79";
/* The local sccs version within ex */
static char *sccsid = "@(#)printf.c 7.1 7/8/81";
#include "varargs.h"
/*
* This version of printf is compatible with the Version 7 C
* printf. The differences are only minor except that this
* printf assumes it is to print through putchar. Version 7
* printf is more general (and is much larger) and includes
* provisions for floating point.
*/
#define MAXOCT 11 /* Maximum octal digits in a long */
#define MAXINT 32767 /* largest normal length positive integer */
#define BIG 1000000000 /* largest power of 10 less than an unsigned long */
#define MAXDIGS 10 /* number of digits in BIG */
static int width, sign, fill;
char *_p_dconv();
printf(va_alist)
va_dcl
{
va_list ap;
register char *fmt;
char fcode;
int prec;
int length,mask1,nbits,n;
long int mask2, num;
register char *bptr;
char *ptr;
char buf[134];
va_start(ap);
fmt = va_arg(ap,char *);
for (;;) {
/* process format string first */
while ((fcode = *fmt++)!='%') {
/* ordinary (non-%) character */
if (fcode=='\0')
return;
putchar(fcode);
}
/* length modifier: -1 for h, 1 for l, 0 for none */
length = 0;
/* check for a leading - sign */
sign = 0;
if (*fmt == '-') {
sign++;
fmt++;
}
/* a '0' may follow the - sign */
/* this is the requested fill character */
fill = 1;
if (*fmt == '0') {
fill--;
fmt++;
}
/* Now comes a digit string which may be a '*' */
if (*fmt == '*') {
width = va_arg(ap, int);
if (width < 0) {
width = -width;
sign = !sign;
}
fmt++;
}
else {
width = 0;
while (*fmt>='0' && *fmt<='9')
width = width * 10 + (*fmt++ - '0');
}
/* maybe a decimal point followed by more digits (or '*') */
if (*fmt=='.') {
if (*++fmt == '*') {
prec = va_arg(ap, int);
fmt++;
}
else {
prec = 0;
while (*fmt>='0' && *fmt<='9')
prec = prec * 10 + (*fmt++ - '0');
}
}
else
prec = -1;
/*
* At this point, "sign" is nonzero if there was
* a sign, "fill" is 0 if there was a leading
* zero and 1 otherwise, "width" and "prec"
* contain numbers corresponding to the digit
* strings before and after the decimal point,
* respectively, and "fmt" addresses the next
* character after the whole mess. If there was
* no decimal point, "prec" will be -1.
*/
switch (*fmt) {
case 'L':
case 'l':
length = 2;
/* no break!! */
case 'h':
case 'H':
length--;
fmt++;
break;
}
/*
* At exit from the following switch, we will
* emit the characters starting at "bptr" and
* ending at "ptr"-1, unless fcode is '\0'.
*/
switch (fcode = *fmt++) {
/* process characters and strings first */
case 'c':
buf[0] = va_arg(ap, int);
ptr = bptr = &buf[0];
if (buf[0] != '\0')
ptr++;
break;
case 's':
bptr = va_arg(ap,char *);
if (bptr==0)
bptr = "(null pointer)";
if (prec < 0)
prec = MAXINT;
for (n=0; *bptr++ && n < prec; n++) ;
ptr = --bptr;
bptr -= n;
break;
case 'O':
length = 1;
fcode = 'o';
/* no break */
case 'o':
case 'X':
case 'x':
if (length > 0)
num = va_arg(ap,long);
else
num = (unsigned)va_arg(ap,int);
if (fcode=='o') {
mask1 = 0x7;
mask2 = 0x1fffffffL;
nbits = 3;
}
else {
mask1 = 0xf;
mask2 = 0x0fffffffL;
nbits = 4;
}
n = (num!=0);
bptr = buf + MAXOCT + 3;
/* shift and mask for speed */
do
if (((int) num & mask1) < 10)
*--bptr = ((int) num & mask1) + 060;
else
*--bptr = ((int) num & mask1) + 0127;
while (num = (num >> nbits) & mask2);
if (fcode=='o') {
if (n)
*--bptr = '0';
}
else
if (!sign && fill <= 0) {
putchar('0');
putchar(fcode);
width -= 2;
}
else {
*--bptr = fcode;
*--bptr = '0';
}
ptr = buf + MAXOCT + 3;
break;
case 'D':
case 'U':
case 'I':
length = 1;
fcode = fcode + 'a' - 'A';
/* no break */
case 'd':
case 'i':
case 'u':
if (length > 0)
num = va_arg(ap,long);
else {
n = va_arg(ap,int);
if (fcode=='u')
num = (unsigned) n;
else
num = (long) n;
}
if (n = (fcode != 'u' && num < 0))
num = -num;
/* now convert to digits */
bptr = _p_dconv(num, buf);
if (n)
*--bptr = '-';
if (fill == 0)
fill = -1;
ptr = buf + MAXDIGS + 1;
break;
default:
/* not a control character,
* print it.
*/
ptr = bptr = &fcode;
ptr++;
break;
}
if (fcode != '\0')
_p_emit(bptr,ptr);
}
va_end(ap);
}
/* _p_dconv converts the unsigned long integer "value" to
* printable decimal and places it in "buffer", right-justified.
* The value returned is the address of the first non-zero character,
* or the address of the last character if all are zero.
* The result is NOT null terminated, and is MAXDIGS characters long,
* starting at buffer[1] (to allow for insertion of a sign).
*
* This program assumes it is running on 2's complement machine
* with reasonable overflow treatment.
*/
char *
_p_dconv(value, buffer)
long value;
char *buffer;
{
register char *bp;
register int svalue;
int n;
long lval;
bp = buffer;
/* zero is a special case */
if (value == 0) {
bp += MAXDIGS;
*bp = '0';
return(bp);
}
/* develop the leading digit of the value in "n" */
n = 0;
while (value < 0) {
value -= BIG; /* will eventually underflow */
n++;
}
while ((lval = value - BIG) >= 0) {
value = lval;
n++;
}
/* stash it in buffer[1] to allow for a sign */
bp[1] = n + '0';
/*
* Now develop the rest of the digits. Since speed counts here,
* we do it in two loops. The first gets "value" down until it
* is no larger than MAXINT. The second one uses integer divides
* rather than long divides to speed it up.
*/
bp += MAXDIGS + 1;
while (value > MAXINT) {
*--bp = (int)(value % 10) + '0';
value /= 10;
}
/* cannot lose precision */
svalue = value;
while (svalue > 0) {
*--bp = (svalue % 10) + '0';
svalue /= 10;
}
/* fill in intermediate zeroes if needed */
if (buffer[1] != '0') {
while (bp > buffer + 2)
*--bp = '0';
--bp;
}
return(bp);
}
/*
* This program sends string "s" to putchar. The character after
* the end of "s" is given by "send". This allows the size of the
* field to be computed; it is stored in "alen". "width" contains the
* user specified length. If width<alen, the width will be taken to
* be alen. "sign" is zero if the string is to be right-justified
* in the field, nonzero if it is to be left-justified. "fill" is
* 0 if the string is to be padded with '0', positive if it is to be
* padded with ' ', and negative if an initial '-' should appear before
* any padding in right-justification (to avoid printing "-3" as
* "000-3" where "-0003" was intended).
*/
_p_emit(s, send)
register char *s;
char *send;
{
char cfill;
register int alen;
int npad;
alen = send - s;
if (alen > width)
width = alen;
cfill = fill>0? ' ': '0';
/* we may want to print a leading '-' before anything */
if (*s == '-' && fill < 0) {
putchar(*s++);
alen--;
width--;
}
npad = width - alen;
/* emit any leading pad characters */
if (!sign)
while (--npad >= 0)
putchar(cfill);
/* emit the string itself */
while (--alen >= 0)
putchar(*s++);
/* emit trailing pad characters */
if (sign)
while (--npad >= 0)
putchar(cfill);
}