[comp.os.minix] enhanced version of doprintf

rmtodd@uokmax.UUCP (06/30/87)

Here is a revised version of the doprintf.c file.  It contains one bug fix
(a problem with printing the long number 0x80000000 in decimal format), and
several enhancements.  This version supports the %ld, %lu, %lx, and %lo
formats for long integers in addition to the already-existing %D,%X,%O
formats.  The %ld, %lu, %lx, %lo formats are the way almost every other
compiler's library uses to specify the printing of a long integer.  This
means the next time you port a program to MINIX you won't have to go
find every last occurence of printf("%ld",...) and change it to
printf("%D", ...) .  This version also supports variable field sizes in
the same way that most other printf()s do (at least, how the BSD4.2 and
Aztec printf() functions do it):  printf("%*d", size, value);  prints
the argument "value" in a field whose size is determined by the integer
"size".  The precision value of the format can also be variable--i.e.
you can have printf("%*.*s", width, precision, string) .   This is also
helpful in porting some programs to MINIX.
(I would have posted context diffs between the original version and mine, but
the diff turned out to be twice as long as the file :-().
--------------------------------------------------------------------------
Richard Todd
USSnail:820 Annie Court,Norman OK 73069
UUCP: {allegra!cbosgd|ihnp4}!okstate!uokmax!rmtodd

--------------------- cut here -----------------------------------------


#include "../include/stdio.h"

#define MAXDIGITS 12
#define PRIVATE   static

/*
** _doprintf(fp, format, args) -- function called by fprintf, printf, sprintf
** Revised 06/21/87 by rmtodd@uokmax to add %ld, %lx, %lu, %lo formats
** Revised 06/28/87 by rmtodd@uokmax to add variable-length formats 
** (e.g. printf("%*s",width,string);
*/

/* This is the same as varargs , on BSD systems */

#define GET_ARG(arglist,mode) ((mode *)(arglist += sizeof(mode)))[-1]

_doprintf(fp, format, args)
FILE *fp;
register char *format;
int args;
{
	register char *vl;
	int long_flag;
	int unsign_flag;
	int  r,
	    w1, w2,
	    sign;
	long l;
	char c;
	char *s;
	char padchar;
	char a[MAXDIGITS];

	vl = (char *) args;

	while ( *format != '\0') {
		if ( *format != '%' ) {
			putc( *format++, fp );
			continue;
		}

		w1 = 0;
		w2 = 0;
		sign = 1;
		long_flag = unsign_flag = 0;
		padchar = ' ';
		format++;


		/* handle variable-size formats */
		if ( *format == '*') {
			w1 = (int) GET_ARG(vl, int);
			format++;
		} else {
			if ( *format == '-' ) {
				sign = -1;
				format++;
			}

			if ( *format == '0') {
				padchar = '0';
				format++;
			}

			while ( *format >='0' && *format <='9' ){
				w1 = 10 * w1 + sign * ( *format - '0' );
				format++;
			}
		}

		if ( *format == '.'){
			format++;
			if ( *format == '*') {
				w2 = (int) GET_ARG(vl, int);
				format++;
			} else {
				while ( *format >='0' && *format <='9' ){
					w2 = 10 * w2 + ( *format - '0' );
					format++;
				}
			}
		}
		if (*format == 'l') {
			long_flag = 1;
			++format;
		}

		switch (*format) {
		case 'd':
			if (long_flag) {
				l = (long) GET_ARG(vl, long);
			} else {
				l = (long) GET_ARG(vl, int);
			}
			r = 10;
			break;
		case 'u':
			if (long_flag) {
				l = (long) GET_ARG(vl, long);
			} else {
				l = (long) GET_ARG(vl, int);
				l = l & 0xFFFF;
			}
			unsign_flag = 1;
			r = 10;
			break;
		case 'o':
			if (long_flag) {
				l = (long) GET_ARG(vl, long);
			} else {
				l = (long) GET_ARG(vl, int);
				if (l < 0) l = l & 0xFFFF;
			}
			unsign_flag = 1;
			r = 8;
			break;
		case 'x':
			if (long_flag) {
				l = (long) GET_ARG(vl, long);
			} else {
				l = (long) GET_ARG(vl, int);
				if (l < 0) l = l & 0xFFFF;
			}
			unsign_flag = 1;
			r = 16;
			break;
		case 'D':
			l = (long) GET_ARG(vl, long);
			r = 10;
			break;
		case 'O':
			l = (long) GET_ARG(vl, long);
			r = 8;
			break;
		case 'X':
			l = (long) GET_ARG(vl, long);
			r = 16;
			break;
		case 'c':
			c = (char) GET_ARG(vl, int); 
			/* char's are casted back to int's */
			putc( c, fp);
			format++;
			continue;
		case 's':
			s = GET_ARG(vl, char *);
			_printit(s,w1,w2,padchar,strlen(s),fp);
			format++;
			continue;
		default:
			putc(*format++,fp);
			continue;
		}

		_bintoascii (l, r, a, unsign_flag);
		_printit(a,w1,w2,padchar,strlen(a),fp);
		format++;
	}
}



PRIVATE _bintoascii (num, radix, a, unsg)
unsigned long    num;
int     radix;
char    *a;
int unsg; /* 1 if unsigned number output */
{
	char b[MAXDIGITS];
	int hit, negative;
	register int n, i;
	long temp;
	negative = 0;
	if (num == 0) {
		a[0] = '0';
		a[1] = '\0';
		return;
	}
	if (radix == 10 && !unsg) {
		temp = num;
		if (temp < 0) {num = -temp; negative++;}
	} 

	for (n = 0; n < MAXDIGITS; n++)
		b[n] = 0;

	n = 0;

	do {
		if (radix == 10) {
			b[n] = num % 10;
			num = (num - b[n]) / 10;
		}
		if (radix == 8) {
			b[n] = num & 0x7;
			num = (num >> 3) & 0x1FFFFFFF;
		}
		if (radix == 16) {
			b[n] = num & 0xF;
			num = (num >> 4) & 0x0FFFFFFF;
		}
		n++;
	} 
	while (num != 0);

	/* Convert to ASCII. */
	hit = 0;
	for (i = n - 1; i >= 0; i--) {
		if (b[i] == 0 && hit == 0) {
			b[i] = ' ';
		}
		else {
			if (b[i] < 10)
				b[i] += '0';
			else
				b[i] += 'A' - 10;
			hit++;
		}
	}
	if (negative)
		b[n++] = '-';
	
	for(i = n - 1 ; i >= 0 ; i-- )
		*a++ = b[i];
	
	*a = '\0';
	
}


PRIVATE _printit(str, w1, w2, padchar, length, file)
char *str;
int w1, w2;
char padchar;
int length;
FILE *file;
{
	int len2 = length;
	int temp;
	
	if ( w2 > 0  && length > w2) 
		len2 = w2;

	temp = len2;

	if ( w1 > 0 )
		while ( w1 > len2 ){
			--w1;
			putc(padchar,file);
		}
	
	while ( *str && ( len2-- != 0 ))
		putc(*str++, file);

	if ( w1 < 0 )
		if ( padchar == '0' ){
			putc('.',file);
			w1++;
		}
		while ( w1 < -temp ){
			w1++;
			putc(padchar,file);
		}
}