/*
 *			d o p r n t . c
 *
 */

/*)LIBRARY
*/

#ifdef	DOCUMENTATION

title		Output Formatter
index		Output formatter

internal

synopsis
	.s.nf
	c_doprnt(format, argvec, iov)
	char		*format;
	int		*argvec[];
	FILE		*iov;
	.s.f
Description

	c_doprnt() performs all formatting operations for printf(),
	sprintf(), and fprintf().  The formatting options are described
	in the documentation of printf().

Bugs

	c_doprnt() is functionally identical to the _doprnt() module
	in Unix and Vax-11 C libraries.  Unfortunately, the initial '_'
	conflicts with RSX FCS library conventions.

#endif

#include		<stdio.h>
#define	EOS		('\0')
#define	FALSE		0
#define	TRUE		1
#ifdef	PDP10
#define	BITS_PER_CHAR	36
#else
#define	BITS_PER_CHAR	8
#endif

/*
 * WORKSIZE is the dimension of work[] which must hold enough characters
 * to store a long integer (expanded using the %b format) + '-' and EOS
 */
#define	WORKSIZE	(((sizeof (long)) * BITS_PER_CHAR) + 2)

struct	fmtbuf {
	char	f_type;		/* Format type ('d', etc.)		*/
	char	f_width;	/* Argument width (INT or LONG)		*/
	char	f_radix;	/* Argument radix (8, 10, etc.)		*/
};

#define	INT	0
#define	LONG	1
/*
 * formatinfo[] stores information about the "value" formats
 */
static struct fmtbuf formatinfo[] = {
	{ 'b',	INT,	 2 },
	{ 'd',	INT,	10 },
	{ 'o',	INT,	 8 },
	{ 'u',	INT,	10 },
	{ 'x',	INT,	16 },
	{ 'B',	LONG,	 2 },
	{ 'D',	LONG,	10 },
	{ 'O',	LONG,	 8 },
	{ 'U',	LONG,	10 },
	{ 'X',	LONG,	16 },
	{ EOS,	0,	 0 },
};

c_doprnt(format, arg, fildes)
char		*format;		/* Format string		*/
register union {
	char		**s;
	int		*i;
	unsigned int	*u;
	long		*l;
	char		*c;
} arg;
FILE		*fildes;		/* File descriptor		*/
{
	register int	c;
	register union {
		struct fmtbuf	*fmt;
		char		*result;
	} p;		
	char		ljust;		/* TRUE for left-justification	*/
	char		work[WORKSIZE];	/* Number buffer		*/
	char		fill;		/* '0' or ' ' for fill		*/
	int		prec;		/* Precision			*/
	int		slen;		/* Field length			*/
	int		width;		/* Argument width		*/
	long		value;		/* Current argument value	*/
	int		temp;		/* Set if '-' needed.		*/
	int		radix;		/* Conversion radix		*/

	while ((c = *format++) != EOS) {
	    /*
	     * Check for conversion specifications.
	     */
	    if (c == '%') {
		/*
		 * Check for various options.
		 */
		c = *format++;
		ljust = 0;			/* No left adjustment	*/
		prec = 0;			/* Print all the arg.	*/
		fill = ' ';			/* Fill with spaces	*/
		if (c == '-') {			/* %-	left justify	*/
		    ljust++;
		    c = *format++;
		}
		if (c == '0') {			/* %0d	zero fill arg.	*/
		    fill = c;
		    c = *format++;
		}
		if (c == '?' || c == '*') {	/* %*	width from arg	*/
		    width = *arg.i++;
		    c = *format++;
		}
		else {				/* %n	field width	*/
		    width = 0;
		    while (isdigit(c)) {
			width *= 10;
			width += (c - '0');
			c = *format++;
		    }
		}
		prec = 32767;
		if (c == '.') {			/* %n.n	precision	*/
		    c = *format++;		/* %n.* (from arg)	*/
		    if (c == '?' || c == '*') {
			prec = *arg.i++;
			c = *format++;
		    }
		    else {
			while (isdigit(c)) {
			    prec *= 10;
			    prec += (c - '0');
			    c = *format++;
			}
		    }
		}
		/*
		 * Process conversion chars, understanding longs.
		 */
		if (c == 'l' || c == 'L') {
		    switch (c = *format++) {
		    case 'b':
		    case 'd':
		    case 'o':
		    case 'u':
		    case 'x':
			c = toupper(c);	/* Make %lb %B		*/
		    case 'B':
		    case 'D':
		    case 'O':
		    case 'U':
		    case 'X':
			break;
		    default:		/* Here on %Lfoo == %Ufoo	*/
			c = 'U';
			format--;
			break;
		    }
		}
		/*
		 * Search the numeric format structure for this conversion.
		 */
		for (p.fmt = formatinfo;
			(p.fmt->f_type != c && p.fmt->f_type != EOS);
			p.fmt++)
		    ;
		if (p.fmt->f_type != EOS) {
		    /*
		     * A numeric conversion was found.
		     * Get the value and expand it into the work area.
		     */
		    if (p.fmt->f_width == INT) {
			if (c == 'd')
			    value = *arg.i++;
			else
			    value = *arg.u++;
		    }
		    else {
			value = *arg.l++;
		    }
		    radix = p.fmt->f_radix;
		    p.result = &work[WORKSIZE];
		    *--p.result = EOS;		/* Terminate result	*/
		    if (value == 0)
			*--p.result = '0';
		    else {
			/*
			 * Convert a signed non-zero number.
			 * temp will signal whether a '-' is needed.
			 */
			if ((temp = (value < 0 && c == 'd' || c == 'D')))
			    value = (-value);
			/*
			 * The sign (if needed) is stored in temp.
			 */
			while (value > 0) {
			    c = value % radix;
			    *--p.result = c + ((c < 10) ? '0' : ('a' - 10));
			    value /= radix;
			}
			if (temp)
			    *--p.result = '-';
		    }
		}
		else { 
		    /*
		     * String or something
		     */		
		    switch (c) {
		    case 'q':			/* Funny int value	*/
			/*
			 * Convert a word as a pair of octal bytes.
			 */
			c = 16384;
			p.result = work;
			temp = *arg.i++;
			while (c > 0) {
			    *p.result++ = (temp / c) + '0';
			    temp %= c;
			    if (c == 256) {
				c = 64;
				*p.result++ = '.';
			    }
			    else
				c >>= 3;
			}
			*p.result = EOS;
			p.result = work;
			break;

		    case 'r':			/* Remote format	*/
			format = *arg.s++;
			continue;		/* No print here	*/

		    case 's':			/* String		*/
			if ((p.result = *arg.s++) == NULL)
			    p.result = "{NULL}";	/* Bug hack	*/
			break;

		    case 'c':			/* Character		*/
			c = *arg.c++;
			/*
			 * Fall through
			 */
		    default:			/* %% or whatever	*/
		    	p.result = work;
			*p.result = c;
			work[1] = EOS;
			break;
		    }
		}
		/*
		 * p.result -> first byte of string to output.
		 * Reuse c as a register temp.
		 */
		c = strlen(p.result);		/* True result length	*/
		slen = (c > prec) ? prec : c;	/* Field length		*/
		if (!ljust) {			/* Right justify?	*/
		    while (width-- > slen) {
			putc(fill, fildes);
		    }
		}
		/*
		 * Output the string (up to "prec" bytes)
		 */
		for (c = prec; *p.result != EOS && --c >= 0;) {
		    putc(*p.result++, fildes);
		}
		if (ljust) {			/* Left justify?	*/
		    while (width-- > slen) {
			putc(' ', fildes);
		    }
		}
	    }
	    else {
		/*
		 * Not in a % thing, just output the byte.
		 */
		putc(c, fildes);
	    }
	}
	return;
}


